diff --git a/.bettercodehub.yml b/.bettercodehub.yml deleted file mode 100644 index f3e187636e..0000000000 --- a/.bettercodehub.yml +++ /dev/null @@ -1,11 +0,0 @@ -exclude: -- /examples/.* -- /src/VSIntegration/.* -- /src/Docs/.* -- /src/NLogAutoLoadExtension/.* -- /src/InstallNLogConfig/.* -- /tests/SampleExtensions/.* -- /tools/.* -component_depth: 3 -languages: -- csharp diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index cdaf5f100b..0918c860e4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,9 +8,9 @@ Hi! Thanks for reporting this bug! Please keep / fill in the relevant info from this template so that we can help you as best as possible. -**NLog version**: (e.g. 4.4.13) +**NLog version**: (e.g. 4.7.15) -**Platform**: .Net 3.5 / .Net 4.0 / .Net 4.5 / Mono 4 / Xamarin Android / Xamarin iOS / .NET Core / .NET5 / .NET6 +**Platform**: .NET8 / .NET Framework 4.8 / Linux / Android / iOS / UWP / Unity / etc. **Current NLog config** (xml or C#, if relevant) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index b7bafb5b71..362a620476 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -8,9 +8,9 @@ Hi! Thanks for reporting this feature! Please keep / fill in the relevant info from this template so that we can help you as best as possible. -**NLog version**: (e.g. 4.4.13) +**NLog version**: (e.g. 4.7.15) -**Platform**: .Net 3.5 / .Net 4.0 / .Net 4.5 / Mono 4 / Xamarin Android / Xamarin iOS / .NET Core / .NET5 / .NET6 +**Platform**: .NET8 / .NET Framework 4.8 / Linux / Android / iOS / UWP / Unity / etc. **Current NLog config** (xml or C#, if relevant) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 81e66e2bc0..6f6e4ecf9b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,49 +6,4 @@ updates: schedule: interval: daily open-pull-requests-limit: 10 - ignore: - - dependency-name: Microsoft.Extensions.PlatformAbstractions - versions: - - ">= 1.1.a, < 1.2" - - dependency-name: System.ComponentModel.Primitives - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.ComponentModel.TypeConverter - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.Data.Common - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.Diagnostics.Process - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.Diagnostics.StackTrace - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.Diagnostics.TraceSource - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.IO.FileSystem.Watcher - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.Net.NameResolution - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.Net.Requests - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.Reflection.TypeExtensions - versions: - - ">= 4.3.a, < 4.7" - - dependency-name: System.Runtime.Loader - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.Threading.Thread - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.Xml.XmlDocument - versions: - - ">= 4.3.a, < 4.4" - - dependency-name: System.Xml.XmlSerializer - versions: - - ">= 4.3.a, < 4.4" \ No newline at end of file + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cc0502d8f5..f81ff568df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,160 @@ Date format: (year/month/day) ## Change Log +### Version 6.0.2 (2025/07/20) + +**Improvements** + +- [#5930](https://github.com/NLog/NLog/pull/5930) XmlParser - Handle XML comments after root-end-tag. (@snakefoot) +- [#5929](https://github.com/NLog/NLog/pull/5929) XmlLoggingConfiguration - Improve handling of invalid XML. (@snakefoot) +- [#5933](https://github.com/NLog/NLog/pull/5933) Handle invalid message template when skipping parameters array. (@snakefoot) +- [#5915](https://github.com/NLog/NLog/pull/5915) ReplaceNewLinesLayoutRendererWrapper - Replace more line ending characters. (@oikku) +- [#5911](https://github.com/NLog/NLog/pull/5911) NLog.Targets.GZipFile - Improve support for ArchiveAboveSize. (@snakefoot) +- [#5921](https://github.com/NLog/NLog/pull/5921) FileTarget - Activate legacy ArchiveFileName when ArchiveSuffixFormat contains legacy placeholder. (@snakefoot) +- [#5924](https://github.com/NLog/NLog/pull/5924) AsyncTargetWrapper - Updated FullBatchSizeWriteLimit default value from 5 to 10. (@snakefoot) +- [#5937](https://github.com/NLog/NLog/pull/5937) Mark Assembly loading with RequiresUnreferencedCodeAttribute for AOT. (@snakefoot) +- [#5938](https://github.com/NLog/NLog/pull/5938) Logger - Align WriteToTargets with WriteToTargetsWithSpan. (@snakefoot) +- [#5909](https://github.com/NLog/NLog/pull/5909) ConfigurationItemFactory - Added extension hints for webservice and activityid. (@snakefoot) +- [#5918](https://github.com/NLog/NLog/pull/5918) Log4JXmlTarget - Removed alias NLogViewer as conflicts with other nuget-packages. (@snakefoot) +- [#5926](https://github.com/NLog/NLog/pull/5926) SplunkTarget - NetworkTarget with SplunkLayout. (@snakefoot) +- [#5927](https://github.com/NLog/NLog/pull/5927) GelfLayout - Align with SplunkLayout. (@snakefoot) +- [#5913](https://github.com/NLog/NLog/pull/5913) NLog.Targets.Network - Updated nuget-package README.md. (@snakefoot) +- [#5912](https://github.com/NLog/NLog/pull/5912) NLog.Targets.Trace - Updated nuget-package README.md. (@snakefoot) +- [#5919](https://github.com/NLog/NLog/pull/5919) XML docs for Targets and Layouts with remarks about default value. (@snakefoot) +- [#5922](https://github.com/NLog/NLog/pull/5922) XML docs for LayoutRenderers with remarks about default value. (@snakefoot) +- [#5925](https://github.com/NLog/NLog/pull/5925) XML docs for Target Wrappers with remarks about default value. (@snakefoot) +- [#5935](https://github.com/NLog/NLog/pull/5935) Improve NLog XSD Schema with better handling of typed Layout. (@snakefoot) +- [#5923](https://github.com/NLog/NLog/pull/5923) Updated unit-tests from NET6 to NET8. (@snakefoot) + +### Version 5.5.1 (2025/07/18) + +**Improvements** + +- [#5858](https://github.com/NLog/NLog/pull/5858) ConsoleTarget - Added ForceWriteLine to match NLog v6 Schema (#5858) (@snakefoot) +- [#5866](https://github.com/NLog/NLog/pull/5866) Layout.FromLiteral to match NLog v6 (#5866) (@snakefoot) +- [#5888](https://github.com/NLog/NLog/pull/5888) ChainsawTarget with type-alias Log4JXml to match NLog v6 (#5888) (@snakefoot) +- [#5883](https://github.com/NLog/NLog/pull/5883) AsyncTargetWrapper - LogEventDropped and EventQueueGrow events fixes (#5883) (@dance) +- [#5890](https://github.com/NLog/NLog/pull/5890) StringBuilderExt - Change Append2DigitsZeroPadded to array-lookup (#5890) (@snakefoot) +- [#5936](https://github.com/NLog/NLog/pull/5936) XmlLayout - Support MaxRecursionLimit == 0 (#5936) (@snakefoot) +- [#5936](https://github.com/NLog/NLog/pull/5936) RegisterObjectTransformation so build trimming will keep public properties (#5936) (@snakefoot) + +### Version 6.0.1 (2025/06/27) + +**Improvements** + +- [#5898](https://github.com/NLog/NLog/pull/5898) Changed ConditionExpression to be nullable by default since no Condition means no filtering. (@snakefoot) +- [#5906](https://github.com/NLog/NLog/pull/5906) Include ConditionExpression in the static type registration. (@snakefoot) +- [#5895](https://github.com/NLog/NLog/pull/5895) Fixed the new XML parser to handle XML comments just before end-tag. (@snakefoot) +- [#5905](https://github.com/NLog/NLog/pull/5905) Fixed the new XML parser to allow InnerText with greater-than characters. (@snakefoot) +- [#5891](https://github.com/NLog/NLog/pull/5891) Updated NLog.Targets.AtomicFile to support net8.0-windows without dependency on Mono.Posix.NETStandard. (@snakefoot) + +### Version 6.0 (2025/06/21) + +**Major Changes** + +- Support AOT builds without build warnings. +- New FileTarget without ConcurrentWrites support, but still support KeepFileOpen (true/false). +- Moved old FileTarget into the new nuget-package NLog.Targets.ConcurrentFile. +- Created new nuget-package NLog.Targets.AtomicFile that supports ConcurrentWrites for NET8 on both Windows / Linux. +- Created new nuget-package NLog.Targets.GZipFile that uses GZipStream for writing directly to compressed files. +- Moved MailTarget into the new nuget-package NLog.Targets.Mail. +- Moved NetworkTarget into the new nuget-package NLog.Targets.Network. +- New GelfTarget introduced for the new nuget-package NLog.Targets.Network. +- New SyslogTarget introduced for the new nuget-package NLog.Targets.Network. +- Moved TraceTarget and NLogTraceListener into the new nuget-package NLog.Targets.Trace. +- Moved WebServiceTarget into the new nuget-package NLog.Targets.WebService +- Renamed ChainsawTarget to Log4JXmlTarget to match Log4JXmlEventLayout +- Removed dependency on System.Text.RegularExpressions and introduced new nuget-package NLog.RegEx. +- Removed dependency on System.Xml.XmlReader by implementing own internal basic XML-Parser. +- Added support for params ReadOnlySpan when using C# 13 +- Skip LogEventInfo.Parameters-array-allocation when unable to defer message-template formatting. +- Updated NLog API with Nullable-support and introduced Layout.Empty +- Marked [RequiredParameter] as obsolete with Nullable-support, and instead validate options at initialization. + +NLog v6.0 release notes: https://nlog-project.org/2025/04/29/nlog-6-0-major-changes.html + +List of all [NLog 6.0 Pull Requests](https://github.com/NLog/NLog/pulls?q=is%3Apr+is%3Amerged+milestone:%226.0%22) +- [Breaking Changes](https://github.com/NLog/NLog/pulls?q=is%3Apr+label%3A%22breaking%20change%22+is%3Amerged+milestone:%226.0%22) +- [Breaking Behavior Changes](https://github.com/NLog/NLog/pulls?q=is%3Apr+label%3A%22breaking%20behavior%20change%22+is%3Amerged+milestone:%226.0%22) +- [Features](https://github.com/NLog/NLog/pulls?q=is%3Apr+label%3A%22Feature%22+is%3Amerged+milestone:%226.0%22) +- [Improvements](https://github.com/NLog/NLog/pulls?q=is%3Apr+label%3A%22Enhancement%22+is%3Amerged+milestone:%226.0%22) +- [Performance](https://github.com/NLog/NLog/pulls?q=is%3Apr+label%3A%22Performance%22+is%3Amerged+milestone:%226.0%22) + +### Version 6.0 RC4 (2025/06/15) + +**Improvements** +- Mark struct as readonly to allow compiler optimization +- RegisterObjectTransformation to preserve public properties +- Log4JXmlEventLayout - Enforce MaxRecursionLimit = 0 +- DatabaseTarget with support for AOT (@JohnVerheij) +- DatabaseTarget only assign ConnectionString when specified (@JohnVerheij) + +### Version 6.0 RC3 (2025/06/08) + +**Improvements** +- Log4JXmlEventLayout - Fixed IncludeEmptyValue for Parameters + +### Version 6.0 RC2 (2025/06/01) + +**Improvements** +- Fixed NLog XmlParser to support XML comments within XML processing instructions. +- NLog.Targets.Network now also supports NET35. +- Updated structs to be readonly to allow compiler optimizations. +- Updated interface ILoggingConfigurationElement to support nullable Values. +- Updated all projects to include `` +- Optimized ConsoleTarget to not use Console.WriteLine, and introduced option `ForceWriteLine` +- Added new LogEventInfo constructor that supports `ReadOnlySpan` +- Updated NLog.Schema nuget-package to include targets-file to copy NLog.xsd to project-folder. +- Improved configuration-file loading to advise about NLog nuget-packages for missing types. + +### Version 5.5 (2025/05/29) + +**Improvements** +- [#5710](https://github.com/NLog/NLog/pull/5710) Restored LogFactory.Setup().SetupFromEnvironmentVariables() as not obsolete (#5710) @snakefoot +- [#5717](https://github.com/NLog/NLog/pull/5717) Avoid using MakeGenericType for Dictionary enumeration when AOT (#5717) @snakefoot +- [#5730](https://github.com/NLog/NLog/pull/5730) Stop using obsolete Assembly.CodeBase for NetStandard (#5730) @snakefoot +- [#5742](https://github.com/NLog/NLog/pull/5742) ExceptionLayoutRenderer - Handle Exception properties like StackTrace can throw with AOT (#5742) @snakefoot +- [#5743](https://github.com/NLog/NLog/pull/5743) ExceptionLayoutRenderer - Handle Data-collection-item ToString can throw with AOT (#5743) @snakefoot +- [#5763](https://github.com/NLog/NLog/pull/5763) ExceptionLayoutRenderer - Handle Exception-properties can throw with AOT (#5763) @snakefoot +- [#5756](https://github.com/NLog/NLog/pull/5756) ServiceRepository - Improve exception-handling when resolving service-types while disposing (#5756) @snakefoot +- [#5759](https://github.com/NLog/NLog/pull/5759) LayoutRenderer - Optimize performance by skipping cache result from render Inner Layout (#5759) @snakefoot +- [#5795](https://github.com/NLog/NLog/pull/5795) ConditionLayoutExpression - Optimize performance by skipping cache result from render Inner Layout (#5795) @snakefoot +- [#5731](https://github.com/NLog/NLog/pull/5731) Mark IFactory RegisterType as obsolete, since it will be removed with NLog v6 (#5731) @snakefoot +- [#5766](https://github.com/NLog/NLog/pull/5766) Mark JsonLayout EscapeForwardSlash as obsolete, since disabled with NLog v6 (#5766) @snakefoot +- [#5823](https://github.com/NLog/NLog/pull/5823) Mark ExceptionLayoutRenderer Formats-List as obsolete, since immutable with NLog v6 (#5823) @snakefoot +- [#5769](https://github.com/NLog/NLog/pull/5769) Updated API-code examples to not depend on obsolete SimpleConfigurator (#5769) @snakefoot +- [#5776](https://github.com/NLog/NLog/pull/5776) ObjectReflectionCache - Handle PropertyValue can throw with AOT (#5776) @snakefoot +- [#5780](https://github.com/NLog/NLog/pull/5780) NetworkTarget - Introduced option NoDelay to disable delayed ACK (#5780) @snakefoot +- [#5788](https://github.com/NLog/NLog/pull/5788) Fix InternalLogger noise about reflection for FuncLayoutRenderer (#5788) @snakefoot +- [#5792](https://github.com/NLog/NLog/pull/5792) TargetWithContext - Reduce allocation for RenderLogEvent when SimpleLayout (#5792) @snakefoot +- [#5810](https://github.com/NLog/NLog/pull/5810) Refactoring to improve null value handling (#5810) @snakefoot +- [#5812](https://github.com/NLog/NLog/pull/5812) Refactoring to improve null value handling (#5812) @snakefoot +- [#5817](https://github.com/NLog/NLog/pull/5817) LoggingConfigurationParser - Prioritize LoggingRules from current config (#5817) @snakefoot +- [#5825](https://github.com/NLog/NLog/pull/5825) WhenEmptyLayoutRendererWrapper - Optimize IStringValueRenderer Logic (#5825) @snakefoot + +### Version 6.0 RC1 (2025/04/25) + +**Improvements** +- Updated NLog API with `enable` and introduced `Layout.Empty` +- Marked `[RequiredParameter]` as obsolete, and replaced with explicit option validation during initialization. +- Marked `LogEventInfo.SequenceID` and `${sequenceid}` as obsolete, and instead use `${counter:sequence=global}`. +- Added support for params ReadOnlySpan when using C# 13 +- Prioritize generic Logger-methods by marking legacy methods with `[OverloadResolutionPriority(-1)]` when using C# 13 +- Skip LogEventInfo.Parameters-array-allocation when unable to defer message-template formatting. +- Renamed ChainsawTarget to Log4JXmlTarget to match Log4JXmlEventLayout +- Updated NLog.Schema to include intellisense for multiple NLog-assemblies. + +### Version 6.0 Preview 1 (2025/04/27) + +NLog v6.0 release notes: https://nlog-project.org/2025/04/29/nlog-6-0-major-changes.html + +List of all [NLog 6.0 Pull Requests](https://github.com/NLog/NLog/pulls?q=is%3Apr+is%3Amerged+milestone:%226.0%22) +- [Breaking Changes](https://github.com/NLog/NLog/pulls?q=is%3Apr+label%3A%22breaking%20change%22+is%3Amerged+milestone:%226.0%22) +- [Breaking Behavior Changes](https://github.com/NLog/NLog/pulls?q=is%3Apr+label%3A%22breaking%20behavior%20change%22+is%3Amerged+milestone:%226.0%22) +- [Features](https://github.com/NLog/NLog/pulls?q=is%3Apr+label%3A%22Feature%22+is%3Amerged+milestone:%226.0%22) +- [Improvements](https://github.com/NLog/NLog/pulls?q=is%3Apr+label%3A%22Enhancement%22+is%3Amerged+milestone:%226.0%22) +- [Performance](https://github.com/NLog/NLog/pulls?q=is%3Apr+label%3A%22Performance%22+is%3Amerged+milestone:%226.0%22) + ### Version 5.3.4 (2024/09/12) **Improvements** diff --git a/.github/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 56% rename from .github/CONTRIBUTING.md rename to CONTRIBUTING.md index d259a6f481..a2a223b06e 100644 --- a/.github/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ Support & contributing guidelines === -Do you have feature requests, questions or would you like to report a bug? Please follow these guidelines when posting on the [issue list](https://github.com/NLog/NLog/issues). The issues are labeled with the [following guideline](/issue-labeling.md). +Do you have feature requests, questions or would you like to report a bug? Please follow these guidelines when posting on the [issue list](https://github.com/NLog/NLog/issues). Feature requests ---- @@ -42,20 +42,41 @@ Please document any public method and property. Document **why** and not how. At Multiple .NET versions -=== +---- Keep in mind that multiple versions of .NET are supported. Some methods are not available in all .NET versions. The following conditional compilation symbols can be used: ``` #if NET35 -#if NET45 -#if NET46 -#if NETSTANDARD -#if NETSTANDARD1_3 -#if NETSTANDARD1_5 +#if NETFRAMEWORK +#if NETSTANDARD2_1_OR_GREATER ``` +.NET Framework is now the odd one, so focus should be on using `NETFRAMEWORK` to mark code that doesn't support NetStandard or NetCore. + +Contributing +---- +As the current NLog team is a small team, we cannot fix every bug or implement every feature on our own. So contributions are really appreciated! + +If you like to start with a small task, then +[up-for-grabs](https://github.com/NLog/NLog/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3Aup-for-grabs) are nice to start with. + +Please note, we have a `dev` and `master` branch + +- `master` is for pure bug fixes and targets NLog 5.x +- `dev` targets NLog 6 + + +A good way to get started (flow) + +1. Fork the NLog repos. +2. Create a new branch in you current repos from the 'dev' branch. (critical bugfixes from 'master') +3. 'Check out' the code with Git or [GitHub Desktop](https://desktop.github.com/) +4. Push commits and create a Pull Request (PR) to NLog + +Please note: bugfixes should target the **master** branch, others the **dev** branch (NLog 6) + Update your fork -=== +---- Is your fork not up-to-date with the NLog code? Most of the time that isn't a problem. But if you like to "sync back" the changes to your repository, execute the following command: The first time: @@ -63,9 +84,7 @@ The first time: git remote add upstream https://github.com/NLog/NLog.git ``` - After that you repository will have two remotes. You could update your remote (the fork) in the following way: - ``` git fetch upstream git checkout @@ -77,3 +96,30 @@ git push -f if `rebase` won't work well, use `git merge master` as alternative. It's also possible to send a PR in the opposite direction, but that's not preferred as it will pollute the commit log. + + +How to build +---- +Use Visual Studio 2022 and open the solution 'NLog.sln'. + +For building in the cloud we use: +- AppVeyor for Windows- and Linux-builds +- SonarQube for code coverage + +Trying to build your fork in the cloud? Check [this how-to](howto-build-your-fork.md) + +Note: master points to NLog 5.x and dev to NLog 6 + +List of NLog extensions +=== +NLog Project Homepage provides a list of available [NLog Targets and Layouts](https://nlog-project.org/config/) + +To add a new NLog extension, then just create pull-request here: + +- https://github.com/NLog/NLog.github.io/pulls + +Updating the relevant file: + +- **NLog Targets** - https://github.com/NLog/NLog.github.io/blob/master/config/targets.json +- **NLog Layouts** - https://github.com/NLog/NLog.github.io/blob/master/config/layouts.json +- **NLog LayoutRenderers** - https://github.com/NLog/NLog.github.io/blob/master/config/layout-renderers.json diff --git a/README.md b/README.md index 3ad5221d7a..97dac440aa 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=nlog2&metric=coverage&branch=dev)](https://sonarcloud.io/dashboard?id=nlog2&branch=dev) - [![](https://img.shields.io/badge/Docs-GitHub%20wiki-brightgreen)](https://github.com/NLog/NLog/wiki) [![](https://img.shields.io/badge/Troubleshoot-Guide-orange)](https://github.com/nlog/nlog/wiki/Logging-troubleshooting) @@ -25,9 +24,9 @@ NLog is a free logging platform for .NET with rich log routing and management capabilities. It makes it easy to produce and manage high-quality logs for your application regardless of its size or complexity. -It can process diagnostic messages emitted from any .NET language, augment -them with contextual information, format them according to your preference -and send them to one or more targets such as file or database. +Handles both structured logging and traditional logging from any .NET language, +augment with contextual information, format according to your preference +and send them to one or more targets such as file or console. Major and minor releases will be posted on [project news](https://nlog-project.org/archives/). @@ -42,30 +41,30 @@ For the possible options in the config, check the [Options list](https://nlog-pr Having troubles? Check the [troubleshooting guide](https://github.com/NLog/NLog/wiki/Logging-troubleshooting) +----- ------ + ℹ️ NLog 6.0 supports AOT - ℹ️ NLog 6.0 will support AOT +[NLog 6.0](https://www.nuget.org/packages/NLog/6.0.0#releasenotes-body-tab) now available. See also [List of major changes in NLog 6.0](https://nlog-project.org/2025/04/29/nlog-6-0-major-changes.html) -NLog 6.0 is now being prepared. See [List of goals for NLog 6.0](https://nlog-project.org/2024/10/01/nlog-6-0-goals.html) -NLog Packages +NLog Extensions --- -The NLog-nuget-package provides everything needed for doing file- and console-logging. But there are also multiple NLog extension packages, -that provides additional target- and layout-output. See [targets](https://nlog-project.org/config/?tab=targets) and [layout renderers](https://nlog-project.org/config/?tab=layout-renderers) overview! +The NLog-nuget-package provides everything needed for doing file- and console-logging. If you need other output options, then there are many NLog extension packages available (such as databases, email, cloud services, etc.). +See [targets](https://nlog-project.org/config/?tab=targets) and [layout renderers](https://nlog-project.org/config/?tab=layout-renderers) overview! -See Nuget/build status of all official packages [here](https://github.com/NLog/NLog/blob/dev/packages-and-status.md) +The NLog extension packages maintained by the NLog-project are [listed here](https://github.com/NLog/NLog/blob/dev/packages-and-status.md) with Nuget/build status. +It is also possible to [Create your own custom NLog extensions](https://github.com/NLog/NLog/wiki/Extending-NLog). Questions, bug reports or feature requests? --- -Issues with getting it working? -Please check the [troubleshooting guide](https://github.com/NLog/NLog/wiki/Logging-troubleshooting) before asking! With a clear error message, it's really easier to solve the issue! +If having issues with getting NLog working? Then please check the [troubleshooting guide](https://github.com/NLog/NLog/wiki/Logging-troubleshooting) before asking! This will often provide you with clear error message when asking, so it is easier to solve the issue! -Unclear how to configure NLog correctly of other questions? Please post questions on [StackOverflow](https://stackoverflow.com/). +If having questions about how to configure NLog correctly? Then please post questions on [StackOverflow](https://stackoverflow.com/questions/tagged/nlog) (using the `nlog` tag) -Do you have feature request or would you like to report a bug? Please post them on the [issue list](https://github.com/NLog/NLog/issues) and follow [these guidelines](.github/CONTRIBUTING.md). +Have you found a bug or issue with NLog functionality? Please post them on the [issue list](https://github.com/NLog/NLog/issues) and follow [these guidelines](/CONTRIBUTING.md). Frequently Asked Questions (FAQ) @@ -73,45 +72,7 @@ Frequently Asked Questions (FAQ) See [FAQ on the Wiki](https://github.com/NLog/NLog/wiki/faq) -Contributing ---- -As the current NLog team is a small team, we cannot fix every bug or implement every feature on our own. So contributions are really appreciated! - -If you like to start with a small task, then -[up-for-grabs](https://github.com/NLog/NLog/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3Aup-for-grabs+-label%3A%22almost+ready%22+) are nice to start with. - -Please note, we have a `dev` and `master` branch - -- `master` is for pure bug fixes and targets NLog 5.x -- `dev` targets NLog 6 - - -A good way to get started (flow) - -1. Fork the NLog repos. -1. Create a new branch in you current repos from the 'dev' branch. (critical bugfixes from 'master') -1. 'Check out' the code with Git or [GitHub Desktop](https://desktop.github.com/) -1. Check [contributing.md](.github/CONTRIBUTING.md#sync-projects) -1. Push commits and create a Pull Request (PR) to NLog - -Please note: bugfixes should target the **master** branch, others the **dev** branch (NLog 6) - - License --- NLog is open source software, licensed under the terms of BSD license. See [LICENSE.txt](LICENSE.txt) for details. - - -How to build ---- -Use Visual Studio 2019 and open the solution 'NLog.sln'. - -For building in the cloud we use: -- AppVeyor for Windows- and Linux-builds -- SonarQube for code coverage - -Trying to build your fork in the cloud? Check [this how-to](howto-build-your-fork.md) - -Note: master points to NLog 5.x and dev to NLog 6 - diff --git a/SECURITY.md b/SECURITY.md index 0936586416..6bad768ba3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,8 +6,8 @@ Currently being supported with security updates. | Version | Supported | | ------- | ------------------ | -| 5.x | :white_check_mark: | -| 4.7.x | :white_check_mark: | +| 6.x | :white_check_mark: | +| 5.x | :white_check_mark: | | < 4.7 | :x: | ## Reporting a Vulnerability diff --git a/Test-XmlFile.ps1 b/Test-XmlFile.ps1 index ef2fb339bf..c194310fd7 100644 --- a/Test-XmlFile.ps1 +++ b/Test-XmlFile.ps1 @@ -54,7 +54,7 @@ function Test-XmlFile $pwd = get-location; $testFilesDir = "$pwd\examples\targets\Configuration File" $xsdFilePath = "$pwd\src\NLog\bin\Release\NLog.xsd" -$excludedTests = ("MessageBox","RichTextBox","FormControl","PerfCounter","OutputDebugString","MSMQ","Database") +$excludedTests = ("Database","Chainsaw","NLogViewer") # Returns true if all selected tests in examples directory are valid $ret = $true Get-ChildItem -Path $testFilesDir -Directory -Exclude $excludedTests | Get-ChildItem -Recurse -File -Filter NLog.config | % { diff --git a/appveyor.yml b/appveyor.yml index 38091fcec0..89f27c3805 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ -version: 5.0.0-{build} # Only change for mayor versions (e.g. 6.0) +version: 6.0.0-{build} # Only change for mayor versions (e.g. 7.0) image: - Visual Studio 2022 - - Ubuntu2004 + - Ubuntu2204 configuration: Release build: false test: false @@ -37,12 +37,12 @@ for: deploy: - provider: NuGet api_key: - secure: ACKSV1ixxNpO+2k8KvNDy6hd9QmR8lkQmKn773ZIIeVpG0ywYUhY4j8LcyykVR1a + secure: f6oWebyOFLpuuo2PMd6xgoxwMq+JvXVUmPyBme89zS7UF0zcvLYPSKN/p6B/KaMs on: branch: master - provider: NuGet api_key: - secure: ACKSV1ixxNpO+2k8KvNDy6hd9QmR8lkQmKn773ZIIeVpG0ywYUhY4j8LcyykVR1a + secure: f6oWebyOFLpuuo2PMd6xgoxwMq+JvXVUmPyBme89zS7UF0zcvLYPSKN/p6B/KaMs on: branch: dev artifacts: @@ -54,9 +54,10 @@ for: - matrix: only: - - image: Ubuntu2004 + - image: Ubuntu2204 environment: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + MSBUILDTERMINALLOGGER: off FrameworkPathOverride: /usr/lib/mono/4.6.1-api/ build_script: - ps: dotnet --version diff --git a/build.ps1 b/build.ps1 index d2ce282bdd..972fd7cd23 100644 --- a/build.ps1 +++ b/build.ps1 @@ -2,7 +2,7 @@ # creates NuGet package at \artifacts dotnet --version -$versionPrefix = "5.3.4" +$versionPrefix = "6.0.2" $versionSuffix = "" $versionFile = $versionPrefix + "." + ${env:APPVEYOR_BUILD_NUMBER} $versionProduct = $versionPrefix; @@ -15,7 +15,7 @@ if ($env:APPVEYOR_PULL_REQUEST_NUMBER) $versionSuffix = "PR" + $env:APPVEYOR_PULL_REQUEST_NUMBER } -$targetNugetExe = "tools\nuget.exe" +$targetNugetExe = "tools/nuget.exe" if (-Not (test-path $targetNugetExe)) { # download nuget.exe @@ -23,23 +23,31 @@ if (-Not (test-path $targetNugetExe)) Invoke-WebRequest $sourceNugetExe -OutFile $targetNugetExe } -msbuild /t:Restore,Pack .\src\NLog\ /p:targetFrameworks='"net46;net45;net35;netstandard1.3;netstandard1.5;netstandard2.0"' /p:VersionPrefix=$versionPrefix /p:VersionSuffix=$versionSuffix /p:FileVersion=$versionFile /p:ProductVersion=$versionProduct /p:Configuration=Release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true /p:PackageOutputPath=..\..\artifacts /verbosity:minimal /maxcpucount +msbuild /t:Restore,Pack ./src/NLog/ /p:targetFrameworks='"net46;net45;net35;netstandard2.0;netstandard2.1"' /p:VersionPrefix=$versionPrefix /p:VersionSuffix=$versionSuffix /p:FileVersion=$versionFile /p:ProductVersion=$versionProduct /p:Configuration=Release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true /p:PackageOutputPath=..\..\artifacts /verbosity:minimal /maxcpucount if (-Not $LastExitCode -eq 0) { exit $LastExitCode } function create-package($packageName, $targetFrameworks) { - $path = ".\src\$packageName\" + $path = "./src/$packageName/" msbuild /t:Restore,Pack $path /p:targetFrameworks=$targetFrameworks /p:VersionPrefix=$versionPrefix /p:VersionSuffix=$versionSuffix /p:FileVersion=$versionFile /p:ProductVersion=$versionProduct /p:Configuration=Release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true /p:PackageOutputPath=..\..\artifacts /verbosity:minimal /maxcpucount if (-Not $LastExitCode -eq 0) { exit $LastExitCode } } -create-package 'NLog.Database' '"net35;net45;net46;netstandard1.3;netstandard1.5;netstandard2.0"' -create-package 'NLog.OutputDebugString' '"net35;net45;net46;netstandard2.0"' -create-package 'NLog.WindowsRegistry' '"net35;net45;net46;netstandard2.0"' -create-package 'NLog.WindowsEventLog' '"netstandard2.0"' - -msbuild /t:xsd /t:NuGetSchemaPackage .\src\NLog.proj /p:Configuration=Release /p:BuildNetFX45=true /p:BuildVersion=$versionProduct /p:Configuration=Release /p:BuildLabelOverride=NONE /verbosity:minimal +create-package 'NLog.Database' '"net35;net45;net46;netstandard2.0;netstandard2.1"' +create-package 'NLog.Targets.Mail' '"net35;net45;net46;netstandard2.0;netstandard2.1"' +create-package 'NLog.Targets.Network' '"net45;net46;netstandard2.0;netstandard2.1"' +create-package 'NLog.Targets.Trace' '"net35;net45;net46;netstandard2.0;netstandard2.1"' +create-package 'NLog.Targets.WebService' '"net45;net46;netstandard2.0;netstandard2.1"' +create-package 'NLog.Targets.GZipFile' '"net45;net46;netstandard2.0;netstandard2.1"' +create-package 'NLog.OutputDebugString' '"net35;net45;net46;netstandard2.0;netstandard2.1"' +create-package 'NLog.RegEx' '"net35;net45;net46;netstandard2.0;netstandard2.1"' +create-package 'NLog.WindowsRegistry' '"net35;net45;net46;netstandard2.0;netstandard2.1"' +create-package 'NLog.Targets.ConcurrentFile' '"net35;net45;net46;netstandard2.0"' +msbuild /t:Restore,Pack ./src/NLog.Targets.AtomicFile/ /p:VersionPrefix=$versionPrefix /p:VersionSuffix=$versionSuffix /p:FileVersion=$versionFile /p:ProductVersion=$versionProduct /p:Configuration=Release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:ContinuousIntegrationBuild=true /p:EmbedUntrackedSources=true /p:PackageOutputPath=..\..\artifacts /verbosity:minimal /maxcpucount +create-package 'NLog.WindowsEventLog' '"netstandard2.0;netstandard2.1"' + +msbuild /t:xsd /t:NuGetSchemaPackage ./src/NLog.proj /p:Configuration=Release /p:BuildNetFX45=true /p:BuildVersion=$versionProduct /p:Configuration=Release /p:BuildLabelOverride=NONE /verbosity:minimal exit $LastExitCode diff --git a/examples/ExtendingLoggers/README.html b/examples/ExtendingLoggers/README.html index 2cb9e10d5a..8eaf306f23 100644 --- a/examples/ExtendingLoggers/README.html +++ b/examples/ExtendingLoggers/README.html @@ -1,10 +1,10 @@ - + + NLog Examples diff --git a/examples/targets/Configuration API/ASPNetBufferingWrapper/Global.asax.cs b/examples/targets/Configuration API/ASPNetBufferingWrapper/Global.asax.cs index bf4461fe6a..201889f18a 100644 --- a/examples/targets/Configuration API/ASPNetBufferingWrapper/Global.asax.cs +++ b/examples/targets/Configuration API/ASPNetBufferingWrapper/Global.asax.cs @@ -30,7 +30,9 @@ protected void Application_Start(object sender, EventArgs e) rule.Filter = "level >= LogLevel.Debug"; postfilteringTarget.Rules.Add(rule); - SimpleConfigurator.ConfigureForTargetLogging(aspnetBufferingTarget, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(aspnetBufferingTarget); + LogManager.Configuration = nlogConfig; } protected void Application_End(object sender, EventArgs e) diff --git a/examples/targets/Configuration API/ASPNetBufferingWrapper/NormalPage.aspx b/examples/targets/Configuration API/ASPNetBufferingWrapper/NormalPage.aspx index d012e129a2..7a4032b2c9 100644 --- a/examples/targets/Configuration API/ASPNetBufferingWrapper/NormalPage.aspx +++ b/examples/targets/Configuration API/ASPNetBufferingWrapper/NormalPage.aspx @@ -2,7 +2,7 @@ - + Untitled Page diff --git a/examples/targets/Configuration API/ASPNetBufferingWrapper/PageWithWarnings.aspx b/examples/targets/Configuration API/ASPNetBufferingWrapper/PageWithWarnings.aspx index f38c27442f..a3f02210bd 100644 --- a/examples/targets/Configuration API/ASPNetBufferingWrapper/PageWithWarnings.aspx +++ b/examples/targets/Configuration API/ASPNetBufferingWrapper/PageWithWarnings.aspx @@ -2,7 +2,7 @@ - + Untitled Page diff --git a/examples/targets/Configuration API/ASPNetTrace/Global.asax.cs b/examples/targets/Configuration API/ASPNetTrace/Global.asax.cs index 9432ae14b1..1e54d20ecc 100644 --- a/examples/targets/Configuration API/ASPNetTrace/Global.asax.cs +++ b/examples/targets/Configuration API/ASPNetTrace/Global.asax.cs @@ -2,6 +2,7 @@ using System.Web; using NLog; +using NLog.Config; using NLog.Targets; namespace SomeWebApplication @@ -19,7 +20,9 @@ protected void Application_Start(Object sender, EventArgs e) ASPNetTraceTarget target = new ASPNetTraceTarget(); target.Layout = "${logger} ${message}"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; } } } diff --git a/examples/targets/Configuration API/ASPNetTrace/test.aspx b/examples/targets/Configuration API/ASPNetTrace/test.aspx index a58ee3c3ea..1238ea441b 100644 --- a/examples/targets/Configuration API/ASPNetTrace/test.aspx +++ b/examples/targets/Configuration API/ASPNetTrace/test.aspx @@ -1,4 +1,7 @@ <%@ Page language="c#" AutoEventWireup="true" %> + + + - + + ASP.NET Trace Test

diff --git a/examples/targets/Configuration API/AsyncWrapper/Wrapping File/Example.cs b/examples/targets/Configuration API/AsyncWrapper/Wrapping File/Example.cs index 678c2a2cad..f3f18ae5c6 100644 --- a/examples/targets/Configuration API/AsyncWrapper/Wrapping File/Example.cs +++ b/examples/targets/Configuration API/AsyncWrapper/Wrapping File/Example.cs @@ -1,7 +1,7 @@ using NLog; +using NLog.Config; using NLog.Targets; using NLog.Targets.Wrappers; -using System.Text; class Example { @@ -11,18 +11,20 @@ static void Main(string[] args) target.Layout = "${longdate} ${logger} ${message}"; target.FileName = "${basedir}/logs/logfile.txt"; target.KeepFileOpen = false; - target.Encoding = Encoding.UTF8; + target.Encoding = System.Text.Encoding.UTF8; AsyncTargetWrapper wrapper = new AsyncTargetWrapper(); wrapper.WrappedTarget = target; wrapper.QueueLimit = 5000; wrapper.OverflowAction = AsyncTargetWrapperOverflowAction.Discard; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(wrapper, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(wrapper); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); - wrapper.Flush(); + LogManager.Flush(); } } diff --git a/examples/targets/Configuration API/AutoFlushWrapper/Simple/Example.cs b/examples/targets/Configuration API/AutoFlushWrapper/Simple/Example.cs index 506736cc53..40c26d7c5b 100644 --- a/examples/targets/Configuration API/AutoFlushWrapper/Simple/Example.cs +++ b/examples/targets/Configuration API/AutoFlushWrapper/Simple/Example.cs @@ -1,9 +1,7 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; using NLog.Targets.Wrappers; -using System.Diagnostics; class Example { @@ -15,7 +13,10 @@ static void Main(string[] args) AutoFlushTargetWrapper target = new AutoFlushTargetWrapper(); target.WrappedTarget = wrappedTarget; target.Condition = "level >= LogLevel.Debug"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/BufferingWrapper/Simple/Example.cs b/examples/targets/Configuration API/BufferingWrapper/Simple/Example.cs index 795c51bc00..2b45989767 100644 --- a/examples/targets/Configuration API/BufferingWrapper/Simple/Example.cs +++ b/examples/targets/Configuration API/BufferingWrapper/Simple/Example.cs @@ -1,9 +1,7 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; using NLog.Targets.Wrappers; -using System.Diagnostics; class Example { @@ -16,9 +14,13 @@ static void Main(string[] args) target.BufferSize = 100; target.WrappedTarget = wrappedTarget; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); + + LogManager.Flush(); } } diff --git a/examples/targets/Configuration API/Chainsaw/Simple/Example.cs b/examples/targets/Configuration API/Chainsaw/Simple/Example.cs index 4bc224534b..4a81ce6f02 100644 --- a/examples/targets/Configuration API/Chainsaw/Simple/Example.cs +++ b/examples/targets/Configuration API/Chainsaw/Simple/Example.cs @@ -1,4 +1,5 @@ using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -8,7 +9,9 @@ static void Main(string[] args) ChainsawTarget target = new ChainsawTarget(); target.Address = "udp://localhost:4000"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Trace("log message 1"); @@ -17,5 +20,7 @@ static void Main(string[] args) logger.Warn("log message 4"); logger.Error("log message 5"); logger.Fatal("log message 6"); + + LogManager.Flush(); } } diff --git a/examples/targets/Configuration API/ColoredConsole/Row Highlighting/Example.cs b/examples/targets/Configuration API/ColoredConsole/Row Highlighting/Example.cs index 944a23a9bf..e43cdc830d 100644 --- a/examples/targets/Configuration API/ColoredConsole/Row Highlighting/Example.cs +++ b/examples/targets/Configuration API/ColoredConsole/Row Highlighting/Example.cs @@ -1,5 +1,6 @@ using NLog; -using NLog.Win32.Targets; +using NLog.Config; +using NLog.Targets; class Example { @@ -21,7 +22,10 @@ static void Main(string[] args) ConsoleOutputColor.Yellow, // foreground color ConsoleOutputColor.DarkBlue) // background color ); - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace); + + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; // LogManager.Configuration = new NLog.Config.XmlLoggingConfiguration("ColoredConsoleTargetRowHighlighting.nlog"); diff --git a/examples/targets/Configuration API/ColoredConsole/Simple/Example.cs b/examples/targets/Configuration API/ColoredConsole/Simple/Example.cs index 700cd1233f..a32de11345 100644 --- a/examples/targets/Configuration API/ColoredConsole/Simple/Example.cs +++ b/examples/targets/Configuration API/ColoredConsole/Simple/Example.cs @@ -1,5 +1,6 @@ using NLog; -using NLog.Win32.Targets; +using NLog.Config; +using NLog.Targets; class Example { @@ -8,7 +9,9 @@ static void Main(string[] args) ColoredConsoleTarget target = new ColoredConsoleTarget(); target.Layout = "${date:format=HH\\:MM\\:ss} ${logger} ${message}"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Trace("trace log message"); diff --git a/examples/targets/Configuration API/ColoredConsole/Word Highlighting/Example.cs b/examples/targets/Configuration API/ColoredConsole/Word Highlighting/Example.cs index 104136e9b2..b051fe81fa 100644 --- a/examples/targets/Configuration API/ColoredConsole/Word Highlighting/Example.cs +++ b/examples/targets/Configuration API/ColoredConsole/Word Highlighting/Example.cs @@ -1,5 +1,6 @@ using NLog; -using NLog.Win32.Targets; +using NLog.Config; +using NLog.Targets; class Example { @@ -16,8 +17,9 @@ static void Main(string[] args) ConsoleOutputColor.Cyan, ConsoleOutputColor.NoChange)); - - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Trace("trace log message abcdefghijklmnopq"); diff --git a/examples/targets/Configuration API/Console/Simple/Example.cs b/examples/targets/Configuration API/Console/Simple/Example.cs index 17e17f867f..94432abe4c 100644 --- a/examples/targets/Configuration API/Console/Simple/Example.cs +++ b/examples/targets/Configuration API/Console/Simple/Example.cs @@ -1,4 +1,5 @@ using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -8,7 +9,9 @@ static void Main(string[] args) ConsoleTarget target = new ConsoleTarget(); target.Layout = "${date:format=HH\\:MM\\:ss} ${logger} ${message}"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/Database/MSSQL/Example.cs b/examples/targets/Configuration API/Database/MSSQL/Example.cs index b208c86890..535168d2d3 100644 --- a/examples/targets/Configuration API/Database/MSSQL/Example.cs +++ b/examples/targets/Configuration API/Database/MSSQL/Example.cs @@ -1,4 +1,5 @@ using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -35,7 +36,9 @@ static void Main(string[] args) param.Layout = "${message}"; target.Parameters.Add(param); - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/Database/Oracle.Native/Example.cs b/examples/targets/Configuration API/Database/Oracle.Native/Example.cs index 005e2d456d..b8f68e4582 100644 --- a/examples/targets/Configuration API/Database/Oracle.Native/Example.cs +++ b/examples/targets/Configuration API/Database/Oracle.Native/Example.cs @@ -1,4 +1,5 @@ using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -20,7 +21,9 @@ static void Main(string[] args) target.Parameters.Add(new DatabaseParameterInfo("CALLSITE", "${callsite:filename=true}")); target.Parameters.Add(new DatabaseParameterInfo("MESSAGE", "${message}")); - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/Database/Oracle.OleDb/Example.cs b/examples/targets/Configuration API/Database/Oracle.OleDb/Example.cs index bbb0c4b301..1de597b641 100644 --- a/examples/targets/Configuration API/Database/Oracle.OleDb/Example.cs +++ b/examples/targets/Configuration API/Database/Oracle.OleDb/Example.cs @@ -1,4 +1,5 @@ using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -18,7 +19,9 @@ static void Main(string[] args) target.Parameters.Add(new DatabaseParameterInfo("CALLSITE", "${callsite:filename=true}")); target.Parameters.Add(new DatabaseParameterInfo("MESSAGE", "${message}")); - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/Debug/Simple/Example.cs b/examples/targets/Configuration API/Debug/Simple/Example.cs index cf7408229a..7eb2771069 100644 --- a/examples/targets/Configuration API/Debug/Simple/Example.cs +++ b/examples/targets/Configuration API/Debug/Simple/Example.cs @@ -1,6 +1,5 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -10,7 +9,9 @@ static void Main(string[] args) DebugTarget target = new DebugTarget(); target.Layout = "${message}"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/Debugger/Simple/Example.cs b/examples/targets/Configuration API/Debugger/Simple/Example.cs index 3141094cf7..365b16c1ac 100644 --- a/examples/targets/Configuration API/Debugger/Simple/Example.cs +++ b/examples/targets/Configuration API/Debugger/Simple/Example.cs @@ -1,6 +1,5 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -10,7 +9,9 @@ static void Main(string[] args) DebuggerTarget target = new DebuggerTarget(); target.Layout = "${message}"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/EventLog/Simple/Example.cs b/examples/targets/Configuration API/EventLog/Simple/Example.cs index 9eef6484bd..88c987c400 100644 --- a/examples/targets/Configuration API/EventLog/Simple/Example.cs +++ b/examples/targets/Configuration API/EventLog/Simple/Example.cs @@ -1,6 +1,6 @@ using NLog; +using NLog.Config; using NLog.Targets; -using NLog.Win32.Targets; class Example { @@ -11,7 +11,9 @@ static void Main(string[] args) target.Log = "Application"; target.Layout = "${logger}: ${message} ${exception}"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/FallbackGroup/Simple/Example.cs b/examples/targets/Configuration API/FallbackGroup/Simple/Example.cs index 7b3dd3f513..08c4412202 100644 --- a/examples/targets/Configuration API/FallbackGroup/Simple/Example.cs +++ b/examples/targets/Configuration API/FallbackGroup/Simple/Example.cs @@ -1,9 +1,7 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; -using NLog.Targets.Compound; -using System.Diagnostics; +using NLog.Targets.Wrappers; class Example { @@ -15,7 +13,6 @@ static void Main(string[] args) FileTarget file2 = new FileTarget(); file2.FileName = "\\\\server2\\share\\file1.txt"; - // write to server1, if it fails switch to server2 FallbackTarget target = new FallbackTarget(); @@ -23,7 +20,9 @@ static void Main(string[] args) target.Targets.Add(file1); target.Targets.Add(file2); - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/File/Archive1/Example.cs b/examples/targets/Configuration API/File/Archive1/Example.cs index 61b0664a22..c319fe38cb 100644 --- a/examples/targets/Configuration API/File/Archive1/Example.cs +++ b/examples/targets/Configuration API/File/Archive1/Example.cs @@ -1,6 +1,6 @@ using NLog; +using NLog.Config; using NLog.Targets; -using NLog.Targets.Wrappers; class Example { @@ -13,10 +13,9 @@ static void Main(string[] args) target.ArchiveAboveSize = 10 * 1024; // archive files greater than 10 KB target.ArchiveNumbering = FileTarget.ArchiveNumberingMode.Sequence; - // this speeds up things when no other processes are writing to the file - target.ConcurrentWrites = true; - - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); diff --git a/examples/targets/Configuration API/File/Archive2/Example.cs b/examples/targets/Configuration API/File/Archive2/Example.cs index 7d37f9d13c..b571c5277d 100644 --- a/examples/targets/Configuration API/File/Archive2/Example.cs +++ b/examples/targets/Configuration API/File/Archive2/Example.cs @@ -1,7 +1,6 @@ using NLog; +using NLog.Config; using NLog.Targets; -using NLog.Targets.Wrappers; -using System.Threading; class Example { @@ -15,10 +14,9 @@ static void Main(string[] args) target.ArchiveNumbering = FileTarget.ArchiveNumberingMode.Rolling; target.MaxArchiveFiles = 3; - // this speeds up things when no other processes are writing to the file - target.ConcurrentWrites = true; - - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); @@ -40,7 +38,7 @@ static void Main(string[] args) for (int i = 0; i < 250; ++i) { logger.Debug("log message {i}", i); - Thread.Sleep(1000); + System.Threading.Thread.Sleep(1000); } } } diff --git a/examples/targets/Configuration API/File/Archive3/Example.cs b/examples/targets/Configuration API/File/Archive3/Example.cs index 376bf0ea62..f89c5966cd 100644 --- a/examples/targets/Configuration API/File/Archive3/Example.cs +++ b/examples/targets/Configuration API/File/Archive3/Example.cs @@ -1,7 +1,6 @@ using NLog; +using NLog.Config; using NLog.Targets; -using NLog.Targets.Wrappers; -using System.Threading; class Example { @@ -17,10 +16,9 @@ static void Main(string[] args) target.MaxArchiveFiles = 3; target.ArchiveAboveSize = 10000; - // this speeds up things when no other processes are writing to the file - target.ConcurrentWrites = true; - - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); @@ -44,7 +42,7 @@ static void Main(string[] args) for (int i = 0; i < 2500; ++i) { logger.Debug("log message {i}", i); - Thread.Sleep(100); + System.Threading.Thread.Sleep(100); } } } diff --git a/examples/targets/Configuration API/File/Archive4/Example.cs b/examples/targets/Configuration API/File/Archive4/Example.cs index a3a9fab2e6..cf1c114a33 100644 --- a/examples/targets/Configuration API/File/Archive4/Example.cs +++ b/examples/targets/Configuration API/File/Archive4/Example.cs @@ -1,7 +1,6 @@ using NLog; +using NLog.Config; using NLog.Targets; -using NLog.Targets.Wrappers; -using System.Threading; class Example { @@ -17,10 +16,9 @@ static void Main(string[] args) target.MaxArchiveFiles = 3; target.ArchiveAboveSize = 10000; - // this speeds up things when no other processes are writing to the file - target.ConcurrentWrites = true; - - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); @@ -57,7 +55,7 @@ static void Main(string[] args) logger.Debug("log message {i}", i); logger.Error("log message {i}", i); logger.Fatal("log message {i}", i); - Thread.Sleep(100); + System.Threading.Thread.Sleep(100); } } } diff --git a/examples/targets/Configuration API/File/Asynchronous/Example.cs b/examples/targets/Configuration API/File/Asynchronous/Example.cs index 53a7449241..f3f18ae5c6 100644 --- a/examples/targets/Configuration API/File/Asynchronous/Example.cs +++ b/examples/targets/Configuration API/File/Asynchronous/Example.cs @@ -1,7 +1,7 @@ using NLog; +using NLog.Config; using NLog.Targets; using NLog.Targets.Wrappers; -using System.Text; class Example { @@ -11,16 +11,20 @@ static void Main(string[] args) target.Layout = "${longdate} ${logger} ${message}"; target.FileName = "${basedir}/logs/logfile.txt"; target.KeepFileOpen = false; - target.Encoding = Encoding.UTF8; + target.Encoding = System.Text.Encoding.UTF8; AsyncTargetWrapper wrapper = new AsyncTargetWrapper(); wrapper.WrappedTarget = target; wrapper.QueueLimit = 5000; wrapper.OverflowAction = AsyncTargetWrapperOverflowAction.Discard; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(wrapper, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(wrapper); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); + + LogManager.Flush(); } } diff --git a/examples/targets/Configuration API/File/CSV/Example.cs b/examples/targets/Configuration API/File/CSV/Example.cs index 23cd05644d..3661346237 100644 --- a/examples/targets/Configuration API/File/CSV/Example.cs +++ b/examples/targets/Configuration API/File/CSV/Example.cs @@ -1,6 +1,5 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; using NLog.Layouts; @@ -12,7 +11,6 @@ static void Main(string[] args) target.FileName = "${basedir}/file.csv"; CsvLayout layout = new CsvLayout(); - layout.Columns.Add(new CsvColumn("time", "${longdate}")); layout.Columns.Add(new CsvColumn("message", "${message}")); layout.Columns.Add(new CsvColumn("logger", "${logger}")); @@ -20,7 +18,9 @@ static void Main(string[] args) target.Layout = layout; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/File/Multiple/Example.cs b/examples/targets/Configuration API/File/Multiple/Example.cs index b6420514a3..55742bbbb8 100644 --- a/examples/targets/Configuration API/File/Multiple/Example.cs +++ b/examples/targets/Configuration API/File/Multiple/Example.cs @@ -1,6 +1,6 @@ using NLog; +using NLog.Config; using NLog.Targets; -using System.Text; class Example { @@ -10,9 +10,11 @@ static void Main(string[] args) target.Layout = "${longdate} ${logger} ${message}"; target.FileName = "${basedir}/${level}.log"; target.KeepFileOpen = false; - target.Encoding = Encoding.UTF8; + target.Encoding = System.Text.Encoding.UTF8; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/File/Multiple2/Example.cs b/examples/targets/Configuration API/File/Multiple2/Example.cs index a00ecebfbd..6b58c4d250 100644 --- a/examples/targets/Configuration API/File/Multiple2/Example.cs +++ b/examples/targets/Configuration API/File/Multiple2/Example.cs @@ -1,6 +1,6 @@ using NLog; +using NLog.Config; using NLog.Targets; -using System.Text; class Example { @@ -8,11 +8,13 @@ static void Main(string[] args) { FileTarget target = new FileTarget(); target.Layout = "${longdate} ${logger} ${message}"; - target.FileName = "${basedir}/${shortdate}/${windows-identity:domain=false}.${level}.log"; + target.FileName = "${basedir}/${shortdate}/${environment-user}.${level}.log"; target.KeepFileOpen = false; - target.Encoding = Encoding.UTF8; + target.Encoding = System.Text.Encoding.UTF8; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/File/Simple/Example.cs b/examples/targets/Configuration API/File/Simple/Example.cs index cc2da018f4..ea25304c78 100644 --- a/examples/targets/Configuration API/File/Simple/Example.cs +++ b/examples/targets/Configuration API/File/Simple/Example.cs @@ -1,6 +1,6 @@ using NLog; +using NLog.Config; using NLog.Targets; -using System.Text; class Example { @@ -10,9 +10,11 @@ static void Main(string[] args) target.Layout = "${longdate} ${logger} ${message}"; target.FileName = "${basedir}/logs/logfile.txt"; target.KeepFileOpen = false; - target.Encoding = Encoding.UTF8; + target.Encoding = System.Text.Encoding.UTF8; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/FilteringWrapper/Simple/Example.cs b/examples/targets/Configuration API/FilteringWrapper/Simple/Example.cs index 5f163416d6..2c17ad65e7 100644 --- a/examples/targets/Configuration API/FilteringWrapper/Simple/Example.cs +++ b/examples/targets/Configuration API/FilteringWrapper/Simple/Example.cs @@ -1,9 +1,7 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; using NLog.Targets.Wrappers; -using System.Diagnostics; class Example { @@ -17,7 +15,9 @@ static void Main(string[] args) filteringTarget.Condition = "contains('${message}','1')"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(filteringTarget, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(filteringTarget); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message 0"); diff --git a/examples/targets/Configuration API/Log4JXml/Simple/Example.cs b/examples/targets/Configuration API/Log4JXml/Simple/Example.cs new file mode 100644 index 0000000000..f186f828a4 --- /dev/null +++ b/examples/targets/Configuration API/Log4JXml/Simple/Example.cs @@ -0,0 +1,26 @@ +using NLog; +using NLog.Config; +using NLog.Targets; + +class Example +{ + static void Main(string[] args) + { + Log4JXmlTarget target = new Log4JXmlTarget(); + target.Address = "udp://localhost:4000"; + + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; + + Logger logger = LogManager.GetLogger("Example"); + logger.Trace("log message 1"); + logger.Debug("log message 2"); + logger.Info("log message 3"); + logger.Warn("log message 4"); + logger.Error("log message 5"); + logger.Fatal("log message 6"); + + LogManager.Flush(); + } +} diff --git a/examples/targets/Configuration API/Log4JXml/Simple/Log4JXml.csproj b/examples/targets/Configuration API/Log4JXml/Simple/Log4JXml.csproj new file mode 100644 index 0000000000..d0020fa664 --- /dev/null +++ b/examples/targets/Configuration API/Log4JXml/Simple/Log4JXml.csproj @@ -0,0 +1,48 @@ + + + Debug + AnyCPU + {D7B11859-2A1B-49F7-A7C7-83B72495BF31} + Exe + false + Log4JXml + + + + + + + 2.0 + + + true + full + false + .\bin\Debug\ + DEBUG;TRACE + + + pdbonly + true + .\bin\Release\ + TRACE + + + + + + + + + + + + {020354EE-5073-4BB5-9AA2-A7EADA8CAD09} + NLog + + + + + + + \ No newline at end of file diff --git a/examples/targets/Configuration API/Log4JXml/Simple/Log4JXml.sln b/examples/targets/Configuration API/Log4JXml/Simple/Log4JXml.sln new file mode 100644 index 0000000000..2d387ac9f5 --- /dev/null +++ b/examples/targets/Configuration API/Log4JXml/Simple/Log4JXml.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Log4JXml", "Log4JXml.csproj", "{D7B11859-2A1B-49F7-A7C7-83B72495BF31}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D7B11859-2A1B-49F7-A7C7-83B72495BF31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7B11859-2A1B-49F7-A7C7-83B72495BF31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7B11859-2A1B-49F7-A7C7-83B72495BF31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7B11859-2A1B-49F7-A7C7-83B72495BF31}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/examples/targets/Configuration API/MSMQ/Multiple Queues/Example.cs b/examples/targets/Configuration API/MSMQ/Multiple Queues/Example.cs index a8692fce24..b7bfa90e87 100644 --- a/examples/targets/Configuration API/MSMQ/Multiple Queues/Example.cs +++ b/examples/targets/Configuration API/MSMQ/Multiple Queues/Example.cs @@ -1,13 +1,11 @@ using NLog; using NLog.Config; -using NLog.Win32.Targets; +using NLog.Targets; class Example { static void Main(string[] args) { - NLog.Internal.InternalLogger.LogToConsole = true; - MSMQTarget target = new MSMQTarget(); target.Queue = ".\\private$\\nlog.${level}"; target.Label = "${message}"; @@ -15,7 +13,9 @@ static void Main(string[] args) target.CreateQueueIfNotExists = true; target.Recoverable = true; - SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger l = LogManager.GetLogger("AAA"); l.Error("This is an error. It goes to .\\private$\\nlog.Error queue."); diff --git a/examples/targets/Configuration API/MSMQ/Simple/Example.cs b/examples/targets/Configuration API/MSMQ/Simple/Example.cs index 955b71d563..aa4c0b8c1a 100644 --- a/examples/targets/Configuration API/MSMQ/Simple/Example.cs +++ b/examples/targets/Configuration API/MSMQ/Simple/Example.cs @@ -1,13 +1,11 @@ using NLog; using NLog.Config; -using NLog.Win32.Targets; +using NLog.Targets; class Example { static void Main(string[] args) { - NLog.Internal.InternalLogger.LogToConsole = true; - MSMQTarget target = new MSMQTarget(); target.Queue = ".\\private$\\nlog"; target.Label = "${message}"; @@ -15,7 +13,9 @@ static void Main(string[] args) target.CreateQueueIfNotExists = true; target.Recoverable = true; - SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger l = LogManager.GetLogger("AAA"); l.Error("This is an error. It goes to .\\private$\\nlog queue."); diff --git a/examples/targets/Configuration API/Mail/Buffered/Example.cs b/examples/targets/Configuration API/Mail/Buffered/Example.cs index 3c191e6e58..22083ec5c9 100644 --- a/examples/targets/Configuration API/Mail/Buffered/Example.cs +++ b/examples/targets/Configuration API/Mail/Buffered/Example.cs @@ -1,6 +1,7 @@ using System; using NLog; +using NLog.Config; using NLog.Targets; using NLog.Targets.Wrappers; @@ -21,9 +22,11 @@ static void Main(string[] args) target.Subject = "sample subject"; target.Body = "${message}${newline}"; - BufferingTargetWrapper buffer = new BufferingTargetWrapper(target, 5); + BufferingTargetWrapper bufferTarget = new BufferingTargetWrapper(target, 5); - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(buffer, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(bufferTarget); + LogManager.Configuration = nlogConfig; Console.WriteLine("Sending..."); Logger logger = LogManager.GetLogger("Example"); @@ -42,7 +45,6 @@ static void Main(string[] args) catch (Exception ex) { Console.WriteLine("EX: {0}", ex); - } } } diff --git a/examples/targets/Configuration API/Mail/Simple/Example.cs b/examples/targets/Configuration API/Mail/Simple/Example.cs index 4a9ccbee70..253ca28ef6 100644 --- a/examples/targets/Configuration API/Mail/Simple/Example.cs +++ b/examples/targets/Configuration API/Mail/Simple/Example.cs @@ -1,6 +1,7 @@ using System; using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -17,7 +18,9 @@ static void Main(string[] args) target.To = "jaak@jkowalski.net"; target.Subject = "sample subject"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Console.WriteLine("Sending..."); Logger logger = LogManager.GetLogger("Example"); @@ -27,7 +30,6 @@ static void Main(string[] args) catch (Exception ex) { Console.WriteLine("EX: {0}", ex); - } } } diff --git a/examples/targets/Configuration API/Memory/Simple/Example.cs b/examples/targets/Configuration API/Memory/Simple/Example.cs index b4b45b5576..c508e0e1ac 100644 --- a/examples/targets/Configuration API/Memory/Simple/Example.cs +++ b/examples/targets/Configuration API/Memory/Simple/Example.cs @@ -1,6 +1,5 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -10,7 +9,9 @@ static void Main(string[] args) MemoryTarget target = new MemoryTarget(); target.Layout = "${message}"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/MethodCall/Simple/Example.cs b/examples/targets/Configuration API/MethodCall/Simple/Example.cs index b11f24f9f3..1a3ec7f2ad 100644 --- a/examples/targets/Configuration API/MethodCall/Simple/Example.cs +++ b/examples/targets/Configuration API/MethodCall/Simple/Example.cs @@ -1,8 +1,8 @@ using System; using NLog; +using NLog.Config; using NLog.Targets; -using System.Diagnostics; public class Example { @@ -10,6 +10,7 @@ public static void LogMethod(string level, string message) { Console.WriteLine("l: {0} m: {1}", level, message); } + static void Main(string[] args) { MethodCallTarget target = new MethodCallTarget(); @@ -18,7 +19,9 @@ static void Main(string[] args) target.Parameters.Add(new MethodCallParameter("${level}")); target.Parameters.Add(new MethodCallParameter("${message}")); - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/NLogViewer/Simple/Example.cs b/examples/targets/Configuration API/NLogViewer/Simple/Example.cs index 49509962bb..96d4d16e67 100644 --- a/examples/targets/Configuration API/NLogViewer/Simple/Example.cs +++ b/examples/targets/Configuration API/NLogViewer/Simple/Example.cs @@ -1,4 +1,5 @@ using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -8,7 +9,9 @@ static void Main(string[] args) NLogViewerTarget target = new NLogViewerTarget(); target.Address = "udp://localhost:4000"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Trace("log message 1"); @@ -17,5 +20,7 @@ static void Main(string[] args) logger.Warn("log message 4"); logger.Error("log message 5"); logger.Fatal("log message 6"); + + LogManager.Flush(); } } diff --git a/examples/targets/Configuration API/Network/Simple/Example.cs b/examples/targets/Configuration API/Network/Simple/Example.cs index ce1fa978e1..6f2f61e0bf 100644 --- a/examples/targets/Configuration API/Network/Simple/Example.cs +++ b/examples/targets/Configuration API/Network/Simple/Example.cs @@ -1,4 +1,5 @@ using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -9,7 +10,9 @@ static void Main(string[] args) target.Layout = "${level} ${logger} ${message}${newline}"; target.Address = "tcp://localhost:5555"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Trace("log message 1"); @@ -18,5 +21,7 @@ static void Main(string[] args) logger.Warn("log message 4"); logger.Error("log message 5"); logger.Fatal("log message 6"); + + LogManager.Flush(); } } diff --git a/examples/targets/Configuration API/Null/Simple/Example.cs b/examples/targets/Configuration API/Null/Simple/Example.cs index d67ceddd16..dfc3b6a6ea 100644 --- a/examples/targets/Configuration API/Null/Simple/Example.cs +++ b/examples/targets/Configuration API/Null/Simple/Example.cs @@ -1,6 +1,5 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -11,7 +10,9 @@ static void Main(string[] args) target.Layout = "${message}"; target.FormatMessage = true; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/OutputDebugString/Simple/Example.cs b/examples/targets/Configuration API/OutputDebugString/Simple/Example.cs index a6b83efafd..b747033718 100644 --- a/examples/targets/Configuration API/OutputDebugString/Simple/Example.cs +++ b/examples/targets/Configuration API/OutputDebugString/Simple/Example.cs @@ -1,8 +1,6 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; -using NLog.Win32.Targets; class Example { @@ -11,7 +9,9 @@ static void Main(string[] args) OutputDebugStringTarget target = new OutputDebugStringTarget(); target.Layout = "${message}"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/PerfCounter/Simple/Example.cs b/examples/targets/Configuration API/PerfCounter/Simple/Example.cs index 3dc4040ce1..d81c715653 100644 --- a/examples/targets/Configuration API/PerfCounter/Simple/Example.cs +++ b/examples/targets/Configuration API/PerfCounter/Simple/Example.cs @@ -1,9 +1,6 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; -using NLog.Win32.Targets; -using System.Diagnostics; class Example { @@ -13,10 +10,12 @@ static void Main(string[] args) target.AutoCreate = true; target.CategoryName = "My category"; target.CounterName = "My counter"; - target.CounterType = PerformanceCounterType.NumberOfItems32; + target.CounterType = System.Diagnostics.PerformanceCounterType.NumberOfItems32; target.InstanceName = "My instance"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/PostFilteringWrapper/Simple/Example.cs b/examples/targets/Configuration API/PostFilteringWrapper/Simple/Example.cs index de47474c31..706a006e17 100644 --- a/examples/targets/Configuration API/PostFilteringWrapper/Simple/Example.cs +++ b/examples/targets/Configuration API/PostFilteringWrapper/Simple/Example.cs @@ -1,9 +1,7 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; using NLog.Targets.Wrappers; -using System.Diagnostics; class Example { @@ -18,12 +16,10 @@ static void Main(string[] args) // set up default filter postFilteringTarget.DefaultFilter = "level >= LogLevel.Info"; - FilteringRule rule; - // if there are any warnings in the buffer // dump the messages whose level is Debug or higher - rule = new FilteringRule(); + FilteringRule rule = new FilteringRule(); rule.Exists = "level >= LogLevel.Warn"; rule.Filter = "level >= LogLevel.Debug"; @@ -33,7 +29,9 @@ static void Main(string[] args) target.BufferSize = 100; target.WrappedTarget = postFilteringTarget; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/RandomizeGroup/Simple/Example.cs b/examples/targets/Configuration API/RandomizeGroup/Simple/Example.cs index 5aab6bd82f..25351d6bc5 100644 --- a/examples/targets/Configuration API/RandomizeGroup/Simple/Example.cs +++ b/examples/targets/Configuration API/RandomizeGroup/Simple/Example.cs @@ -1,9 +1,7 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; -using NLog.Targets.Compound; -using System.Diagnostics; +using NLog.Targets.Wrappers; class Example { @@ -19,9 +17,13 @@ static void Main(string[] args) target.Targets.Add(file1); target.Targets.Add(file2); - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); - logger.Debug("log message"); + logger.Debug("log message 1"); + logger.Debug("log message 2"); + logger.Debug("log message 3"); } } diff --git a/examples/targets/Configuration API/RepeatingWrapper/Simple/Example.cs b/examples/targets/Configuration API/RepeatingWrapper/Simple/Example.cs index e37a6becfd..cf5f5a44f9 100644 --- a/examples/targets/Configuration API/RepeatingWrapper/Simple/Example.cs +++ b/examples/targets/Configuration API/RepeatingWrapper/Simple/Example.cs @@ -1,9 +1,7 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; using NLog.Targets.Wrappers; -using System.Diagnostics; class Example { @@ -16,7 +14,9 @@ static void Main(string[] args) target.WrappedTarget = wrappedTarget; target.RepeatCount = 3; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/RetryingWrapper/Simple/Example.cs b/examples/targets/Configuration API/RetryingWrapper/Simple/Example.cs index df19314aea..7d55c24022 100644 --- a/examples/targets/Configuration API/RetryingWrapper/Simple/Example.cs +++ b/examples/targets/Configuration API/RetryingWrapper/Simple/Example.cs @@ -1,9 +1,7 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; using NLog.Targets.Wrappers; -using System.Diagnostics; class Example { @@ -17,7 +15,9 @@ static void Main(string[] args) target.RetryCount = 3; target.RetryDelayMilliseconds = 1000; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/RoundRobinGroup/Simple/Example.cs b/examples/targets/Configuration API/RoundRobinGroup/Simple/Example.cs index e1cc24e3f2..256be49e1d 100644 --- a/examples/targets/Configuration API/RoundRobinGroup/Simple/Example.cs +++ b/examples/targets/Configuration API/RoundRobinGroup/Simple/Example.cs @@ -1,9 +1,7 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; -using NLog.Targets.Compound; -using System.Diagnostics; +using NLog.Targets.Wrapper; class Example { @@ -19,9 +17,13 @@ static void Main(string[] args) target.Targets.Add(file1); target.Targets.Add(file2); - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); - logger.Debug("log message"); + logger.Debug("log message 1"); + logger.Debug("log message 2"); + logger.Debug("log message 3"); } } diff --git a/examples/targets/Configuration API/SplitGroup/Simple/Example.cs b/examples/targets/Configuration API/SplitGroup/Simple/Example.cs index f7766baeda..fe23b57cdc 100644 --- a/examples/targets/Configuration API/SplitGroup/Simple/Example.cs +++ b/examples/targets/Configuration API/SplitGroup/Simple/Example.cs @@ -1,9 +1,7 @@ -using System; - using NLog; +using NLog.Config; using NLog.Targets; using NLog.Targets.Wrapper; -using System.Diagnostics; class Example { @@ -19,7 +17,9 @@ static void Main(string[] args) target.Targets.Add(file1); target.Targets.Add(file2); - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/Trace/Simple/Example.cs b/examples/targets/Configuration API/Trace/Simple/Example.cs index 45f1b44dee..508fdce2c0 100644 --- a/examples/targets/Configuration API/Trace/Simple/Example.cs +++ b/examples/targets/Configuration API/Trace/Simple/Example.cs @@ -1,6 +1,7 @@ using System; using NLog; +using NLog.Config; using NLog.Targets; using System.Diagnostics; @@ -13,7 +14,9 @@ static void Main(string[] args) TraceTarget target = new TraceTarget(); target.Layout = "${message}"; - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Debug("log message"); diff --git a/examples/targets/Configuration API/WebService/Simple/Example.cs b/examples/targets/Configuration API/WebService/Simple/Example.cs index e55ac1db76..0634d78889 100644 --- a/examples/targets/Configuration API/WebService/Simple/Example.cs +++ b/examples/targets/Configuration API/WebService/Simple/Example.cs @@ -1,4 +1,5 @@ using NLog; +using NLog.Config; using NLog.Targets; class Example @@ -15,7 +16,9 @@ static void Main(string[] args) target.Parameters.Add(new MethodCallParameter("n2", "${logger}")); target.Parameters.Add(new MethodCallParameter("n3", "${level}")); - NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + LoggingConfiguration nlogConfig = new LoggingConfiguration(); + nlogConfig.AddRuleForAllLevels(target); + LogManager.Configuration = nlogConfig; Logger logger = LogManager.GetLogger("Example"); logger.Trace("log message 1"); @@ -24,5 +27,7 @@ static void Main(string[] args) logger.Warn("log message 4"); logger.Error("log message 5"); logger.Fatal("log message 6"); + + LogManager.Flush(); } } diff --git a/examples/targets/Configuration File/ASPNetBufferingWrapper/web.nlog b/examples/targets/Configuration File/ASPNetBufferingWrapper/web.nlog index f67132d5ab..204797e30f 100644 --- a/examples/targets/Configuration File/ASPNetBufferingWrapper/web.nlog +++ b/examples/targets/Configuration File/ASPNetBufferingWrapper/web.nlog @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/ASPNetTrace/web.nlog b/examples/targets/Configuration File/ASPNetTrace/web.nlog index 395dbb4b6b..6c24c922b4 100644 --- a/examples/targets/Configuration File/ASPNetTrace/web.nlog +++ b/examples/targets/Configuration File/ASPNetTrace/web.nlog @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/AsyncWrapper/NLog.config b/examples/targets/Configuration File/AsyncWrapper/NLog.config index b26d0555a7..2e6ed0e7ef 100644 --- a/examples/targets/Configuration File/AsyncWrapper/NLog.config +++ b/examples/targets/Configuration File/AsyncWrapper/NLog.config @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/Database/Oracle.OleDb/NLog.config b/examples/targets/Configuration File/Database/Oracle.OleDb/NLog.config index cef7f5a737..ea356d77cc 100644 --- a/examples/targets/Configuration File/Database/Oracle.OleDb/NLog.config +++ b/examples/targets/Configuration File/Database/Oracle.OleDb/NLog.config @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/Debug/NLog.config b/examples/targets/Configuration File/Debug/NLog.config index c1693fa3ce..b2c14b90d9 100644 --- a/examples/targets/Configuration File/Debug/NLog.config +++ b/examples/targets/Configuration File/Debug/NLog.config @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/Debugger/NLog.config b/examples/targets/Configuration File/Debugger/NLog.config index 6dca1efdc8..03be50598b 100644 --- a/examples/targets/Configuration File/Debugger/NLog.config +++ b/examples/targets/Configuration File/Debugger/NLog.config @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/EventLog/NLog.config b/examples/targets/Configuration File/EventLog/NLog.config index 7447ea3b26..58d34063a5 100644 --- a/examples/targets/Configuration File/EventLog/NLog.config +++ b/examples/targets/Configuration File/EventLog/NLog.config @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/FallbackGroup/NLog.config b/examples/targets/Configuration File/FallbackGroup/NLog.config index 02550c5dfb..511b0f2668 100644 --- a/examples/targets/Configuration File/FallbackGroup/NLog.config +++ b/examples/targets/Configuration File/FallbackGroup/NLog.config @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/File/Archive1/NLog.config b/examples/targets/Configuration File/File/Archive1/NLog.config index f1988b5bb7..6cae76c416 100644 --- a/examples/targets/Configuration File/File/Archive1/NLog.config +++ b/examples/targets/Configuration File/File/Archive1/NLog.config @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/File/Archive2/NLog.config b/examples/targets/Configuration File/File/Archive2/NLog.config index 60aafb784f..b01444d82f 100644 --- a/examples/targets/Configuration File/File/Archive2/NLog.config +++ b/examples/targets/Configuration File/File/Archive2/NLog.config @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/File/Archive3/NLog.config b/examples/targets/Configuration File/File/Archive3/NLog.config index edd9961e67..dfb74dec10 100644 --- a/examples/targets/Configuration File/File/Archive3/NLog.config +++ b/examples/targets/Configuration File/File/Archive3/NLog.config @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/File/Archive4/NLog.config b/examples/targets/Configuration File/File/Archive4/NLog.config index d23b3e83fb..c0383eb80a 100644 --- a/examples/targets/Configuration File/File/Archive4/NLog.config +++ b/examples/targets/Configuration File/File/Archive4/NLog.config @@ -1,6 +1,6 @@ - + diff --git a/examples/targets/Configuration File/File/Asynchronous/NLog.config b/examples/targets/Configuration File/File/Asynchronous/NLog.config index eeec090275..dbf76679e3 100644 --- a/examples/targets/Configuration File/File/Asynchronous/NLog.config +++ b/examples/targets/Configuration File/File/Asynchronous/NLog.config @@ -1,6 +1,6 @@ - + - - - - + @@ -17,17 +12,11 @@ - - + - + diff --git a/examples/targets/Configuration File/WebService/NLog.config b/examples/targets/Configuration File/WebService/NLog.config index dfa0a4e62d..14c1914295 100644 --- a/examples/targets/Configuration File/WebService/NLog.config +++ b/examples/targets/Configuration File/WebService/NLog.config @@ -1,7 +1,7 @@ - + + diff --git a/packages-and-status.md b/packages-and-status.md index 4a38e210dc..39b91e9c9f 100644 --- a/packages-and-status.md +++ b/packages-and-status.md @@ -3,7 +3,6 @@ Package | Build status | NuGet NLog | [![AppVeyor](https://img.shields.io/appveyor/ci/nlog/nlog/master.svg)](https://ci.appveyor.com/project/nlog/nlog/branch/master) | [![NuGet](https://img.shields.io/nuget/vpre/nlog.svg)](https://www.nuget.org/packages/NLog) [NLog.Extensions.Logging](https://github.com/NLog/NLog.Extensions.Logging) | [![Build status](https://img.shields.io/appveyor/ci/nlog/nlog-framework-logging/master.svg)](https://ci.appveyor.com/project/nlog/nlog-framework-logging/branch/master) | [![NuGet Pre Release](https://img.shields.io/nuget/vpre/NLog.Extensions.Logging.svg)](https://www.nuget.org/packages/NLog.Extensions.Logging) NLog.Config | [![AppVeyor](https://img.shields.io/appveyor/ci/nlog/nlog/master.svg)](https://ci.appveyor.com/project/nlog/nlog/branch/master) | [![NuGet package](https://img.shields.io/nuget/v/NLog.Config.svg)](https://www.nuget.org/packages/NLog.Config) -[NLog.Contrib.ActiveMQ](https://github.com/NLog/NLog.Contrib.ActiveMQ) | [![AppVeyor](https://img.shields.io/appveyor/ci/nlog/nlog-contrib-activemq/master.svg)](https://ci.appveyor.com/project/nlog/nlog-contrib-activemq/branch/master) | [![NuGet package](https://img.shields.io/nuget/v/NLog.Contrib.ActiveMQ.svg)](https://www.nuget.org/packages/NLog.Contrib.ActiveMQ) NLog.Database | [![AppVeyor](https://img.shields.io/appveyor/ci/nlog/nlog/master.svg)](https://ci.appveyor.com/project/nlog/nlog/branch/master) | [![NuGet](https://img.shields.io/nuget/vpre/NLog.Database.svg)](https://www.nuget.org/packages/NLog.Database) [NLog.Elmah](https://github.com/NLog/NLog.Elmah) | [![AppVeyor](https://img.shields.io/appveyor/ci/nlog/nlog-Elmah/master.svg)](https://ci.appveyor.com/project/nlog/nlog-Elmah/branch/master) | [![NuGet package](https://img.shields.io/nuget/v/NLog.Elmah.svg)](https://www.nuget.org/packages/NLog.Elmah) [NLog.Etw](https://github.com/NLog/NLog.Etw) | [![AppVeyor](https://img.shields.io/appveyor/ci/nlog/nlog-etw/master.svg)](https://ci.appveyor.com/project/nlog/nlog-etw/branch/master) | [![NuGet package](https://img.shields.io/nuget/v/NLog.Etw.svg)](https://www.nuget.org/packages/NLog.Etw) diff --git a/run-tests.ps1 b/run-tests.ps1 index 0fecf6f43a..3dff9e3a68 100644 --- a/run-tests.ps1 +++ b/run-tests.ps1 @@ -2,7 +2,7 @@ dotnet restore ./tests/NLog.UnitTests/ if (-Not $LastExitCode -eq 0) { exit $LastExitCode } -dotnet test ./tests/NLog.UnitTests/ --framework net6.0 --configuration release --no-restore +dotnet test ./tests/NLog.UnitTests/ --framework net8.0 --configuration release --no-restore if (-Not $LastExitCode -eq 0) { exit $LastExitCode } @@ -24,6 +24,34 @@ if ($isWindows -or $Env:WinDir) if (-Not $LastExitCode -eq 0) { exit $LastExitCode } + dotnet test ./tests/NLog.Targets.AtomicFile.Tests/ --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.RegEx.Tests/ --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.Targets.Mail.Tests/ --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.Targets.Network.Tests/ --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.Targets.Trace.Tests/ --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.Targets.WebService.Tests/ --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.Targets.GZipFile.Tests/ --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + dotnet test ./tests/NLog.WindowsRegistry.Tests/ --configuration release if (-Not $LastExitCode -eq 0) { exit $LastExitCode } @@ -51,7 +79,35 @@ if ($isWindows -or $Env:WinDir) } else { - dotnet test ./tests/NLog.Database.Tests/ --framework net6.0 --configuration release + dotnet test ./tests/NLog.Database.Tests/ --framework net8.0 --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj --framework net8.0 --configuration release --runtime linux-x64 + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.RegEx.Tests/ --framework net8.0 --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.Targets.Mail.Tests/ --framework net8.0 --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.Targets.Network.Tests/ --framework net8.0 --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.Targets.Trace.Tests/ --framework net8.0 --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.Targets.WebService.Tests/ --framework net8.0 --configuration release + if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + dotnet test ./tests/NLog.Targets.GZipFile.Tests/ --framework net8.0 --configuration release if (-Not $LastExitCode -eq 0) { exit $LastExitCode } diff --git a/src/NLog.Database/DatabaseCommandInfo.cs b/src/NLog.Database/DatabaseCommandInfo.cs index fc8868b6ac..5d7c23fc41 100644 --- a/src/NLog.Database/DatabaseCommandInfo.cs +++ b/src/NLog.Database/DatabaseCommandInfo.cs @@ -49,21 +49,19 @@ public class DatabaseCommandInfo /// /// The type of the command. /// - [RequiredParameter] public CommandType CommandType { get; set; } = CommandType.Text; ///

/// Gets or sets the connection string to run the command against. If not provided, connection string from the target is used. /// /// - public Layout ConnectionString { get; set; } + public Layout ConnectionString { get; set; } = Layout.Empty; /// /// Gets or sets the command text. /// /// - [RequiredParameter] - public Layout Text { get; set; } + public Layout Text { get; set; } = Layout.Empty; /// /// Gets or sets a value indicating whether to ignore failures. diff --git a/src/NLog.Database/DatabaseObjectPropertyInfo.cs b/src/NLog.Database/DatabaseObjectPropertyInfo.cs index be622ef1f0..21cf832e0f 100644 --- a/src/NLog.Database/DatabaseObjectPropertyInfo.cs +++ b/src/NLog.Database/DatabaseObjectPropertyInfo.cs @@ -35,7 +35,6 @@ namespace NLog.Targets { using System; using System.Globalization; - using System.Reflection; using NLog.Config; using NLog.Internal; using NLog.Layouts; @@ -59,14 +58,12 @@ public DatabaseObjectPropertyInfo() /// Gets or sets the name for the object-property /// /// - [RequiredParameter] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; /// /// Gets or sets the value to assign on the object-property /// /// - [RequiredParameter] public Layout Layout { get => _layoutInfo.Layout; set => _layoutInfo.Layout = value; } /// @@ -79,22 +76,23 @@ public DatabaseObjectPropertyInfo() /// Gets or sets convert format of the property value /// /// - public string Format { get => _layoutInfo.ValueParseFormat; set => _layoutInfo.ValueParseFormat = value; } + public string? Format { get => _layoutInfo.ValueParseFormat; set => _layoutInfo.ValueParseFormat = value; } /// /// Gets or sets the culture used for parsing property string-value for type-conversion /// /// - public CultureInfo Culture { get => _layoutInfo.ValueParseCulture; set => _layoutInfo.ValueParseCulture = value; } + public CultureInfo? Culture { get => _layoutInfo.ValueParseCulture; set => _layoutInfo.ValueParseCulture = value; } /// /// Render Result Value /// /// Log event for rendering /// Result value when available, else fallback to defaultValue - public object RenderValue(LogEventInfo logEvent) => _layoutInfo.RenderValue(logEvent); + public object? RenderValue(LogEventInfo logEvent) => _layoutInfo.RenderValue(logEvent); - internal bool SetPropertyValue(object dbObject, object propertyValue) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Not supported", "IL2075")] + internal bool SetPropertyValue(object dbObject, object? propertyValue) { var dbConnectionType = dbObject.GetType(); var propertySetterCache = _propertySetter; @@ -119,9 +117,9 @@ private struct PropertySetterCacheItem { public string PropertyName { get; } public Type ObjectType { get; } - public Action PropertySetter { get; } + public Action PropertySetter { get; } - public PropertySetterCacheItem(string propertyName, Type objectType, Action propertySetter) + public PropertySetterCacheItem(string propertyName, Type objectType, Action propertySetter) { PropertyName = propertyName; ObjectType = objectType; @@ -134,6 +132,6 @@ public bool Equals(string propertyName, Type objectType) } } - PropertySetterCacheItem _propertySetter; + private PropertySetterCacheItem _propertySetter; } } diff --git a/src/NLog.Database/DatabaseParameterInfo.cs b/src/NLog.Database/DatabaseParameterInfo.cs index e56b1f17ff..97c8d230cc 100644 --- a/src/NLog.Database/DatabaseParameterInfo.cs +++ b/src/NLog.Database/DatabaseParameterInfo.cs @@ -81,13 +81,15 @@ public class DatabaseParameterInfo { nameof(System.Data.DbType.DateTimeOffset), typeof(DateTimeOffset) } }; + private Func? _dbTypeSetter; + private readonly ValueTypeLayoutInfo _layoutInfo = new ValueTypeLayoutInfo(); /// /// Initializes a new instance of the class. /// public DatabaseParameterInfo() - : this(null, null) + : this(string.Empty, Layout.Empty) { } @@ -102,30 +104,51 @@ public DatabaseParameterInfo(string parameterName, Layout parameterLayout) Layout = parameterLayout; } + /// + /// Initializes a new instance of the class. + /// + /// Name of the parameter. + /// The parameter layout. + /// Method-delegate to perform custom initialization database-parameter. Ex. for AOT to assign custom DbType. + public DatabaseParameterInfo(string parameterName, Layout parameterLayout, Action dbTypeSetter) + : this(parameterName, parameterLayout) + { + if (dbTypeSetter is null) throw new ArgumentNullException(nameof(dbTypeSetter)); + + _dbTypeSetter = (dbParameter) => + { + dbTypeSetter.Invoke(dbParameter); + return true; + }; + } + /// /// Gets or sets the database parameter name. /// /// - [RequiredParameter] public string Name { get; set; } /// - /// Gets or sets the layout that should be use to calculate the value for the parameter. + /// Gets or sets the layout used for rendering the database-parameter value. /// /// - [RequiredParameter] public Layout Layout { get => _layoutInfo.Layout; set => _layoutInfo.Layout = value; } /// /// Gets or sets the database parameter DbType. /// + /// + /// Not compatible with AOT since using type-reflection to convert into Enum and assigning value. + /// /// public string DbType { - get => _dbType; + get => _dbTypeEnum.HasValue ? _dbTypeEnum.ToString() : _dbType; set { _dbType = value; + _dbTypeEnum = null; + if (!string.IsNullOrEmpty(_dbType)) { if (ParameterType is null || ParameterType == _dbParameterType) @@ -142,10 +165,42 @@ public string DbType { ParameterType = null; } + + _dbTypeSetter = AssignParameterDbType; } } - private string _dbType; - private Type _dbParameterType; + private string _dbType = string.Empty; + private Type? _dbParameterType; + + /// + /// Gets or sets the database parameter DbType (without reflection logic) + /// + public DbType DbTypeEnum + { + get => _dbTypeEnum ?? default; + set + { + _dbTypeEnum = value; + _dbType = string.Empty; + + if (ParameterType is null || ParameterType == _dbParameterType) + { + var dbParameterType = TryParseDbType(value.ToString()); + if (dbParameterType != null) + { + ParameterType = dbParameterType; + } + _dbParameterType = dbParameterType; + } + + _dbTypeSetter = (dbParam) => + { + dbParam.DbType = value; + return true; + }; + } + } + private DbType? _dbTypeEnum; /// /// Gets or sets the database parameter size. @@ -169,7 +224,7 @@ public string DbType /// Gets or sets the type of the parameter. /// /// - public Type ParameterType + public Type? ParameterType { get => _layoutInfo.ValueType; set @@ -183,19 +238,19 @@ public Type ParameterType /// Gets or sets the fallback value when result value is not available /// /// - public Layout DefaultValue { get => _layoutInfo.DefaultValue; set => _layoutInfo.DefaultValue = value; } + public Layout? DefaultValue { get => _layoutInfo.DefaultValue; set => _layoutInfo.DefaultValue = value; } /// /// Gets or sets convert format of the database parameter value. /// /// - public string Format { get => _layoutInfo.ValueParseFormat; set => _layoutInfo.ValueParseFormat = value; } + public string? Format { get => _layoutInfo.ValueParseFormat; set => _layoutInfo.ValueParseFormat = value; } /// /// Gets or sets the culture used for parsing parameter string-value for type-conversion /// /// - public CultureInfo Culture { get => _layoutInfo.ValueParseCulture; set => _layoutInfo.ValueParseCulture = value; } + public CultureInfo? Culture { get => _layoutInfo.ValueParseCulture; set => _layoutInfo.ValueParseCulture = value; } /// /// Gets or sets whether empty value should translate into DbNull. Requires database column to allow NULL values. @@ -220,27 +275,17 @@ public bool AllowDbNull /// /// Log event for rendering /// Result value when available, else fallback to defaultValue - public object RenderValue(LogEventInfo logEvent) => _layoutInfo.RenderValue(logEvent); + public object? RenderValue(LogEventInfo logEvent) => _layoutInfo.RenderValue(logEvent); internal bool SetDbType(IDbDataParameter dbParameter) { - if (!string.IsNullOrEmpty(DbType)) - { - if (_cachedDbTypeSetter is null || !_cachedDbTypeSetter.IsValid(dbParameter.GetType(), DbType)) - { - _cachedDbTypeSetter = new DbTypeSetter(dbParameter.GetType(), DbType); - } - - return _cachedDbTypeSetter.SetDbType(dbParameter); - } - - return true; // DbType not in use + return _dbTypeSetter?.Invoke(dbParameter) ?? true; } - private static Type TryParseDbType(string dbTypeName) + private static Type? TryParseDbType(string dbTypeName) { // retrieve the type name if a full name is given - dbTypeName = dbTypeName?.Substring(dbTypeName.LastIndexOf('.') + 1).Trim(); + dbTypeName = dbTypeName is null ? string.Empty : dbTypeName.Substring(dbTypeName.LastIndexOf('.') + 1).Trim(); if (string.IsNullOrEmpty(dbTypeName)) return null; @@ -248,74 +293,89 @@ private static Type TryParseDbType(string dbTypeName) return _typesByDbTypeName.TryGetValue(dbTypeName, out var type) ? type : null; } - DbTypeSetter _cachedDbTypeSetter; + private bool AssignParameterDbType(IDbDataParameter dbParameter) + { + if (_cachedDbTypeSetter is null || !_cachedDbTypeSetter.IsValid(dbParameter.GetType(), DbType)) + { + _cachedDbTypeSetter = new DbTypeSetter(dbParameter.GetType(), _dbType); + } + + return _cachedDbTypeSetter.SetDbType(dbParameter); + } + + private DbTypeSetter? _cachedDbTypeSetter; private sealed class DbTypeSetter { - private readonly Type _dbPropertyInfoType; + private readonly Type _dbParameterType; private readonly string _dbTypeName; - private readonly PropertyInfo _dbTypeSetter; - private readonly Enum _dbTypeValue; - private Action _dbTypeSetterFast; + private readonly Action? _dbTypeSetter; public DbTypeSetter(Type dbParameterType, string dbTypeName) { - _dbPropertyInfoType = dbParameterType; - _dbTypeName = dbTypeName?.Trim(); + _dbParameterType = dbParameterType; + _dbTypeName = dbTypeName?.Trim() ?? string.Empty; if (!string.IsNullOrEmpty(_dbTypeName)) { string[] dbTypeNames = _dbTypeName.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); if (dbTypeNames.Length > 1 && !string.Equals(dbTypeNames[0], nameof(System.Data.DbType), StringComparison.OrdinalIgnoreCase)) { - PropertyInfo propInfo = dbParameterType.GetProperty(dbTypeNames[0], BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); - if (propInfo != null && TryParseEnum(dbTypeNames[1], propInfo.PropertyType, out Enum enumType)) - { - _dbTypeSetter = propInfo; - _dbTypeValue = enumType; - } + _dbTypeSetter = BuildCustomDbSetter(dbParameterType, dbTypeNames[0], dbTypeNames[1]); } else { dbTypeName = dbTypeNames[dbTypeNames.Length - 1]; if (!string.IsNullOrEmpty(dbTypeName) && ConversionHelpers.TryParseEnum(dbTypeName, out DbType dbType)) { - _dbTypeValue = dbType; - _dbTypeSetterFast = (p) => p.DbType = dbType; + _dbTypeSetter = (p) => p.DbType = dbType; + } + else + { + InternalLogger.Error("DatabaseTarget: Failed to resolve enum to assign DbType={0}", dbTypeName); } } } + else + { + _dbTypeSetter = (p) => { }; + } } - public bool IsValid(Type dbParameterType, string dbTypeName) + private static Action? BuildCustomDbSetter(Type dbParameterType, string dbTypePropertyName, string dbTypeEnumValue) { - if (ReferenceEquals(_dbPropertyInfoType, dbParameterType) && ReferenceEquals(_dbTypeName, dbTypeName)) + PropertyInfo propInfo = dbParameterType.GetProperty(dbTypePropertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + if (propInfo is null) { - if (_dbTypeSetterFast == null && _dbTypeSetter != null && _dbTypeValue != null) - { - var propertySetter = _dbTypeSetter.CreatePropertySetter(); - _dbTypeSetterFast = (p) => propertySetter.Invoke(p, _dbTypeValue); - } - return true; + InternalLogger.Error("DatabaseTarget: Failed to resolve type {0} property '{1}' to assign DbType={2}", dbParameterType, dbTypePropertyName, dbTypeEnumValue); + return null; } - return false; + + if (!TryParseEnum(dbTypeEnumValue, propInfo.PropertyType, out var dbType) || dbType is null) + { + InternalLogger.Error("DatabaseTarget: Failed to resolve enum {0} to assign DbType={1}", propInfo.PropertyType, dbTypeEnumValue); + return null; + } + + var propertySetter = propInfo.CreatePropertySetter(); + return (p) => propertySetter.Invoke(p, dbType); + } + + public bool IsValid(Type dbParameterType, string dbTypeName) + { + return ReferenceEquals(_dbParameterType, dbParameterType) && string.Equals(_dbTypeName, dbTypeName, StringComparison.OrdinalIgnoreCase); } public bool SetDbType(IDbDataParameter dbParameter) { - if (_dbTypeSetterFast != null) + if (_dbTypeSetter != null) { - _dbTypeSetterFast.Invoke(dbParameter); - return true; - } - else if (_dbTypeSetter != null && _dbTypeValue != null) - { - _dbTypeSetter.SetValue(dbParameter, _dbTypeValue, null); + _dbTypeSetter.Invoke(dbParameter); return true; } return false; } - private static bool TryParseEnum(string value, Type enumType, out Enum enumValue) + private static bool TryParseEnum(string value, Type enumType, out Enum? enumValue) { if (!string.IsNullOrEmpty(value)) { @@ -331,11 +391,9 @@ private static bool TryParseEnum(string value, Type enumType, out Enum enumValue return false; } } - else - { - enumValue = null; - return false; - } + + enumValue = null; + return false; } } } diff --git a/src/NLog.Database/DatabaseTarget.cs b/src/NLog.Database/DatabaseTarget.cs index cc1f792cb8..9fe9ae55df 100644 --- a/src/NLog.Database/DatabaseTarget.cs +++ b/src/NLog.Database/DatabaseTarget.cs @@ -35,22 +35,18 @@ namespace NLog.Targets { using System; using System.Collections.Generic; - using System.ComponentModel; using System.Data; using System.Data.Common; using System.Reflection; using System.Text; -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 using System.Transactions; -#endif using NLog.Common; using NLog.Config; - using NLog.Internal; using NLog.Layouts; -#if !NETSTANDARD +#if NETFRAMEWORK using System.Configuration; using ConfigurationManager = System.Configuration.ConfigurationManager; #endif @@ -83,31 +79,52 @@ namespace NLog.Targets [Target("Database")] public class DatabaseTarget : Target, IInstallable { - private IDbConnection _activeConnection; - private string _activeConnectionString; + private readonly Func _dbConnectionFactory; + private readonly Func? _resolveConnectionType; + private IDbConnection? _activeConnection; + private string? _activeConnectionString; /// /// Initializes a new instance of the class. /// + /// + /// Not compatible with AOT since using type-reflection with to resolve DbConnection-factory. + /// public DatabaseTarget() { - DBProvider = "sqlserver"; - DBHost = "."; -#if !NETSTANDARD +#if NETFRAMEWORK ConnectionStringsSettings = ConfigurationManager.ConnectionStrings; #endif CommandType = CommandType.Text; + _resolveConnectionType = ResolveConnectionType; + _dbConnectionFactory = CreateDbConnectionFromType; } /// /// Initializes a new instance of the class. /// /// Name of the target. + /// + /// Not compatible with AOT since using type-reflection with to resolve DbConnection-factory. + /// public DatabaseTarget(string name) : this() { Name = name; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A factory function that creates instances of for connecting to the database (AOT compatible) + /// + public DatabaseTarget(Func dbConnectionFactory) + { + _dbConnectionFactory = dbConnectionFactory ?? throw new ArgumentNullException(nameof(dbConnectionFactory)); + CommandType = CommandType.Text; + ConnectionString = new SimpleLayout(""); + } + /// /// Gets or sets the name of the database provider. /// @@ -136,16 +153,14 @@ public DatabaseTarget(string name) : this() /// /// /// - [RequiredParameter] - [DefaultValue("sqlserver")] - public string DBProvider { get; set; } + public string DBProvider { get; set; } = "sqlserver"; -#if !NETSTANDARD +#if NETFRAMEWORK /// /// Gets or sets the name of the connection string (as specified in <connectionStrings> configuration section. /// /// - public string ConnectionStringName { get; set; } + public string ConnectionStringName { get; set; } = string.Empty; #endif /// @@ -153,13 +168,13 @@ public DatabaseTarget(string name) : this() /// specified in DBHost, DBUserName, DBPassword, DBDatabase. /// /// - public Layout ConnectionString { get; set; } + public Layout ConnectionString { get; set; } = Layout.Empty; /// /// Gets or sets the connection string using for installation and uninstallation. If not provided, regular ConnectionString is being used. /// /// - public Layout InstallConnectionString { get; set; } + public Layout InstallConnectionString { get; set; } = Layout.Empty; /// /// Gets the installation DDL commands. @@ -180,7 +195,6 @@ public DatabaseTarget(string name) : this() /// database connection open between the log events. /// /// - [DefaultValue(false)] public bool KeepConnection { get; set; } /// @@ -189,7 +203,7 @@ public DatabaseTarget(string name) : this() /// connection string. /// /// - public Layout DBHost { get; set; } + public Layout DBHost { get; set; } = "."; /// /// Gets or sets the database user name. If the ConnectionString is not provided @@ -197,7 +211,7 @@ public DatabaseTarget(string name) : this() /// connection string. /// /// - public Layout DBUserName { get; set; } + public Layout? DBUserName { get; set; } /// /// Gets or sets the database password. If the ConnectionString is not provided @@ -205,7 +219,7 @@ public DatabaseTarget(string name) : this() /// connection string. /// /// - public Layout DBPassword + public Layout? DBPassword { get => _dbPassword; set @@ -217,8 +231,8 @@ public Layout DBPassword _dbPasswordFixed = null; } } - private Layout _dbPassword; - private string _dbPasswordFixed; + private Layout? _dbPassword; + private string? _dbPasswordFixed; /// /// Gets or sets the database name. If the ConnectionString is not provided @@ -226,7 +240,7 @@ public Layout DBPassword /// connection string. /// /// - public Layout DBDatabase { get; set; } + public Layout? DBDatabase { get; set; } /// /// Gets or sets the text of the SQL command to be run on each log level. @@ -240,8 +254,7 @@ public Layout DBPassword /// The layout renderers should be specified as <parameter /> elements instead. /// /// - [RequiredParameter] - public Layout CommandText { get; set; } + public Layout CommandText { get; set; } = Layout.Empty; /// /// Gets or sets the type of the SQL command to be run on each log level. @@ -252,7 +265,6 @@ public Layout DBPassword /// normally be the name of the stored procedure. TableDirect method is not supported in this context. /// /// - [DefaultValue(CommandType.Text)] public CommandType CommandType { get; set; } /// @@ -285,14 +297,14 @@ public Layout DBPassword /// public System.Data.IsolationLevel? IsolationLevel { get; set; } -#if !NETSTANDARD - internal DbProviderFactory ProviderFactory { get; set; } +#if NETFRAMEWORK + internal DbProviderFactory? ProviderFactory { get; set; } // this is so we can mock the connection string without creating sub-processes - internal ConnectionStringSettingsCollection ConnectionStringsSettings { get; set; } + internal ConnectionStringSettingsCollection? ConnectionStringsSettings { get; set; } #endif - internal Type ConnectionType { get; private set; } + internal Type? ConnectionType { get; private set; } /// /// Performs installation which requires administrative permissions. @@ -326,32 +338,40 @@ public void Uninstall(InstallationContext installationContext) internal IDbConnection OpenConnection(string connectionString, LogEventInfo logEventInfo) { - IDbConnection connection; - -#if !NETSTANDARD - if (ProviderFactory != null) + var dbConnection = _dbConnectionFactory.Invoke(); + if (dbConnection is null) { - connection = ProviderFactory.CreateConnection(); + throw new NLogRuntimeException("Creation of DbConnection failed"); } - else -#endif + + if (!string.IsNullOrEmpty(connectionString)) { - connection = (IDbConnection)Activator.CreateInstance(ConnectionType); + dbConnection.ConnectionString = connectionString; } - if (connection is null) + if (ConnectionProperties?.Count > 0) { - throw new NLogRuntimeException("Creation of connection failed"); + ApplyDatabaseObjectProperties(dbConnection, ConnectionProperties, logEventInfo ?? LogEventInfo.CreateNullEvent()); } - connection.ConnectionString = connectionString; - if (ConnectionProperties?.Count > 0) + dbConnection.Open(); + return dbConnection; + } + + private IDbConnection CreateDbConnectionFromType() + { +#if NETFRAMEWORK + if (ProviderFactory != null) { - ApplyDatabaseObjectProperties(connection, ConnectionProperties, logEventInfo ?? LogEventInfo.CreateNullEvent()); + return ProviderFactory.CreateConnection(); + } +#endif + if (ConnectionType is null) + { + throw new NLogRuntimeException($"Failed to resolve ConnectionType from DbProvider: {DBProvider}"); } - connection.Open(); - return connection; + return (IDbConnection)Activator.CreateInstance(ConnectionType); } private void ApplyDatabaseObjectProperties(object databaseObject, IList objectProperties, LogEventInfo logEventInfo) @@ -381,22 +401,26 @@ protected override void InitializeTarget() { base.InitializeTarget(); + if (CommandText is null || ReferenceEquals(CommandText, Layout.Empty)) + throw new NLogConfigurationException("DatabaseTarget CommandText-property must be assigned. CommandText required for executing SQL commands."); + bool foundProvider = false; string providerName = string.Empty; -#if !NETSTANDARD +#if NETFRAMEWORK if (!string.IsNullOrEmpty(ConnectionStringName)) { // read connection string and provider factory from the configuration file - var cs = ConnectionStringsSettings[ConnectionStringName]; + var cs = ConnectionStringsSettings?[ConnectionStringName]; if (cs is null) { throw new NLogConfigurationException($"Connection string '{ConnectionStringName}' is not declared in section."); } - if (!string.IsNullOrEmpty(cs.ConnectionString?.Trim())) + var connectionString = cs.ConnectionString ?? string.Empty; + if (!string.IsNullOrEmpty(connectionString.Trim())) { - ConnectionString = SimpleLayout.Escape(cs.ConnectionString.Trim()); + ConnectionString = Layout.FromLiteral(connectionString); } providerName = cs.ProviderName?.Trim() ?? string.Empty; } @@ -407,7 +431,7 @@ protected override void InitializeTarget() providerName = InitConnectionString(providerName); } -#if !NETSTANDARD +#if NETFRAMEWORK if (string.IsNullOrEmpty(providerName)) { providerName = GetProviderNameFromDbProviderFactories(providerName); @@ -419,11 +443,11 @@ protected override void InitializeTarget() } #endif - if (!foundProvider) + if (!foundProvider && _resolveConnectionType != null) { try { - SetConnectionType(); + ConnectionType = _resolveConnectionType.Invoke(); if (ConnectionType is null) { InternalLogger.Warn("{0}: No ConnectionType created from DBProvider={1}", this, DBProvider); @@ -435,6 +459,36 @@ protected override void InitializeTarget() throw; } } + + foreach (var parameter in Parameters) + { + if (string.IsNullOrEmpty(parameter.Name)) + throw new NLogConfigurationException($"{this}: Contains invalid Database-Parameter with unassigned Name-property"); + } + + foreach (var property in ConnectionProperties) + { + if (string.IsNullOrEmpty(property.Name)) + throw new NLogConfigurationException($"{this}: Contains invalid Connection-option with unassigned Name-property"); + } + + foreach (var property in CommandProperties) + { + if (string.IsNullOrEmpty(property.Name)) + throw new NLogConfigurationException($"{this}: Contains invalid Command-option with unassigned Name-property"); + } + + foreach (var command in InstallDdlCommands) + { + if (command.Text is null || ReferenceEquals(command.Text, Layout.Empty)) + throw new NLogConfigurationException($"{this}: Contains invalid Install-Command with unassigned Text-property"); + } + + foreach (var command in UninstallDdlCommands) + { + if (command.Text is null || ReferenceEquals(command.Text, Layout.Empty)) + throw new NLogConfigurationException($"{this}: Contains invalid Uninstall-Command with unassigned Text-property"); + } } private string InitConnectionString(string providerName) @@ -453,12 +507,12 @@ private string InitConnectionString(string providerName) } // ConnectionString was overriden by ConnectionString :) - ConnectionString = SimpleLayout.Escape(connectionStringValue.ToString()); + ConnectionString = Layout.FromLiteral(connectionStringValue?.ToString() ?? string.Empty); } } catch (Exception ex) { -#if !NETSTANDARD +#if NETFRAMEWORK if (!string.IsNullOrEmpty(ConnectionStringName)) InternalLogger.Warn(ex, "{0}: DbConnectionStringBuilder failed to parse '{1}' ConnectionString", this, ConnectionStringName); else @@ -469,7 +523,7 @@ private string InitConnectionString(string providerName) return providerName; } -#if !NETSTANDARD +#if NETFRAMEWORK private bool InitProviderFactory(string providerName) { bool foundProvider; @@ -510,7 +564,7 @@ private string GetProviderNameFromDbProviderFactories(string providerName) /// /// Set the to use it for opening connections to the database. /// - private void SetConnectionType() + private Type ResolveConnectionType() { switch (DBProvider.ToUpperInvariant()) { @@ -518,63 +572,56 @@ private void SetConnectionType() case "MSSQL": case "MICROSOFT": case "MSDE": -#if NETSTANDARD +#if !NETFRAMEWORK { try { var assembly = Assembly.Load(new AssemblyName("Microsoft.Data.SqlClient")); - ConnectionType = assembly.GetType("Microsoft.Data.SqlClient.SqlConnection", true, true); + return assembly.GetType("Microsoft.Data.SqlClient.SqlConnection", true, true); } catch (Exception ex) { InternalLogger.Warn(ex, "{0}: Failed to load assembly 'Microsoft.Data.SqlClient'. Falling back to 'System.Data.SqlClient'.", this); var assembly = Assembly.Load(new AssemblyName("System.Data.SqlClient")); - ConnectionType = assembly.GetType("System.Data.SqlClient.SqlConnection", true, true); + return assembly.GetType("System.Data.SqlClient.SqlConnection", true, true); } - break; } case "SYSTEM.DATA.SQLCLIENT": { var assembly = Assembly.Load(new AssemblyName("System.Data.SqlClient")); - ConnectionType = assembly.GetType("System.Data.SqlClient.SqlConnection", true, true); - break; + return assembly.GetType("System.Data.SqlClient.SqlConnection", true, true); } #else case "SYSTEM.DATA.SQLCLIENT": { - var assembly = typeof(IDbConnection).GetAssembly(); - ConnectionType = assembly.GetType("System.Data.SqlClient.SqlConnection", true, true); - break; + var assembly = typeof(IDbConnection).Assembly; + return assembly.GetType("System.Data.SqlClient.SqlConnection", true, true); } #endif case "MICROSOFT.DATA.SQLCLIENT": { var assembly = Assembly.Load(new AssemblyName("Microsoft.Data.SqlClient")); - ConnectionType = assembly.GetType("Microsoft.Data.SqlClient.SqlConnection", true, true); - break; + return assembly.GetType("Microsoft.Data.SqlClient.SqlConnection", true, true); } -#if !NETSTANDARD +#if NETFRAMEWORK case "OLEDB": { - var assembly = typeof(IDbConnection).GetAssembly(); - ConnectionType = assembly.GetType("System.Data.OleDb.OleDbConnection", true, true); - break; + var assembly = typeof(IDbConnection).Assembly; + return assembly.GetType("System.Data.OleDb.OleDbConnection", true, true); } #endif case "ODBC": case "SYSTEM.DATA.ODBC": { -#if NETSTANDARD - var assembly = Assembly.Load(new AssemblyName("System.Data.Odbc")); +#if NETFRAMEWORK + var assembly = typeof(IDbConnection).Assembly; #else - var assembly = typeof(IDbConnection).GetAssembly(); + var assembly = Assembly.Load(new AssemblyName("System.Data.Odbc")); #endif - ConnectionType = assembly.GetType("System.Data.Odbc.OdbcConnection", true, true); - break; + return assembly.GetType("System.Data.Odbc.OdbcConnection", true, true); } default: - ConnectionType = Type.GetType(DBProvider, true, true); - break; + return Type.GetType(DBProvider, true, true); } } @@ -641,7 +688,7 @@ private ICollection>> GroupInBucke } string firstConnectionString = BuildConnectionString(logEvents[0].LogEvent); - IDictionary> dictionary = null; + IDictionary>? dictionary = null; for (int i = 1; i < logEvents.Count; ++i) { var connectionString = BuildConnectionString(logEvents[i].LogEvent); @@ -663,7 +710,10 @@ private ICollection>> GroupInBucke bucket.Add(logEvents[i]); } - return (ICollection>>)dictionary ?? new KeyValuePair>[] { new KeyValuePair>(firstConnectionString, logEvents) }; + if (dictionary is null) + return new[] { new KeyValuePair>(firstConnectionString, logEvents) }; + else + return dictionary; } private void WriteLogEventsToDatabase(IList logEvents, string connectionString) @@ -732,14 +782,14 @@ private void WriteLogEventsWithTransactionScope(IList logEven //Always suppress transaction so that the caller does not rollback logging if they are rolling back their transaction. using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.Suppress)) { - EnsureConnectionOpen(connectionString, logEvents.Count > 0 ? logEvents[0].LogEvent : null); + var activeConnection = EnsureConnectionOpen(connectionString, logEvents[0].LogEvent); - var dbTransaction = _activeConnection.BeginTransaction(IsolationLevel.Value); + var dbTransaction = activeConnection.BeginTransaction(IsolationLevel ?? System.Data.IsolationLevel.Snapshot); try { for (int i = 0; i < logEvents.Count; ++i) { - ExecuteDbCommandWithParameters(logEvents[i].LogEvent, _activeConnection, dbTransaction); + ExecuteDbCommandWithParameters(logEvents[i].LogEvent, activeConnection, dbTransaction); } dbTransaction?.Commit(); @@ -788,9 +838,9 @@ private void WriteLogEventSuppressTransactionScope(LogEventInfo logEvent, string //Always suppress transaction so that the caller does not rollback logging if they are rolling back their transaction. using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.Suppress)) { - EnsureConnectionOpen(connectionString, logEvent); + var activeConnection = EnsureConnectionOpen(connectionString, logEvent); - ExecuteDbCommandWithParameters(logEvent, _activeConnection, null); + ExecuteDbCommandWithParameters(logEvent, activeConnection, null); transactionScope.Complete(); //not really needed as there is no transaction at all. } @@ -810,7 +860,7 @@ private void WriteLogEventSuppressTransactionScope(LogEventInfo logEvent, string /// /// Write logEvent to database /// - private void ExecuteDbCommandWithParameters(LogEventInfo logEvent, IDbConnection dbConnection, IDbTransaction dbTransaction) + private void ExecuteDbCommandWithParameters(LogEventInfo logEvent, IDbConnection dbConnection, IDbTransaction? dbTransaction) { using (IDbCommand command = CreateDbCommand(logEvent, dbConnection)) { @@ -863,7 +913,7 @@ private IDbCommand CreateDbCommandWithParameters(LogEventInfo logEvent, IDbConne /// protected string BuildConnectionString(LogEventInfo logEvent) { - if (ConnectionString != null) + if (ConnectionString != null && !ReferenceEquals(ConnectionString, Layout.Empty)) { return RenderLogEvent(ConnectionString, logEvent); } @@ -883,7 +933,7 @@ protected string BuildConnectionString(LogEventInfo logEvent) sb.Append("User id="); sb.Append(dbUserName); sb.Append(";Password="); - var password = _dbPasswordFixed ?? EscapeValueForConnectionString(_dbPassword.Render(logEvent)); + var password = _dbPasswordFixed ?? EscapeValueForConnectionString(RenderLogEvent(_dbPassword, logEvent)); sb.Append(password); sb.Append(";"); } @@ -941,7 +991,7 @@ private static string EscapeValueForConnectionString(string value) return value; } - private void EnsureConnectionOpen(string connectionString, LogEventInfo logEventInfo) + private IDbConnection EnsureConnectionOpen(string connectionString, LogEventInfo logEventInfo) { if (_activeConnection != null && _activeConnectionString != connectionString) { @@ -951,12 +1001,13 @@ private void EnsureConnectionOpen(string connectionString, LogEventInfo logEvent if (_activeConnection != null) { - return; + return _activeConnection; } InternalLogger.Trace("{0}: Open connection.", this); _activeConnection = OpenConnection(connectionString, logEventInfo); _activeConnectionString = connectionString; + return _activeConnection; } private void CloseConnection() @@ -993,18 +1044,18 @@ private void RunInstallCommands(InstallationContext installationContext, IEnumer var connectionString = GetConnectionStringFromCommand(commandInfo, logEvent); // Set ConnectionType if it has not been initialized already - if (ConnectionType is null) + if (ConnectionType is null && _resolveConnectionType != null) { - SetConnectionType(); + ConnectionType = _resolveConnectionType.Invoke(); } - EnsureConnectionOpen(connectionString, logEvent); + var activeConnection = EnsureConnectionOpen(connectionString, logEvent); string commandText = RenderLogEvent(commandInfo.Text, logEvent); installationContext.Trace("DatabaseTarget(Name={0}) - Executing {1} '{2}'", Name, commandInfo.CommandType, commandText); - using (IDbCommand command = CreateDbCommandWithParameters(logEvent, _activeConnection, commandInfo.CommandType, commandText, commandInfo.Parameters)) + using (IDbCommand command = CreateDbCommandWithParameters(logEvent, activeConnection, commandInfo.CommandType, commandText, commandInfo.Parameters)) { try { @@ -1038,12 +1089,12 @@ private void RunInstallCommands(InstallationContext installationContext, IEnumer private string GetConnectionStringFromCommand(DatabaseCommandInfo commandInfo, LogEventInfo logEvent) { string connectionString; - if (commandInfo.ConnectionString != null) + if (commandInfo.ConnectionString != null && !ReferenceEquals(commandInfo.ConnectionString, Layout.Empty)) { // if there is connection string specified on the command info, use it connectionString = RenderLogEvent(commandInfo.ConnectionString, logEvent); } - else if (InstallConnectionString != null) + else if (InstallConnectionString != null && !ReferenceEquals(InstallConnectionString, Layout.Empty)) { // next, try InstallConnectionString connectionString = RenderLogEvent(InstallConnectionString, logEvent); @@ -1109,7 +1160,7 @@ protected virtual IDbDataParameter CreateDatabaseParameter(IDbCommand command, D /// /// Current logevent. /// Parameter configuration info. - protected internal virtual object GetDatabaseParameterValue(LogEventInfo logEvent, DatabaseParameterInfo parameterInfo) + protected internal virtual object? GetDatabaseParameterValue(LogEventInfo logEvent, DatabaseParameterInfo parameterInfo) { var value = parameterInfo.RenderValue(logEvent); if (parameterInfo.AllowDbNull && (value is null || string.Empty.Equals(value))) @@ -1117,42 +1168,5 @@ protected internal virtual object GetDatabaseParameterValue(LogEventInfo logEven else return value; } - -#if NETSTANDARD1_3 || NETSTANDARD1_5 - /// - /// Fake transaction - /// - /// Transactions aren't in .NET Core: https://github.com/dotnet/corefx/issues/2949 - /// - private sealed class TransactionScope : IDisposable - { - private readonly TransactionScopeOption _suppress; - - public TransactionScope(TransactionScopeOption suppress) - { - _suppress = suppress; - } - - public void Complete() - { - // Fake scope for compatibility, so nothing to do - } - - public void Dispose() - { - // Fake scope for compatibility, so nothing to do - } - } - - /// - /// Fake option - /// - private enum TransactionScopeOption - { - Required, - RequiresNew, - Suppress, - } -#endif } } diff --git a/src/NLog.Database/Internal/DynamicallyAccessedMemberTypes.cs b/src/NLog.Database/Internal/DynamicallyAccessedMemberTypes.cs new file mode 100644 index 0000000000..d9f6a1990e --- /dev/null +++ b/src/NLog.Database/Internal/DynamicallyAccessedMemberTypes.cs @@ -0,0 +1,233 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +#if !NET5_0_OR_GREATER + +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] + internal sealed class DynamicallyAccessedMembersAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + MemberTypes = memberTypes; + } + + /// + /// Gets the which specifies the type + /// of members dynamically accessed. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } + } + + /// + /// Specifies the types of members that are dynamically accessed. + /// + /// This enumeration has a attribute that allows a + /// bitwise combination of its member values. + /// + [Flags] + internal enum DynamicallyAccessedMemberTypes + { + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, + + /// + /// Specifies all members. + /// + All = ~None + } + + /// + /// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a + /// single code artifact. + /// + /// + /// is different than + /// in that it doesn't have a + /// . So it is always preserved in the compiled assembly. + /// + [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] + internal sealed class UnconditionalSuppressMessageAttribute : Attribute + { + /// + /// Initializes a new instance of the + /// class, specifying the category of the tool and the identifier for an analysis rule. + /// + /// The category for the attribute. + /// The identifier of the analysis rule the attribute applies to. + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + + /// + /// Gets the category identifying the classification of the attribute. + /// + /// + /// The property describes the tool or tool analysis category + /// for which a message suppression attribute applies. + /// + public string Category { get; } + + /// + /// Gets the identifier of the analysis tool rule to be suppressed. + /// + /// + /// Concatenated together, the and + /// properties form a unique check identifier. + /// + public string CheckId { get; } + + /// + /// Gets or sets the scope of the code that is relevant for the attribute. + /// + /// + /// The Scope property is an optional argument that specifies the metadata scope for which + /// the attribute is relevant. + /// + public string? Scope { get; set; } + + /// + /// Gets or sets a fully qualified path that represents the target of the attribute. + /// + /// + /// The property is an optional argument identifying the analysis target + /// of the attribute. An example value is "System.IO.Stream.ctor():System.Void". + /// Because it is fully qualified, it can be long, particularly for targets such as parameters. + /// The analysis tool user interface should be capable of automatically formatting the parameter. + /// + public string? Target { get; set; } + + /// + /// Gets or sets an optional argument expanding on exclusion criteria. + /// + /// + /// The property is an optional argument that specifies additional + /// exclusion where the literal metadata target is not sufficiently precise. For example, + /// the cannot be applied within a method, + /// and it may be desirable to suppress a violation against a statement in the method that will + /// give a rule violation, but not against all statements in the method. + /// + public string? MessageId { get; set; } + + /// + /// Gets or sets the justification for suppressing the code analysis message. + /// + public string? Justification { get; set; } + } +} + +#endif diff --git a/src/NLog.Database/Internal/ReflectionHelpers.cs b/src/NLog.Database/Internal/ReflectionHelpers.cs index 7df0aecc5d..e5c2439b19 100644 --- a/src/NLog.Database/Internal/ReflectionHelpers.cs +++ b/src/NLog.Database/Internal/ReflectionHelpers.cs @@ -41,22 +41,12 @@ namespace NLog.Internal /// internal static class ReflectionHelpers { - public static Assembly GetAssembly(this Type type) - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - return type.Assembly; -#else - var typeInfo = type.GetTypeInfo(); - return typeInfo.Assembly; -#endif - } - - public static Action CreatePropertySetter(this PropertyInfo propertyInfo) + public static Action CreatePropertySetter(this PropertyInfo propertyInfo) { var target = System.Linq.Expressions.Expression.Parameter(typeof(object), "target"); var value = System.Linq.Expressions.Expression.Parameter(typeof(object), "parameter"); var body = System.Linq.Expressions.Expression.Call(System.Linq.Expressions.Expression.Convert(target, propertyInfo.DeclaringType), propertyInfo.GetSetMethod(true), System.Linq.Expressions.Expression.Convert(value, propertyInfo.PropertyType)); - return System.Linq.Expressions.Expression.Lambda>(body, target, value).Compile(); + return System.Linq.Expressions.Expression.Lambda>(body, target, value).Compile(); } } } diff --git a/src/NLog.Database/NLog.Database.csproj b/src/NLog.Database/NLog.Database.csproj index 7cfacb26fe..c0c4b45948 100644 --- a/src/NLog.Database/NLog.Database.csproj +++ b/src/NLog.Database/NLog.Database.csproj @@ -1,10 +1,10 @@  - PackageReference - - net35;net46;netstandard1.3;netstandard1.5;netstandard2.0 - + 17.0 + net35;net46;netstandard2.0 + net35;net46;netstandard2.0;netstandard2.1 + NLog Database Target NLog NLog.Database includes DatabaseTarget for writing to any database with DbProvider support @@ -27,12 +27,18 @@ https://github.com/NLog/NLog.git true - 5.0.0.0 + 6.0.0.0 ..\NLog.snk true + true true true + enable + 9 + true + true + true @@ -50,20 +56,14 @@ true - - NLog.Database for NetStandard 1.3 - 1.6.0 - - - - NLog.Database for NetStandard 1.5 - 1.6.0 - - NLog.Database for NetStandard 2.0 + + NLog.Database for NetStandard 2.1 + + @@ -72,7 +72,7 @@ - + @@ -80,14 +80,6 @@ - - - - - - - - $(Title) diff --git a/src/NLog.Database/Properties/AssemblyInfo.cs b/src/NLog.Database/Properties/AssemblyInfo.cs index b41de73006..2d6bf6c293 100644 --- a/src/NLog.Database/Properties/AssemblyInfo.cs +++ b/src/NLog.Database/Properties/AssemblyInfo.cs @@ -42,6 +42,6 @@ [assembly: ComVisible(false)] [assembly: InternalsVisibleTo("NLog.Database.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ef8eab4fbdeb511eeb475e1659fe53f00ec1c1340700f1aa347bf3438455d71993b28b1efbed44c8d97a989e0cb6f01bcb5e78f0b055d311546f63de0a969e04cf04450f43834db9f909e566545a67e42822036860075a1576e90e1c43d43e023a24c22a427f85592ae56cac26f13b7ec2625cbc01f9490d60f16cfbb1bc34d9")] [assembly: AllowPartiallyTrustedCallers] -#if !NET35 && !NETSTANDARD1_3 && !NETSTANDARD1_5 +#if !NET35 [assembly: SecurityRules(SecurityRuleSet.Level1)] #endif diff --git a/src/NLog.OutputDebugString/NLog.OutputDebugString.csproj b/src/NLog.OutputDebugString/NLog.OutputDebugString.csproj index 4fee6c5e45..740347ab91 100644 --- a/src/NLog.OutputDebugString/NLog.OutputDebugString.csproj +++ b/src/NLog.OutputDebugString/NLog.OutputDebugString.csproj @@ -1,12 +1,14 @@  - net35;net46;netstandard2.0 + 17.0 + net35;net46;netstandard2.0 + net35;net46;netstandard2.0;netstandard2.1 NLog.OutputDebugString NLog NLog.OutputDebugString provides support for DebugView output - NLog.PerformanceCounter v$(ProductVersion) + NLog.OutputDebugString v$(ProductVersion) $(ProductVersion) Jarek Kowalski,Kim Christensen,Julian Verdurmen $([System.DateTime]::Now.ToString(yyyy)) @@ -25,16 +27,22 @@ https://github.com/NLog/NLog.git true - 5.0.0.0 + 6.0.0.0 ..\NLog.snk true true true true - true - true - copyused + enable + 9 + true + true + true + + + + NLog.OutputDebugString for .NET Standard 2.1 diff --git a/src/NLog.OutputDebugString/OutputDebugStringTarget.cs b/src/NLog.OutputDebugString/OutputDebugStringTarget.cs index 6b4b664dc2..b5c8629156 100644 --- a/src/NLog.OutputDebugString/OutputDebugStringTarget.cs +++ b/src/NLog.OutputDebugString/OutputDebugStringTarget.cs @@ -38,6 +38,9 @@ namespace NLog.Targets /// /// Outputs log messages through the OutputDebugString() Win32 API. /// + /// + /// See NLog Wiki + /// /// Documentation on NLog Wiki /// ///

@@ -59,7 +62,7 @@ public sealed class OutputDebugStringTarget : TargetWithLayoutHeaderAndFooter /// /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} /// - public OutputDebugStringTarget() : base() + public OutputDebugStringTarget() { } @@ -106,7 +109,7 @@ protected override void Write(LogEventInfo logEvent) WriteDebugString(RenderLogEvent(Layout, logEvent)); } - private void WriteDebugString(string message) + private static void WriteDebugString(string message) { NativeMethods.OutputDebugString(message); } diff --git a/src/NLog.OutputDebugString/Properties/AssemblyInfo.cs b/src/NLog.OutputDebugString/Properties/AssemblyInfo.cs index 0e114863aa..3c9d9e1f4a 100644 --- a/src/NLog.OutputDebugString/Properties/AssemblyInfo.cs +++ b/src/NLog.OutputDebugString/Properties/AssemblyInfo.cs @@ -33,7 +33,6 @@ using System; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; @@ -41,6 +40,6 @@ [assembly: CLSCompliant(true)] [assembly: ComVisible(false)] [assembly: AllowPartiallyTrustedCallers] -#if !NET35 && !NETSTANDARD1_3 && !NETSTANDARD1_5 +#if !NET35 [assembly: SecurityRules(SecurityRuleSet.Level1)] #endif diff --git a/src/NLog.RegEx/Conditions/RegexConditionMethods.cs b/src/NLog.RegEx/Conditions/RegexConditionMethods.cs new file mode 100644 index 0000000000..a7ddc488f5 --- /dev/null +++ b/src/NLog.RegEx/Conditions/RegexConditionMethods.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace NLog.Conditions +{ + ///

+ /// A bunch of utility methods (mostly predicates) which can be used in + /// condition expressions. Partially inspired by XPath 1.0. + /// + [ConditionMethods] + public static class RegexConditionMethods + { + /// + /// Indicates whether the specified regular expression finds a match in the specified input string. + /// + /// The string to search for a match. + /// The regular expression pattern to match. + /// A string consisting of the desired options for the test. The possible values are those of the separated by commas. + /// if the regular expression finds a match; otherwise, . + [ConditionMethod("regex-matches")] + public static bool RegexMatches(string input, string pattern, [Optional, DefaultParameterValue("")] string options) + { + RegexOptions regexOpts = ParseRegexOptions(options) | RegexOptions.ExplicitCapture; + return Regex.IsMatch(input, pattern, regexOpts); + } + + /// + /// + /// + /// + /// + private static RegexOptions ParseRegexOptions(string options) + { + if (string.IsNullOrEmpty(options)) + { + return RegexOptions.None; + } + + return (RegexOptions)Enum.Parse(typeof(RegexOptions), options, true); + } + } +} diff --git a/src/NLog/Internal/RegexHelper.cs b/src/NLog.RegEx/Internal/RegexHelper.cs similarity index 86% rename from src/NLog/Internal/RegexHelper.cs rename to src/NLog.RegEx/Internal/RegexHelper.cs index cccce4b50f..0dd2a0a1d6 100644 --- a/src/NLog/Internal/RegexHelper.cs +++ b/src/NLog.RegEx/Internal/RegexHelper.cs @@ -31,23 +31,22 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.Internal +namespace NLog.RegEx.Internal { using System; using System.Text.RegularExpressions; internal sealed class RegexHelper { - private Regex _regex; - private string _searchText; - private string _regexPattern; + private Regex? _regex; + private string? _searchText; + private string? _regexPattern; private bool _wholeWords; private bool _ignoreCase; - private bool _simpleSearchText; public string SearchText { - get => _searchText; + get => _searchText ?? string.Empty; set { _searchText = value; @@ -56,7 +55,7 @@ public string SearchText } } - public string RegexPattern + public string? RegexPattern { get => _regexPattern; set @@ -68,7 +67,7 @@ public string RegexPattern } /// - /// Compile the ? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used. + /// Compile the ? This can improve the performance, but at the costs of more memory usage. If , the Regex Cache is used. /// public bool CompileRegex { get; set; } @@ -104,7 +103,7 @@ public bool IgnoreCase } } - public Regex Regex + public Regex? Regex { get { @@ -121,7 +120,6 @@ public Regex Regex private void ResetRegex() { - _simpleSearchText = !WholeWords && !CompileRegex && !string.IsNullOrEmpty(SearchText); if (!string.IsNullOrEmpty(SearchText)) { _regexPattern = Regex.Escape(SearchText); @@ -151,11 +149,7 @@ private RegexOptions GetRegexOptions() public string Replace(string input, string replacement) { - if (_simpleSearchText) - { - return IgnoreCase ? StringHelpers.Replace(input, SearchText, replacement, StringComparison.CurrentCultureIgnoreCase) : input.Replace(SearchText, replacement); - } - else if (CompileRegex) + if (CompileRegex) { return Regex?.Replace(input, replacement) ?? input; } @@ -165,7 +159,7 @@ public string Replace(string input, string replacement) } } - public MatchCollection Matches(string input) + public MatchCollection? Matches(string input) { if (CompileRegex) { diff --git a/src/NLog.RegEx/LayoutRenderers/Wrappers/RegexReplaceLayoutRendererWrapper.cs b/src/NLog.RegEx/LayoutRenderers/Wrappers/RegexReplaceLayoutRendererWrapper.cs new file mode 100644 index 0000000000..a6085cf9f1 --- /dev/null +++ b/src/NLog.RegEx/LayoutRenderers/Wrappers/RegexReplaceLayoutRendererWrapper.cs @@ -0,0 +1,164 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.LayoutRenderers.Wrappers +{ + using System; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using NLog.Common; + using NLog.Config; + using NLog.RegEx.Internal; + + /// + /// Replaces a string in the output of another layout with another string. + /// + /// + /// ${replace:searchFor=\\n+:replaceWith=-:inner=${message}} + /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + [LayoutRenderer("regex-replace")] + [AppDomainFixedOutput] + [ThreadAgnostic] + public sealed class RegexReplaceLayoutRendererWrapper : WrapperLayoutRendererBase + { + private readonly RegexHelper _regexHelper = new RegexHelper(); + private MatchEvaluator? _groupMatchEvaluator; + + /// + /// Gets or sets the text to search for. + /// + /// The text search for. + /// + public string SearchFor { get; set; } = string.Empty; + + /// + /// Gets or sets the replacement string. + /// + /// The replacement string. + /// + public string ReplaceWith { get; set; } = string.Empty; + + /// + /// Gets or sets the group name to replace when using regular expressions. + /// Leave null or empty to replace without using group name. + /// + /// The group name. + /// + public string? ReplaceGroupName { get; set; } + + /// + /// Gets or sets a value indicating whether to ignore case. + /// + /// A value of if case should be ignored when searching; otherwise, . + /// + public bool IgnoreCase { get; set; } + + /// + /// Gets or sets a value indicating whether to search for whole words. + /// + /// A value of if whole words should be searched for; otherwise, . + /// + public bool WholeWords { get; set; } + + /// + /// Compile the ? This can improve the performance, but at the costs of more memory usage. If , the Regex Cache is used. + /// + /// + public bool CompileRegex { get; set; } + + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + + if (string.IsNullOrEmpty(SearchFor)) + throw new NLogConfigurationException("RegEx-Replace-LayoutRenderer SearchFor-property must be assigned. Searching for blank value not supported."); + + _regexHelper.IgnoreCase = IgnoreCase; + _regexHelper.WholeWords = WholeWords; + _regexHelper.CompileRegex = CompileRegex; + _regexHelper.RegexPattern = SearchFor; + + if (!string.IsNullOrEmpty(ReplaceGroupName) && _regexHelper.Regex?.GetGroupNames()?.Contains(ReplaceGroupName) == false) + { + InternalLogger.Warn("RegEx-Replace-LayoutRenderer assigned unknown ReplaceGroupName: {0}", ReplaceGroupName); + } + } + + /// + protected override string Transform(string text) + { + if (ReplaceGroupName is null || string.IsNullOrEmpty(ReplaceGroupName)) + { + return _regexHelper.Replace(text, ReplaceWith); + } + else + { + if (_groupMatchEvaluator is null) + _groupMatchEvaluator = m => ReplaceNamedGroup(ReplaceGroupName, ReplaceWith, m); + return _regexHelper.Regex?.Replace(text, _groupMatchEvaluator) ?? text; + } + } + + /// + /// A match evaluator for Regular Expression based replacing + /// + /// Group name in the regex. + /// Replace value. + /// Match from regex. + /// Groups replaced with . + private static string ReplaceNamedGroup(string groupName, string replacement, Match match) + { + var sb = new StringBuilder(match.Value); + var matchLength = match.Length; + + var captures = match.Groups[groupName].Captures.OfType().OrderByDescending(c => c.Index); + foreach (var capt in captures) + { + matchLength += replacement.Length - capt.Length; + + sb.Remove(capt.Index - match.Index, capt.Length); + sb.Insert(capt.Index - match.Index, replacement); + } + + var end = matchLength; + sb.Remove(end, sb.Length - end); + return sb.ToString(); + } + } +} diff --git a/src/NLog.RegEx/NLog.RegEx.csproj b/src/NLog.RegEx/NLog.RegEx.csproj new file mode 100644 index 0000000000..edb3fd05af --- /dev/null +++ b/src/NLog.RegEx/NLog.RegEx.csproj @@ -0,0 +1,88 @@ + + + + 17.0 + net35;net46;netstandard2.0 + net35;net46;netstandard2.0;netstandard2.1 + + NLog.RegEx + NLog + NLog.RegEx included ${regex-replace} and condition method regex-matches + NLog.RegEx v$(ProductVersion) + $(ProductVersion) + Jarek Kowalski,Kim Christensen,Julian Verdurmen + $([System.DateTime]::Now.ToString(yyyy)) + Copyright (c) 2004-$(CurrentYear) NLog Project - https://nlog-project.org/ + + + RegEx-Replace Docs: + https://github.com/NLog/NLog/wiki/Replace-Layout-Renderer + + README.md + NLog;RegEx:RegularExpressions;;logging;log + N.png + https://nlog-project.org/ + BSD-3-Clause + git + https://github.com/NLog/NLog.git + + true + 6.0.0.0 + ..\NLog.snk + true + + true + true + true + enable + 9 + true + true + true + + + + NLog.RegEx for .NET Framework 4.6 + true + + + + NLog.RegEx for .NET Framework 4.5 + true + + + + NLog.RegEx for .NET Framework 3.5 + true + + + + NLog.RegEx for NetStandard 2.0 + + + + NLog.RegEx for NetStandard 2.1 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NLog.RegEx/Properties/AssemblyInfo.cs b/src/NLog.RegEx/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3c9d9e1f4a --- /dev/null +++ b/src/NLog.RegEx/Properties/AssemblyInfo.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security; + +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] +[assembly: AllowPartiallyTrustedCallers] +#if !NET35 +[assembly: SecurityRules(SecurityRuleSet.Level1)] +#endif diff --git a/src/NLog.RegEx/README.md b/src/NLog.RegEx/README.md new file mode 100644 index 0000000000..5d852455e5 --- /dev/null +++ b/src/NLog.RegEx/README.md @@ -0,0 +1,25 @@ +# NLog RegEx Replace LayoutRenderer + +NLog RegEx Replace LayoutRenderer for complex search and replace operations + +If having trouble with output, then check [NLog InternalLogger](https://github.com/NLog/NLog/wiki/Internal-Logging) for clues. See also [Troubleshooting NLog](https://github.com/NLog/NLog/wiki/Logging-Troubleshooting) + +See the [NLog Wiki](https://github.com/NLog/NLog/wiki/Replace-Layout-Renderer) for available options and examples. + +## Register Extension + +NLog will only recognize type-alias `regex-replace` when loading from `NLog.config`-file, if having added extension to `NLog.config`-file: + +```xml + + + +``` + +Alternative register from code using [fluent configuration API](https://github.com/NLog/NLog/wiki/Fluent-Configuration-API): + +```csharp +LogManager.Setup().SetupExtensions(ext => { + ext.RegisterLayoutRenderer(); +}); +``` diff --git a/src/NLog.RegEx/Targets/ConsoleWordHighlightingRuleRegex.cs b/src/NLog.RegEx/Targets/ConsoleWordHighlightingRuleRegex.cs new file mode 100644 index 0000000000..b60db0f146 --- /dev/null +++ b/src/NLog.RegEx/Targets/ConsoleWordHighlightingRuleRegex.cs @@ -0,0 +1,95 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets +{ + using System.Collections.Generic; + using NLog.Config; + using NLog.RegEx.Internal; + + /// + /// Highlighting rule for Win32 colorful console. + /// + [NLogConfigurationItem] + public class ConsoleWordHighlightingRuleRegex : ConsoleWordHighlightingRule + { + private readonly RegexHelper _regexHelper = new RegexHelper(); + + /// + /// Gets or sets the regular expression to be matched. You must specify either text or regex. + /// + /// + public string? Regex + { + get => _regexHelper.RegexPattern; + set => _regexHelper.RegexPattern = value; + } + + /// + /// Compile the ? This can improve the performance, but at the costs of more memory usage. If , the Regex Cache is used. + /// + /// + public bool CompileRegex + { + get => _regexHelper.CompileRegex; + set => _regexHelper.CompileRegex = value; + } + + private string _searchText = string.Empty; + + /// + protected override IEnumerable>? GetWordsForHighlighting(string haystack) + { + if (!ReferenceEquals(_searchText, Text)) + { + if (!string.IsNullOrEmpty(Text)) + _regexHelper.SearchText = Text; + _regexHelper.WholeWords = WholeWords; + _regexHelper.IgnoreCase = IgnoreCase; + _searchText = Text; + } + + var matches = _regexHelper.Matches(haystack); + if (matches is null || matches.Count == 0) + return null; + + return YieldWordsForHighlighting(matches); + } + + private static IEnumerable> YieldWordsForHighlighting(System.Text.RegularExpressions.MatchCollection matches) + { + foreach (System.Text.RegularExpressions.Match match in matches) + yield return new KeyValuePair(match.Index, match.Length); + } + } +} diff --git a/src/NLog.Targets.AtomicFile/AtomicFileTarget.cs b/src/NLog.Targets.AtomicFile/AtomicFileTarget.cs new file mode 100644 index 0000000000..fcbb349691 --- /dev/null +++ b/src/NLog.Targets.AtomicFile/AtomicFileTarget.cs @@ -0,0 +1,161 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets +{ + using System; + using System.IO; + using NLog.Common; + + /// + /// Extended standard FileTarget with atomic file append for multi-process logging to the same file + /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + [Target("AtomFile")] + [Target("AtomicFile")] + public class AtomicFileTarget : FileTarget + { + /// + /// Gets or sets a value indicating whether concurrent writes to the log file by multiple processes on the same host. + /// + public bool ConcurrentWrites { get; set; } = true; + + /// + protected override Stream CreateFileStream(string filePath, int bufferSize) + { + if (!ConcurrentWrites || !KeepFileOpen || ReplaceFileContentsOnEachWrite) + return base.CreateFileStream(filePath, bufferSize); + + const int maxRetryCount = 5; + for (int i = 1; i <= maxRetryCount; ++i) + { + try + { + return CreateAtomicFileStream(filePath); + } + catch (DirectoryNotFoundException) + { + throw; + } + catch (IOException ex) + { + InternalLogger.Debug(ex, "{0}: Failed opening file: {1}", this, filePath); + if (i == maxRetryCount) + throw; + } + catch (Exception ex) + { + InternalLogger.Debug(ex, "{0}: Failed opening file: {1}", this, filePath); + if (i > 1) + throw; + } + } + + throw new InvalidOperationException("Should not be reached."); + } + + private Stream CreateAtomicFileStream(string filePath) + { + var fileShare = FileShare.ReadWrite; + if (EnableFileDelete) + fileShare |= FileShare.Delete; + +#if NETFRAMEWORK + // https://blogs.msdn.microsoft.com/oldnewthing/20151127-00/?p=92211/ + // https://msdn.microsoft.com/en-us/library/ff548289.aspx + // If only the FILE_APPEND_DATA and SYNCHRONIZE flags are set, the caller can write only to the end of the file, + // and any offset information about writes to the file is ignored. + // However, the file will automatically be extended as necessary for this type of write operation. + return new FileStream( + filePath, + FileMode.Append, + System.Security.AccessControl.FileSystemRights.AppendData | System.Security.AccessControl.FileSystemRights.Synchronize, // <- Atomic append + fileShare, + bufferSize: 1, // No internal buffer, write directly from user-buffer + FileOptions.None); +#else + +#if !WINDOWS + if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) + { + return CreateUnixStream(filePath); + } +#endif + + var systemRights = System.Security.AccessControl.FileSystemRights.AppendData | System.Security.AccessControl.FileSystemRights.Synchronize; + return FileSystemAclExtensions.Create( + new FileInfo(filePath), + FileMode.Append, + systemRights, + fileShare, + bufferSize: 1, // No internal buffer, write directly from user-buffer + FileOptions.None, + fileSecurity: null); +#endif + } + +#if !NETFRAMEWORK && !WINDOWS + private Stream CreateUnixStream(string filePath) + { + // Use 0666 (read/write for all) + var permissions = (Mono.Unix.Native.FilePermissions)(6 | (6 << 3) | (6 << 6)); + var openFlags = Mono.Unix.Native.OpenFlags.O_CREAT | Mono.Unix.Native.OpenFlags.O_WRONLY | Mono.Unix.Native.OpenFlags.O_APPEND; + + int fd = Mono.Unix.Native.Syscall.open(filePath, openFlags, permissions); + if (fd == -1 && Mono.Unix.Native.Stdlib.GetLastError() == Mono.Unix.Native.Errno.ENOENT && CreateDirs) + { + var dirName = Path.GetDirectoryName(filePath); + if (dirName != null && !Directory.Exists(dirName)) + Directory.CreateDirectory(dirName); + + fd = Mono.Unix.Native.Syscall.open(filePath, openFlags, permissions); + } + if (fd == -1) + Mono.Unix.UnixMarshal.ThrowExceptionForLastError(); + + try + { + return new Mono.Unix.UnixStream(fd, true); + } + catch + { + Mono.Unix.Native.Syscall.close(fd); + throw; + } + } +#endif + } +} diff --git a/src/NLog.Targets.AtomicFile/NLog.Targets.AtomicFile.csproj b/src/NLog.Targets.AtomicFile/NLog.Targets.AtomicFile.csproj new file mode 100644 index 0000000000..3436039836 --- /dev/null +++ b/src/NLog.Targets.AtomicFile/NLog.Targets.AtomicFile.csproj @@ -0,0 +1,100 @@ + + + + net35;net46;net8.0;net8.0-windows + + NLog.Targets.AtomicFile + NLog + FileTarget with support for atomic append where multiple processes can write to the same file + NLog.Targets.AtomicFile v$(ProductVersion) + $(ProductVersion) + Jarek Kowalski,Kim Christensen,Julian Verdurmen + $([System.DateTime]::Now.ToString(yyyy)) + Copyright (c) 2004-$(CurrentYear) NLog Project - https://nlog-project.org/ + + + AtomFile Target Docs: + https://github.com/NLog/NLog/wiki/Atomic-File-target + + NLog;File;Archive;NTFS;logging;log + N.png + https://nlog-project.org/ + BSD-3-Clause + git + https://github.com/NLog/NLog.git + + true + 6.0.0.0 + ..\NLog.snk + true + + true + true + true + enable + 9 + true + true + true + true + + + + NLog.Targets.AtomicFile for .NET Framework 4.6 + true + + + + NLog.Targets.AtomicFile for .NET Framework 4.5 + true + + + + NLog.Targets.AtomicFile for .NET Framework 3.5 + true + + + + NLog.Targets.AtomicFile for NET8 + + + + NLog.Targets.AtomicFile for NET8 Windows + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NLog.Targets.AtomicFile/Properties/AssemblyInfo.cs b/src/NLog.Targets.AtomicFile/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3c9d9e1f4a --- /dev/null +++ b/src/NLog.Targets.AtomicFile/Properties/AssemblyInfo.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security; + +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] +[assembly: AllowPartiallyTrustedCallers] +#if !NET35 +[assembly: SecurityRules(SecurityRuleSet.Level1)] +#endif diff --git a/src/NLog/Targets/ArchiveNumberingMode.cs b/src/NLog.Targets.ConcurrentFile/ArchiveNumberingMode.cs similarity index 100% rename from src/NLog/Targets/ArchiveNumberingMode.cs rename to src/NLog.Targets.ConcurrentFile/ArchiveNumberingMode.cs diff --git a/src/NLog.Targets.ConcurrentFile/ConcurrentFileTarget.cs b/src/NLog.Targets.ConcurrentFile/ConcurrentFileTarget.cs new file mode 100644 index 0000000000..e0e3fcdfc4 --- /dev/null +++ b/src/NLog.Targets.ConcurrentFile/ConcurrentFileTarget.cs @@ -0,0 +1,2511 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Globalization; + using System.IO; + using System.Text; + using System.Threading; + using NLog.Common; + using NLog.Config; + using NLog.Internal; + using NLog.Internal.FileAppenders; + using NLog.Layouts; + using NLog.Targets.FileArchiveModes; + using NLog.Time; + + /// + /// Writes log messages to one or more files. + /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + [Target("File")] + public class ConcurrentFileTarget : TargetWithLayoutHeaderAndFooter, ICreateFileParameters + { + /// + /// Default clean up period of the initialized files. When a file exceeds the clean up period is removed from the list. + /// + /// Clean up period is defined in days. + private const int InitializedFilesCleanupPeriod = 2; + + /// + /// This value disables file archiving based on the size. + /// + private const long ArchiveAboveSizeDisabled = -1L; + + /// + /// Holds the initialized files each given time by the instance. Against each file, the last write time is stored. + /// + /// Last write time is store in local time (no UTC). + private readonly Dictionary _initializedFiles = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// List of the associated file appenders with the instance. + /// + private IFileAppenderCache _fileAppenderCache; + + IFileArchiveMode GetFileArchiveHelper(string archiveFilePattern) + { + return _fileArchiveHelper ?? (_fileArchiveHelper = FileArchiveModeFactory.CreateArchiveStyle(archiveFilePattern, ArchiveNumbering, GetArchiveDateFormatString(ArchiveDateFormat), ArchiveFileName != null, MaxArchiveFiles > 0 || MaxArchiveDays > 0)); + } + private IFileArchiveMode _fileArchiveHelper; + + private Timer _autoClosingTimer; + + /// + /// The number of initialized files at any one time. + /// + private int _initializedFilesCounter; + + /// + /// The maximum number of archive files that should be kept. + /// + private int _maxArchiveFiles; + + /// + /// The maximum days of archive files that should be kept. + /// + private int _maxArchiveDays; + + /// + /// The filename as target + /// + private FilePathLayout _fullFileName; + + /// + /// The archive file name as target + /// + private FilePathLayout _fullArchiveFileName; + + private FileArchiveEveryPeriod _archiveEvery; + private long _archiveAboveSize; + + private bool _enableArchiveFileCompression; + + /// + /// The date of the previous log event. + /// + private DateTime? _previousLogEventTimestamp; + + /// + /// The file name of the previous log event. + /// + private string _previousLogFileName; + + private bool _concurrentWrites; + private bool _cleanupFileName; + private FilePathKind _fileNameKind = FilePathKind.Unknown; + private FilePathKind _archiveFileKind; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} + /// + public ConcurrentFileTarget() : this(FileAppenderCache.Empty) + { + } + + internal ConcurrentFileTarget(IFileAppenderCache fileAppenderCache) + { + ArchiveNumbering = ArchiveNumberingMode.Sequence; + _maxArchiveFiles = 0; + _maxArchiveDays = 0; + + ArchiveEvery = FileArchiveEveryPeriod.None; + ArchiveAboveSize = ArchiveAboveSizeDisabled; + _cleanupFileName = true; + + _fileAppenderCache = fileAppenderCache; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} + /// + /// Name of the target. + public ConcurrentFileTarget(string name) : this() + { + Name = name; + } + + /// + /// Gets or sets the name of the file to write to. + /// + /// + /// This FileName string is a layout which may include instances of layout renderers. + /// This lets you use a single target to write to multiple files. + /// + /// + /// The following value makes NLog write logging events to files based on the log level in the directory where + /// the application runs. + /// ${basedir}/${level}.log + /// All Debug messages will go to Debug.log, all Info messages will go to Info.log and so on. + /// You can combine as many of the layout renderers as you want to produce an arbitrary log file name. + /// + /// + public Layout FileName + { + get + { + return _fullFileName?.GetLayout() ?? Layout.Empty; + } + set + { + _fullFileName = CreateFileNameLayout(value); + ResetFileAppenders("FileName Changed"); + } + } + + private FilePathLayout CreateFileNameLayout(Layout value) + { + if (value is null) + return null; + + return new FilePathLayout(value, CleanupFileName, FileNameKind); + } + + /// + /// Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. + /// If set to , nothing gets written when the filename is wrong. + /// + /// + public bool CleanupFileName + { + get => _cleanupFileName; + set + { + if (_cleanupFileName != value) + { + _cleanupFileName = value; + _fullFileName = CreateFileNameLayout(FileName); + _fullArchiveFileName = CreateFileNameLayout(ArchiveFileName); + ResetFileAppenders("CleanupFileName Changed"); + } + } + } + + /// + /// Is the an absolute or relative path? + /// + /// + public FilePathKind FileNameKind + { + get => _fileNameKind; + set + { + if (_fileNameKind != value) + { + _fileNameKind = value; + _fullFileName = CreateFileNameLayout(FileName); + _fullArchiveFileName = CreateFileNameLayout(ArchiveFileName); + ResetFileAppenders("FileNameKind Changed"); + } + } + } + + /// + /// Gets or sets a value indicating whether to create directories if they do not exist. + /// + /// + /// Setting this to may improve performance a bit, but you'll receive an error + /// when attempting to write to a directory that's not present. + /// + /// + public bool CreateDirs { get; set; } = true; + + /// + /// Gets or sets a value indicating whether to delete old log file on startup. + /// + /// + /// This option works only when the "FileName" parameter denotes a single file. + /// + /// + public bool DeleteOldFileOnStartup { get; set; } + + /// + /// Gets or sets a value indicating whether to replace file contents on each write instead of appending log message at the end. + /// + /// + public bool ReplaceFileContentsOnEachWrite { get; set; } + + /// + /// Gets or sets a value indicating whether to keep log file open instead of opening and closing it on each logging event. + /// + /// + /// KeepFileOpen = gives the best performance, and ensure the file-lock is not lost to other applications.
+ /// KeepFileOpen = gives the best compatibility, but slow performance and lead to file-locking issues with other applications. + ///
+ /// + public bool KeepFileOpen + { + get => _keepFileOpen; + set + { + if (_keepFileOpen != value) + { + _keepFileOpen = value; + ResetFileAppenders("KeepFileOpen Changed"); + } + } + } + private bool _keepFileOpen = true; + + /// + /// Gets or sets a value indicating whether to enable log file(s) to be deleted. + /// + /// + public bool EnableFileDelete { get; set; } = true; + + /// + /// Gets or sets the file attributes (Windows only). + /// + /// + public Win32FileAttributes FileAttributes + { + get => _fileAttributes; + set + { + if (value != Win32FileAttributes.Normal && PlatformDetector.IsWin32) + { + ForceManaged = false; + } + _fileAttributes = value; + } + } + Win32FileAttributes _fileAttributes = Win32FileAttributes.Normal; + + bool ICreateFileParameters.IsArchivingEnabled => IsArchivingEnabled; + + int ICreateFileParameters.FileOpenRetryCount => ConcurrentWrites ? ConcurrentWriteAttempts : (KeepFileOpen ? 0 : (_concurrentWriteAttempts ?? 2)); + + int ICreateFileParameters.FileOpenRetryDelay => ConcurrentWriteAttemptDelay; + + /// + /// Gets or sets the line ending mode. + /// + /// + public LineEndingMode LineEnding { get; set; } = LineEndingMode.Default; + + /// + /// Gets or sets a value indicating whether to automatically flush the file buffers after each log message. + /// + /// + public bool AutoFlush { get; set; } = true; + + /// + /// Gets or sets the number of files to be kept open. Setting this to a higher value may improve performance + /// in a situation where a single File target is writing to many files + /// (such as splitting by level or by logger). + /// + /// + /// The files are managed on a LRU (least recently used) basis, which flushes + /// the files that have not been used for the longest period of time should the + /// cache become full. As a rule of thumb, you shouldn't set this parameter to + /// a very high value. A number like 10-15 shouldn't be exceeded, because you'd + /// be keeping a large number of files open which consumes system resources. + /// + /// + public int OpenFileCacheSize { get; set; } = 5; + + /// + /// Gets or sets the maximum number of seconds that files are kept open. Zero or negative means disabled. + /// + /// + public int OpenFileCacheTimeout { get; set; } + + /// + /// Gets or sets the maximum number of seconds before open files are flushed. Zero or negative means disabled. + /// + /// + public int OpenFileFlushTimeout { get; set; } + + /// + /// Gets or sets the log file buffer size in bytes. + /// + /// + public int BufferSize { get; set; } = 32768; + + /// + /// Gets or sets the file encoding. + /// + /// + public Encoding Encoding + { + get => _encoding; + set + { + _encoding = value; + if (!_writeBom.HasValue && InitialValueBom(value)) + _writeBom = true; + } + } + private Encoding _encoding = Encoding.UTF8; + + /// + /// Gets or sets whether or not this target should just discard all data that its asked to write. + /// Mostly used for when testing NLog Stack except final write + /// + /// + public bool DiscardAll { get; set; } + + /// + /// Gets or sets a value indicating whether concurrent writes to the log file by multiple processes on the same host. + /// + /// + /// This makes multi-process logging possible. NLog uses a special technique + /// that lets it keep the files open for writing. + /// + /// + public bool ConcurrentWrites + { + get => _concurrentWrites; + set + { + if (_concurrentWrites != value) + { + _concurrentWrites = value; + ResetFileAppenders("ConcurrentWrites Changed"); + } + } + } + + /// + /// Obsolete and replaced by = with NLog v5.3. + /// Gets or sets a value indicating whether concurrent writes to the log file by multiple processes on different network hosts. + /// + /// + /// This effectively prevents files from being kept open. + /// + /// + [Obsolete("Instead use KeepFileOpen = false. Marked obsolete with NLog v5.3")] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool NetworkWrites { get => !KeepFileOpen; set => KeepFileOpen = !value; } + + /// + /// Gets or sets a value indicating whether to write BOM (byte order mark) in created files. + /// + /// Defaults to for UTF-16 and UTF-32 + /// + /// + public bool WriteBom + { + get => _writeBom ?? false; + set => _writeBom = value; + } + private bool? _writeBom; + + /// + /// Gets or sets the number of times the write is appended on the file before NLog + /// discards the log message. + /// + /// + public int ConcurrentWriteAttempts { get => _concurrentWriteAttempts ?? 10; set => _concurrentWriteAttempts = value; } + private int? _concurrentWriteAttempts; + + /// + /// Gets or sets the delay in milliseconds to wait before attempting to write to the file again. + /// + /// + /// The actual delay is a random value between 0 and the value specified + /// in this parameter. On each failed attempt the delay base is doubled + /// up to times. + /// + /// + /// Assuming that ConcurrentWriteAttemptDelay is 10 the time to wait will be:

+ /// a random value between 0 and 10 milliseconds - 1st attempt
+ /// a random value between 0 and 20 milliseconds - 2nd attempt
+ /// a random value between 0 and 40 milliseconds - 3rd attempt
+ /// a random value between 0 and 80 milliseconds - 4th attempt
+ /// ...

+ /// and so on. + /// + /// + public int ConcurrentWriteAttemptDelay { get; set; } = 1; + + ///

+ /// Gets or sets a value indicating whether to archive old log file on startup. + /// + /// + /// This option works only when the "FileName" parameter denotes a single file. + /// After archiving the old file, the current log file will be empty. + /// + /// + public bool ArchiveOldFileOnStartup + { + get => _archiveOldFileOnStartup ?? false; + set => _archiveOldFileOnStartup = value; + } + private bool? _archiveOldFileOnStartup; + + /// + /// Gets or sets whether to write the Header on initial creation of file appender, even if the file is not empty. + /// Default value is , which means only write header when initial file is empty (Ex. ensures valid CSV files) + /// + /// + /// Alternative use to ensure each application session gets individual log-file. + /// + public bool WriteHeaderWhenInitialFileNotEmpty { get; set; } + + /// + /// Gets or sets a value of the file size threshold to archive old log file on startup. + /// + /// + /// This option won't work if is set to + /// Default value is 0 which means that the file is archived as soon as archival on + /// startup is enabled. + /// + /// + public long ArchiveOldFileOnStartupAboveSize { get; set; } + + /// + /// Gets or sets a value specifying the date format to use when archiving files. + /// + /// + /// This option works only when the "ArchiveNumbering" parameter is set either to Date or DateAndSequence. + /// + /// + public string ArchiveDateFormat + { + get => _archiveDateFormat; + set + { + if (_archiveDateFormat != value) + { + _archiveDateFormat = value; + ResetFileAppenders("ArchiveDateFormat Changed"); // Reset archive file-monitoring + } + } + } + private string _archiveDateFormat = string.Empty; + + /// + /// Gets or sets the size in bytes above which log files will be automatically archived. + /// + /// + /// Notice when combined with then it will attempt to append to any existing + /// archive file if grown above size multiple times. New archive file will be created when using + /// + /// + public long ArchiveAboveSize + { + get => _archiveAboveSize; + set + { + var newValue = value > 0 ? value : ArchiveAboveSizeDisabled; + if ((_archiveAboveSize > 0) != (newValue > 0)) + { + _archiveAboveSize = newValue; + ResetFileAppenders("ArchiveAboveSize Changed"); // Reset archive file-monitoring + } + else + { + _archiveAboveSize = newValue; + } + } + } + + /// + /// Gets or sets a value indicating whether to automatically archive log files every time the specified time passes. + /// + /// + /// Files are moved to the archive as part of the write operation if the current period of time changes. For example + /// if the current hour changes from 10 to 11, the first write that will occur + /// on or after 11:00 will trigger the archiving. + /// + /// + public FileArchiveEveryPeriod ArchiveEvery + { + get => _archiveEvery; + set + { + if (_archiveEvery != value) + { + _archiveEvery = value; + ResetFileAppenders("ArchiveEvery Changed"); // Reset archive file-monitoring + } + } + } + + /// + /// Is the an absolute or relative path? + /// + /// + public FilePathKind ArchiveFileKind + { + get => _archiveFileKind; + set + { + if (_archiveFileKind != value) + { + _archiveFileKind = value; + _fullArchiveFileName = CreateFileNameLayout(ArchiveFileName); + ResetFileAppenders("ArchiveFileKind Changed"); // Reset archive file-monitoring + } + } + } + + /// + /// Gets or sets the name of the file to be used for an archive. + /// + /// + /// It may contain a special placeholder {#####} + /// that will be replaced with a sequence of numbers depending on + /// the archiving strategy. The number of hash characters used determines + /// the number of numerical digits to be used for numbering files. + /// + /// + public Layout ArchiveFileName + { + get + { + return _fullArchiveFileName?.GetLayout(); + } + set + { + _fullArchiveFileName = CreateFileNameLayout(value); + ResetFileAppenders("ArchiveFileName Changed"); // Reset archive file-monitoring + } + } + + /// + /// Gets or sets the maximum number of archive files that should be kept. + /// + /// + public int MaxArchiveFiles + { + get => _maxArchiveFiles; + set + { + if (_maxArchiveFiles != value) + { + _maxArchiveFiles = value; + ResetFileAppenders("MaxArchiveFiles Changed"); // Enforce archive cleanup + } + } + } + + /// + /// Gets or sets the maximum days of archive files that should be kept. + /// + /// + public int MaxArchiveDays + { + get => _maxArchiveDays; + set + { + if (_maxArchiveDays != value) + { + _maxArchiveDays = value; + ResetFileAppenders("MaxArchiveDays Changed"); // Enforce archive cleanup + } + } + } + + /// + /// Gets or sets the way file archives are numbered. + /// + /// + public ArchiveNumberingMode ArchiveNumbering + { + get => _archiveNumbering; + set + { + if (_archiveNumbering != value) + { + _archiveNumbering = value; + ResetFileAppenders("ArchiveNumbering Changed"); // Reset archive file-monitoring + } + } + } + private ArchiveNumberingMode _archiveNumbering; + + /// + /// Used to compress log files during archiving. + /// This may be used to provide your own implementation of a zip file compressor, + /// on platforms other than .Net4.5. + /// Defaults to ZipArchiveFileCompressor on .Net4.5 and to null otherwise. + /// + /// + public static IFileCompressor FileCompressor { get; set; } +#if !NET35 && !NET40 + = new ZipArchiveFileCompressor(); +#endif + + /// + /// Gets or sets a value indicating whether to compress archive files into the zip archive format. + /// + /// + public bool EnableArchiveFileCompression + { + get => _enableArchiveFileCompression && FileCompressor != null; + set + { + if (_enableArchiveFileCompression != value) + { + _enableArchiveFileCompression = value; + ResetFileAppenders("EnableArchiveFileCompression Changed"); // Reset archive file-monitoring + } + } + } + + /// + /// Gets or set a value indicating whether a managed file stream is forced, instead of using the native implementation. + /// + /// + public bool ForceManaged { get; set; } = true; + + /// + /// Gets or sets a value indicating whether file creation calls should be synchronized by a system global mutex. + /// + /// + public bool ForceMutexConcurrentWrites { get; set; } + + /// + /// Gets or sets a value indicating whether the footer should be written only when the file is archived. + /// + /// + public bool WriteFooterOnArchivingOnly { get; set; } + + /// + /// Gets the characters that are appended after each line. + /// + protected internal string NewLineChars => LineEnding.NewLineCharacters; + + /// + /// Refresh the ArchiveFilePatternToWatch option of the . + /// The log file must be watched for archiving when multiple processes are writing to the same + /// open file. + /// + private void RefreshArchiveFilePatternToWatch(string fileName, LogEventInfo logEvent) + { + _fileAppenderCache.CheckCloseAppenders -= AutoCloseAppendersAfterArchive; + + bool mustWatchArchiving = IsArchivingEnabled && KeepFileOpen && ConcurrentWrites; + bool mustWatchActiveFile = EnableFileDelete && ((KeepFileOpen && ConcurrentWrites) || (IsSimpleKeepFileOpen && !EnableFileDeleteSimpleMonitor)); + if (mustWatchArchiving || mustWatchActiveFile) + { + _fileAppenderCache.CheckCloseAppenders += AutoCloseAppendersAfterArchive; // Activates FileSystemWatcher + } + + if (mustWatchArchiving) + { + string archiveFilePattern = GetArchiveFileNamePattern(fileName, logEvent); + var fileArchiveStyle = !string.IsNullOrEmpty(archiveFilePattern) ? GetFileArchiveHelper(archiveFilePattern) : null; + string fileNameMask = fileArchiveStyle != null ? _fileArchiveHelper.GenerateFileNameMask(archiveFilePattern) : string.Empty; + string directoryMask = !string.IsNullOrEmpty(fileNameMask) ? Path.Combine(Path.GetDirectoryName(archiveFilePattern), fileNameMask) : string.Empty; + _fileAppenderCache.ArchiveFilePatternToWatch = directoryMask; + } + else + { + _fileAppenderCache.ArchiveFilePatternToWatch = null; + } + } + + /// + /// Removes records of initialized files that have not been + /// accessed in the last two days. + /// + /// + /// Files are marked 'initialized' for the purpose of writing footers when the logging finishes. + /// + public void CleanupInitializedFiles() + { + try + { + CleanupInitializedFiles(TimeSource.Current.Time.AddDays(-InitializedFilesCleanupPeriod)); + } + catch (Exception exception) + { + InternalLogger.Error(exception, "{0}: Exception in CleanupInitializedFiles", this); + } + } + + /// + /// Removes records of initialized files that have not been + /// accessed after the specified date. + /// + /// The cleanup threshold. + /// + /// Files are marked 'initialized' for the purpose of writing footers when the logging finishes. + /// + public void CleanupInitializedFiles(DateTime cleanupThreshold) + { + InternalLogger.Trace("{0}: CleanupInitializedFiles with cleanupThreshold {1}", this, cleanupThreshold); + + List filesToFinalize = null; + + // Select the files require to be finalized. + foreach (var file in _initializedFiles) + { + if (file.Value < cleanupThreshold) + { + if (filesToFinalize is null) + { + filesToFinalize = new List(); + } + filesToFinalize.Add(file.Key); + } + } + + // Finalize the files. + if (filesToFinalize != null) + { + foreach (string fileName in filesToFinalize) + { + FinalizeFile(fileName); + } + } + + InternalLogger.Trace("{0}: CleanupInitializedFiles Completed and finalized {0} files", this, filesToFinalize?.Count ?? 0); + } + + /// + /// Flushes all pending file operations. + /// + /// The asynchronous continuation. + /// + /// The timeout parameter is ignored, because file APIs don't provide + /// the needed functionality. + /// + protected override void FlushAsync(AsyncContinuation asyncContinuation) + { + try + { + InternalLogger.Trace("{0}: FlushAsync", this); + _fileAppenderCache.FlushAppenders(); + asyncContinuation(null); + InternalLogger.Trace("{0}: FlushAsync Done", this); + } + catch (Exception exception) + { + if (LogManager.ThrowExceptions) + { + throw; + } + + asyncContinuation(exception); + } + } + + /// + /// Returns the suitable appender factory ( ) to be used to generate the file + /// appenders associated with the instance. + /// + /// The type of the file appender factory returned depends on the values of various properties. + /// + /// suitable for this instance. + private IFileAppenderFactory GetFileAppenderFactory() + { + if (DiscardAll) + { + return NullAppender.TheFactory; + } + else if (!KeepFileOpen) + { + return RetryingMultiProcessFileAppender.TheFactory; + } + else if (ConcurrentWrites) + { + if (!ForceMutexConcurrentWrites) + { +#if MONO + if (PlatformDetector.IsUnix) + { + return UnixMultiProcessFileAppender.TheFactory; + } +#elif NETFRAMEWORK + if (PlatformDetector.IsWin32 && !PlatformDetector.IsMono) + { + return WindowsMultiProcessFileAppender.TheFactory; + } +#endif + } + + if (MutexDetector.SupportsSharableMutex) + { + return MutexMultiProcessFileAppender.TheFactory; + } + else + { + return RetryingMultiProcessFileAppender.TheFactory; + } + } + else if (IsArchivingEnabled) + return CountingSingleProcessFileAppender.TheFactory; + else + return SingleProcessFileAppender.TheFactory; + } + + private bool IsArchivingEnabled => ArchiveAboveSize != ArchiveAboveSizeDisabled || ArchiveEvery != FileArchiveEveryPeriod.None; + + private bool IsSimpleKeepFileOpen => KeepFileOpen && !ConcurrentWrites && !ReplaceFileContentsOnEachWrite; + + private bool EnableFileDeleteSimpleMonitor => EnableFileDelete && IsSimpleKeepFileOpen +#if NETFRAMEWORK + && !PlatformDetector.IsWin32 +#endif + ; + + bool ICreateFileParameters.EnableFileDeleteSimpleMonitor => EnableFileDeleteSimpleMonitor; + + /// + /// Initializes file logging by creating data structures that + /// enable efficient multi-file logging. + /// + protected override void InitializeTarget() + { + base.InitializeTarget(); + + if (FileName is null || ReferenceEquals(FileName, Layout.Empty)) + throw new NLogConfigurationException("FileTarget FileName-property must be assigned. FileName is needed for file writing."); + + var appenderFactory = GetFileAppenderFactory(); + if (InternalLogger.IsTraceEnabled) + { + InternalLogger.Trace("{0}: Using appenderFactory: {1}", this, appenderFactory.GetType()); + } + + _fileAppenderCache = new FileAppenderCache(OpenFileCacheSize, appenderFactory, this); + + if ((OpenFileCacheSize > 0 || EnableFileDelete) && (OpenFileCacheTimeout > 0 || OpenFileFlushTimeout > 0)) + { + int openFileAutoTimeoutSecs = (OpenFileCacheTimeout > 0 && OpenFileFlushTimeout > 0) ? Math.Min(OpenFileCacheTimeout, OpenFileFlushTimeout) : Math.Max(OpenFileCacheTimeout, OpenFileFlushTimeout); + InternalLogger.Trace("{0}: Start autoClosingTimer", this); + _autoClosingTimer = new Timer( + (state) => AutoClosingTimerCallback(this, EventArgs.Empty), + null, + openFileAutoTimeoutSecs * 1000, + openFileAutoTimeoutSecs * 1000); + } + } + + /// + /// Closes the file(s) opened for writing. + /// + protected override void CloseTarget() + { + base.CloseTarget(); + + foreach (string fileName in new List(_initializedFiles.Keys)) + { + FinalizeFile(fileName); + } + + _fileArchiveHelper = null; + + var currentTimer = _autoClosingTimer; + if (currentTimer != null) + { + InternalLogger.Trace("{0}: Stop autoClosingTimer", this); + _autoClosingTimer = null; + currentTimer.Change(Timeout.Infinite, Timeout.Infinite); + currentTimer.Dispose(); + } + + _fileAppenderCache.CloseAppenders("Dispose"); + _fileAppenderCache.Dispose(); + } + + private void ResetFileAppenders(string reason) + { + _fileArchiveHelper = null; + if (IsInitialized) + { + _fileAppenderCache.CloseAppenders(reason); + _initializedFiles.Clear(); + } + } + + private readonly ReusableBuilderCreator _reusableLayoutBuilder = new ReusableBuilderCreator(); + private readonly ReusableStreamCreator _reusableFileWriteStream = new ReusableStreamCreator(); + private readonly ReusableStreamCreator _reusableBatchFileWriteStream = new ReusableStreamCreator(true); + private readonly ReusableBufferCreator _reusableEncodingBuffer = new ReusableBufferCreator(1024); + + /// + /// Writes the specified logging event to a file specified in the FileName + /// parameter. + /// + /// The logging event. + protected override void Write(LogEventInfo logEvent) + { + var logFileName = GetFullFileName(logEvent); + if (string.IsNullOrEmpty(logFileName)) + { + throw new ArgumentException("The path is not of a legal form."); + } + + using (var targetStream = _reusableBatchFileWriteStream.Allocate()) + { + using (var targetBuilder = _reusableLayoutBuilder.Allocate()) + using (var targetBuffer = _reusableEncodingBuffer.Allocate()) + { + RenderFormattedMessageToStream(logEvent, targetBuilder.Result, targetBuffer.Result, targetStream.Result); + } + + ProcessLogEvent(logEvent, logFileName, new ArraySegment(targetStream.Result.GetBuffer(), 0, (int)targetStream.Result.Length)); + } + } + + /// + /// Get full filename (=absolute) and cleaned if needed. + /// + /// + /// + internal string GetFullFileName(LogEventInfo logEvent) + { + if (_fullFileName is null) + return null; + + if (_fullFileName.IsFixedFilePath) + return _fullFileName.Render(logEvent); + + using (var targetBuilder = _reusableLayoutBuilder.Allocate()) + { + return _fullFileName.RenderWithBuilder(logEvent, targetBuilder.Result); + } + } + + SortHelpers.KeySelector _getFullFileNameDelegate; + + /// + /// Writes the specified array of logging events to a file specified in the FileName + /// parameter. + /// + /// An array of objects. + /// + /// This function makes use of the fact that the events are batched by sorting + /// the requests by filename. This optimizes the number of open/close calls + /// and can help improve performance. + /// + protected override void Write(IList logEvents) + { + if (_getFullFileNameDelegate is null) + _getFullFileNameDelegate = c => GetFullFileName(c.LogEvent); + + var buckets = logEvents.BucketSort(_getFullFileNameDelegate); + + using (var reusableStream = _reusableBatchFileWriteStream.Allocate()) + { + var ms = reusableStream.Result ?? new MemoryStream(); + + foreach (var bucket in buckets) + { + int bucketCount = bucket.Value.Count; + if (bucketCount <= 0) + continue; + + string fileName = bucket.Key; + if (string.IsNullOrEmpty(fileName)) + { + InternalLogger.Warn("{0}: FileName Layout returned empty string. The path is not of a legal form.", this); + var emptyPathException = new ArgumentException("The path is not of a legal form."); + for (int i = 0; i < bucketCount; ++i) + { + bucket.Value[i].Continuation(emptyPathException); + } + continue; + } + + int currentIndex = 0; + while (currentIndex < bucketCount) + { + ms.Position = 0; + ms.SetLength(0); + + var written = WriteToMemoryStream(bucket.Value, currentIndex, ms); + AppendMemoryStreamToFile(fileName, bucket.Value[currentIndex].LogEvent, ms, out var lastException); + for (int i = 0; i < written; ++i) + { + bucket.Value[currentIndex++].Continuation(lastException); + } + } + } + } + } + + private int WriteToMemoryStream(IList logEvents, int startIndex, MemoryStream ms) + { + long maxBufferSize = BufferSize * 100; // Max Buffer Default = 30 KiloByte * 100 = 3 MegaByte + + using (var targetStream = _reusableFileWriteStream.Allocate()) + using (var targetBuilder = _reusableLayoutBuilder.Allocate()) + using (var targetBuffer = _reusableEncodingBuffer.Allocate()) + { + var formatBuilder = targetBuilder.Result; + var transformBuffer = targetBuffer.Result; + var encodingStream = targetStream.Result; + + for (int i = startIndex; i < logEvents.Count; ++i) + { + // For some CPU's then it is faster to write to a small MemoryStream, and then copy to the larger one + encodingStream.Position = 0; + encodingStream.SetLength(0); + formatBuilder.Length = 0; + + AsyncLogEventInfo ev = logEvents[i]; + RenderFormattedMessageToStream(ev.LogEvent, formatBuilder, transformBuffer, encodingStream); + ms.Write(encodingStream.GetBuffer(), 0, (int)encodingStream.Length); + if (ms.Length > maxBufferSize && !ReplaceFileContentsOnEachWrite) + return i - startIndex + 1; // Max Chunk Size Limit to avoid out-of-memory issues + } + } + + return logEvents.Count - startIndex; + } + + private void ProcessLogEvent(LogEventInfo logEvent, string fileName, ArraySegment bytesToWrite) + { + DateTime previousLogEventTimestamp = InitializeFile(fileName, logEvent); + bool initializedNewFile = previousLogEventTimestamp == DateTime.MinValue; + if (initializedNewFile && fileName == _previousLogFileName && _previousLogEventTimestamp.HasValue) + previousLogEventTimestamp = _previousLogEventTimestamp.Value; + + bool archiveOccurred = TryArchiveFile(fileName, logEvent, bytesToWrite.Count, previousLogEventTimestamp, initializedNewFile); + if (archiveOccurred) + initializedNewFile = InitializeFile(fileName, logEvent) == DateTime.MinValue || initializedNewFile; + + if (ReplaceFileContentsOnEachWrite) + { + ReplaceFileContent(fileName, bytesToWrite, true); + } + else + { + WriteToFile(fileName, bytesToWrite, initializedNewFile); + } + + _previousLogFileName = fileName; + _previousLogEventTimestamp = logEvent.TimeStamp; + } + + /// + /// Obsolete and replaced by with NLog v5. + /// Formats the log event for write. + /// + /// The log event to be formatted. + /// A string representation of the log event. + [Obsolete("No longer used and replaced by RenderFormattedMessage. Marked obsolete on NLog 5.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + protected virtual string GetFormattedMessage(LogEventInfo logEvent) + { + return Layout.Render(logEvent); + } + + /// + /// Obsolete and replaced by with NLog v5. + /// Gets the bytes to be written to the file. + /// + /// Log event. + /// Array of bytes that are ready to be written. + [Obsolete("No longer used and replaced by RenderFormattedMessage. Marked obsolete on NLog 5.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + protected virtual byte[] GetBytesToWrite(LogEventInfo logEvent) + { + string text = GetFormattedMessage(logEvent); + int textBytesCount = Encoding.GetByteCount(text); + int newLineBytesCount = Encoding.GetByteCount(NewLineChars); + byte[] bytes = new byte[textBytesCount + newLineBytesCount]; + Encoding.GetBytes(text, 0, text.Length, bytes, 0); + Encoding.GetBytes(NewLineChars, 0, NewLineChars.Length, bytes, textBytesCount); + return TransformBytes(bytes); + } + + /// + /// Obsolete and replaced by with NLog v5. + /// Modifies the specified byte array before it gets sent to a file. + /// + /// The byte array. + /// The modified byte array. The function can do the modification in-place. + [Obsolete("No longer used and replaced by TransformStream. Marked obsolete on NLog 5.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + protected virtual byte[] TransformBytes(byte[] value) + { + return value; + } + + /// + /// Gets the bytes to be written to the file. + /// + /// The log event to be formatted. + /// to help format log event. + /// Optional temporary char-array to help format log event. + /// Destination for the encoded result. + protected virtual void RenderFormattedMessageToStream(LogEventInfo logEvent, StringBuilder formatBuilder, char[] transformBuffer, MemoryStream streamTarget) + { + RenderFormattedMessage(logEvent, formatBuilder); + formatBuilder.Append(NewLineChars); + TransformBuilderToStream(logEvent, formatBuilder, transformBuffer, streamTarget); + } + + /// + /// Formats the log event for write. + /// + /// The log event to be formatted. + /// for the result. + protected virtual void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target) + { + Layout.Render(logEvent, target); + } + + private void TransformBuilderToStream(LogEventInfo logEvent, StringBuilder builder, char[] transformBuffer, MemoryStream workStream) + { + builder.CopyToStream(workStream, Encoding, transformBuffer); + TransformStream(logEvent, workStream); + } + + /// + /// Modifies the specified byte array before it gets sent to a file. + /// + /// The LogEvent being written + /// The byte array. + protected virtual void TransformStream(LogEventInfo logEvent, MemoryStream stream) + { + } + + private void AppendMemoryStreamToFile(string currentFileName, LogEventInfo firstLogEvent, MemoryStream ms, out Exception lastException) + { + try + { + ArraySegment bytes = new ArraySegment(ms.GetBuffer(), 0, (int)ms.Length); + ProcessLogEvent(firstLogEvent, currentFileName, bytes); + lastException = null; + } + catch (Exception exception) + { + if (LogManager.ThrowExceptions) + throw; + + lastException = exception; + } + } + + /// + /// Archives fileName to archiveFileName. + /// + /// File name to be archived. + /// Name of the archive file. + private void ArchiveFile(string fileName, string archiveFileName) + { + string archiveFolderPath = Path.GetDirectoryName(archiveFileName); + if (archiveFolderPath != null && !Directory.Exists(archiveFolderPath)) + Directory.CreateDirectory(archiveFolderPath); + + if (string.Equals(fileName, archiveFileName, StringComparison.OrdinalIgnoreCase)) + { + InternalLogger.Info("{0}: Archiving {1} skipped as ArchiveFileName equals FileName", this, fileName); + } + else if (EnableArchiveFileCompression) + { + InternalLogger.Info("{0}: Archiving {1} to compressed {2}", this, fileName, archiveFileName); + if (File.Exists(archiveFileName)) + { + InternalLogger.Warn("{0}: Failed archiving because compressed file already exists: {1}", this, archiveFileName); + } + else + { + ArchiveFileCompress(fileName, archiveFileName); + } + } + else + { + InternalLogger.Info("{0}: Archiving {1} to {2}", this, fileName, archiveFileName); + if (File.Exists(archiveFileName)) + { + ArchiveFileAppendExisting(fileName, archiveFileName); + } + else + { + ArchiveFileMove(fileName, archiveFileName); + } + } + } + + private void ArchiveFileCompress(string fileName, string archiveFileName) + { + int fileCompressRetryCount = ConcurrentWrites ? ConcurrentWriteAttempts : 2; + for (int i = 1; i <= fileCompressRetryCount; ++i) + { + try + { + if (FileCompressor is IArchiveFileCompressor archiveFileCompressor) + { + string entryName = (ArchiveNumbering != ArchiveNumberingMode.Rolling) ? (Path.GetFileNameWithoutExtension(archiveFileName) + Path.GetExtension(fileName)) : Path.GetFileName(fileName); + archiveFileCompressor.CompressFile(fileName, archiveFileName, entryName); + } + else + { + FileCompressor.CompressFile(fileName, archiveFileName); + } + + break; // Success + } + catch (DirectoryNotFoundException) + { + throw; // Skip retry when directory does not exist + } + catch (FileNotFoundException) + { + throw; // Skip retry when file does not exist + } + catch (IOException ex) + { + if (i == fileCompressRetryCount) + throw; + + if (File.Exists(archiveFileName)) + throw; + + int sleepTimeMs = i * 50; + InternalLogger.Warn("{0}: Archiving Attempt #{1} to compress {2} to {3} failed - {4} {5}. Sleeping for {6}ms", this, i, fileName, archiveFileName, ex.GetType(), ex.Message, sleepTimeMs); + Thread.Sleep(TimeSpan.FromMilliseconds(sleepTimeMs)); + } + } + + DeleteAndWaitForFileDelete(fileName); + } + + private void ArchiveFileAppendExisting(string fileName, string archiveFileName) + { + //todo handle double footer + InternalLogger.Info("{0}: Already exists, append to {1}", this, archiveFileName); + + //copy to archive file. + var fileShare = FileShare.ReadWrite; + if (EnableFileDelete) + { + fileShare |= FileShare.Delete; + } + + using (FileStream fileStream = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, fileShare)) + using (FileStream archiveFileStream = File.Open(archiveFileName, FileMode.Append)) + { + fileStream.CopyAndSkipBom(archiveFileStream, Encoding); + //clear old content + fileStream.SetLength(0); + + if (EnableFileDelete && !DeleteOldArchiveFile(fileName)) + { + // Attempt to delete file to reset File-Creation-Time (Delete under file-lock) + fileShare &= ~FileShare.Delete; // Retry after having released file-lock + } + + fileStream.Close(); // This flushes the content, too. +#if !NET35 + archiveFileStream.Flush(true); +#else + archiveFileStream.Flush(); +#endif + } + + if ((fileShare & FileShare.Delete) == FileShare.None) + { + DeleteOldArchiveFile(fileName); // Attempt to delete file to reset File-Creation-Time + } + } + + private void ArchiveFileMove(string fileName, string archiveFileName) + { + try + { + InternalLogger.Debug("{0}: Move file from '{1}' to '{2}'", this, fileName, archiveFileName); + File.Move(fileName, archiveFileName); + } + catch (IOException ex) + { + if (IsSimpleKeepFileOpen) + throw; // No need to retry, when only single process access + + if (!EnableFileDelete && KeepFileOpen) + throw; // No need to retry when file delete has been disabled + + if (ConcurrentWrites && !MutexDetector.SupportsSharableMutex) + throw; // No need to retry when not having a real archive mutex to protect us + + // It is possible to move a file while other processes has open file-handles. + // Unless the other process is actively writing, then the file move might fail. + // We are already holding the archive-mutex, so lets retry if things are stable + InternalLogger.Warn(ex, "{0}: Archiving failed. Checking for retry move of {1} to {2}.", this, fileName, archiveFileName); + if (!File.Exists(fileName) || File.Exists(archiveFileName)) + throw; + + Thread.Sleep(TimeSpan.FromMilliseconds(50)); + + if (!File.Exists(fileName) || File.Exists(archiveFileName)) + throw; + + InternalLogger.Debug("{0}: Archiving retrying move of {1} to {2}.", this, fileName, archiveFileName); + File.Move(fileName, archiveFileName); + } + } + + private bool DeleteOldArchiveFile(string fileName) + { + try + { + InternalLogger.Info("{0}: Deleting old archive file: '{1}'.", this, fileName); + CloseInvalidFileHandle(fileName); + File.Delete(fileName); + return true; + } + catch (DirectoryNotFoundException exception) + { + //never rethrow this, as this isn't an exceptional case. + InternalLogger.Debug(exception, "{0}: Failed to delete old log file '{1}' as directory is missing.", this, fileName); + return false; + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "{0}: Failed to delete old archive file: '{1}'.", this, fileName); + if (LogManager.ThrowExceptions) + { + throw; + } + + return false; + } + } + + private void DeleteAndWaitForFileDelete(string fileName) + { + try + { + InternalLogger.Trace("{0}: Waiting for file delete of '{1}' for 12 sec", this, fileName); + var originalFileCreationTime = (new FileInfo(fileName)).CreationTime; + if (DeleteOldArchiveFile(fileName) && File.Exists(fileName)) + { + FileInfo currentFileInfo; + for (int i = 0; i < 120; ++i) + { + Thread.Sleep(TimeSpan.FromMilliseconds(100)); + currentFileInfo = new FileInfo(fileName); + if (!currentFileInfo.Exists || currentFileInfo.CreationTime != originalFileCreationTime) + return; + } + + InternalLogger.Warn("{0}: Timeout while deleting old archive file: '{1}'.", this, fileName); + } + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "{0}: Failed to delete old archive file: '{1}'.", this, fileName); + if (LogManager.ThrowExceptions) + throw; + } + } + + /// + /// Gets the correct formatting to be used based on the value of for converting values which will be inserting into file + /// names during archiving. + /// + /// This value will be computed only when a empty value or is passed into + /// + /// Date format to used irrespectively of value. + /// Formatting for dates. + private string GetArchiveDateFormatString(string defaultFormat) + { + // If archiveDateFormat is not set in the config file, use a default + // date format string based on the archive period. + if (!string.IsNullOrEmpty(defaultFormat)) + return defaultFormat; + + switch (ArchiveEvery) + { + case FileArchiveEveryPeriod.Year: + return "yyyy"; + case FileArchiveEveryPeriod.Month: + return "yyyyMM"; + case FileArchiveEveryPeriod.Hour: + return "yyyyMMddHH"; + case FileArchiveEveryPeriod.Minute: + return "yyyyMMddHHmm"; + default: + return "yyyyMMdd"; // Also for Weekdays + } + } + + private DateTime? GetArchiveDate(string fileName, LogEventInfo logEvent, DateTime previousLogEventTimestamp) + { + // Using File LastModified to handle FileArchivePeriod.Month (where file creation time is one month ago) + var fileLastModifiedUtc = _fileAppenderCache.GetFileLastWriteTimeUtc(fileName); + + InternalLogger.Trace("{0}: Calculating archive date. File-LastModifiedUtc: {1}; Previous LogEvent-TimeStamp: {2}", this, fileLastModifiedUtc, previousLogEventTimestamp); + if (!fileLastModifiedUtc.HasValue) + { + if (previousLogEventTimestamp == DateTime.MinValue) + { + InternalLogger.Info("{0}: Unable to acquire useful timestamp to archive file: {1}", this, fileName); + return null; + } + return previousLogEventTimestamp; + } + + var lastWriteTimeSource = Time.TimeSource.Current.FromSystemTime(fileLastModifiedUtc.Value); + if (previousLogEventTimestamp != DateTime.MinValue) + { + if (previousLogEventTimestamp > lastWriteTimeSource) + { + InternalLogger.Trace("{0}: Using previous LogEvent-TimeStamp {1}, because more recent than File-LastModified {2}", this, previousLogEventTimestamp, lastWriteTimeSource); + return previousLogEventTimestamp; + } + + if (PreviousLogOverlappedPeriod(logEvent, previousLogEventTimestamp, lastWriteTimeSource)) + { + InternalLogger.Trace("{0}: Using previous LogEvent-TimeStamp {1}, because archive period is overlapping with File-LastModified {2}", this, previousLogEventTimestamp, lastWriteTimeSource); + return previousLogEventTimestamp; + } + + if (!AutoFlush && IsSimpleKeepFileOpen && previousLogEventTimestamp < lastWriteTimeSource) + { + InternalLogger.Trace("{0}: Using previous LogEvent-TimeStamp {1}, because AutoFlush=false affects File-LastModified {2}", this, previousLogEventTimestamp, lastWriteTimeSource); + return previousLogEventTimestamp; + } + } + + InternalLogger.Trace("{0}: Using last write time: {1}", this, lastWriteTimeSource); + return lastWriteTimeSource; + } + + private bool PreviousLogOverlappedPeriod(LogEventInfo logEvent, DateTime previousLogEventTimestamp, DateTime lastFileWrite) + { + string formatString = GetArchiveDateFormatString(string.Empty); + string lastWriteTimeString = lastFileWrite.ToString(formatString, CultureInfo.InvariantCulture); + string logEventTimeString = logEvent.TimeStamp.ToString(formatString, CultureInfo.InvariantCulture); + + if (lastWriteTimeString != logEventTimeString) + return false; + + DateTime? periodAfterPreviousLogEventTime = CalculateNextArchiveEventTime(previousLogEventTimestamp); + if (!periodAfterPreviousLogEventTime.HasValue) + return false; + + string periodAfterPreviousLogEventTimeString = periodAfterPreviousLogEventTime.Value.ToString(formatString, CultureInfo.InvariantCulture); + return lastWriteTimeString == periodAfterPreviousLogEventTimeString; + } + + DateTime? CalculateNextArchiveEventTime(DateTime timestamp) + { + switch (ArchiveEvery) + { + case FileArchiveEveryPeriod.Year: + return timestamp.AddYears(1); + case FileArchiveEveryPeriod.Month: + return timestamp.AddMonths(1); + case FileArchiveEveryPeriod.Day: + return timestamp.AddDays(1); + case FileArchiveEveryPeriod.Hour: + return timestamp.AddHours(1); + case FileArchiveEveryPeriod.Minute: + return timestamp.AddMinutes(1); + case FileArchiveEveryPeriod.Sunday: + return CalculateNextWeekday(timestamp, DayOfWeek.Sunday); + case FileArchiveEveryPeriod.Monday: + return CalculateNextWeekday(timestamp, DayOfWeek.Monday); + case FileArchiveEveryPeriod.Tuesday: + return CalculateNextWeekday(timestamp, DayOfWeek.Tuesday); + case FileArchiveEveryPeriod.Wednesday: + return CalculateNextWeekday(timestamp, DayOfWeek.Wednesday); + case FileArchiveEveryPeriod.Thursday: + return CalculateNextWeekday(timestamp, DayOfWeek.Thursday); + case FileArchiveEveryPeriod.Friday: + return CalculateNextWeekday(timestamp, DayOfWeek.Friday); + case FileArchiveEveryPeriod.Saturday: + return CalculateNextWeekday(timestamp, DayOfWeek.Saturday); + default: + return null; + } + } + + /// + /// Calculate the DateTime of the requested day of the week. + /// + /// The DateTime of the previous log event. + /// The next occurring day of the week to return a DateTime for. + /// The DateTime of the next occurring dayOfWeek. + /// For example: if previousLogEventTimestamp is Thursday 2017-03-02 and dayOfWeek is Sunday, this will return + /// Sunday 2017-03-05. If dayOfWeek is Thursday, this will return *next* Thursday 2017-03-09. + public static DateTime CalculateNextWeekday(DateTime previousLogEventTimestamp, DayOfWeek dayOfWeek) + { + // Shamelessly taken from https://stackoverflow.com/a/7611480/1354930 + int start = (int)previousLogEventTimestamp.DayOfWeek; + int target = (int)dayOfWeek; + if (target <= start) + target += 7; + return previousLogEventTimestamp.AddDays(target - start); + } + + /// + /// Invokes the archiving process after determining when and which type of archiving is required. + /// + /// File name to be checked and archived. + /// Log event that the instance is currently processing. + /// The DateTime of the previous log event for this file. + /// File has just been opened. + private void DoAutoArchive(string fileName, LogEventInfo eventInfo, DateTime previousLogEventTimestamp, bool initializedNewFile) + { + InternalLogger.Debug("{0}: Do archive file: '{1}'", this, fileName); + var fileInfo = new FileInfo(fileName); + if (!fileInfo.Exists) + { + CloseInvalidFileHandle(fileName); // Close possible stale file handles + return; + } + + string archiveFilePattern = GetArchiveFileNamePattern(fileName, eventInfo); + if (string.IsNullOrEmpty(archiveFilePattern)) + { + InternalLogger.Warn("{0}: Skip auto archive because archiveFilePattern is blank", this); + return; + } + + DateTime? archiveDate = GetArchiveDate(fileName, eventInfo, previousLogEventTimestamp); + + var archiveFileName = GenerateArchiveFileNameAfterCleanup(fileName, fileInfo, archiveFilePattern, archiveDate, initializedNewFile); + if (!string.IsNullOrEmpty(archiveFileName)) + { + ArchiveFile(fileInfo.FullName, archiveFileName); + } + } + + private string GenerateArchiveFileNameAfterCleanup(string fileName, FileInfo fileInfo, string archiveFilePattern, DateTime? archiveDate, bool initializedNewFile) + { + InternalLogger.Trace("{0}: Archive pattern '{1}'", this, archiveFilePattern); + + var fileArchiveStyle = GetFileArchiveHelper(archiveFilePattern); + var existingArchiveFiles = fileArchiveStyle.GetExistingArchiveFiles(archiveFilePattern); + + if (MaxArchiveFiles == 1) + { + InternalLogger.Trace("{0}: MaxArchiveFiles = 1", this); + // Perform archive cleanup before generating the next filename, + // as next archive-filename can be affected by existing files. + for (int i = existingArchiveFiles.Count - 1; i >= 0; i--) + { + var oldArchiveFile = existingArchiveFiles[i]; + if (!string.Equals(oldArchiveFile.FileName, fileInfo.FullName, StringComparison.OrdinalIgnoreCase)) + { + DeleteOldArchiveFile(oldArchiveFile.FileName); + existingArchiveFiles.RemoveAt(i); + } + } + + if (initializedNewFile && string.Equals(Path.GetDirectoryName(archiveFilePattern), fileInfo.DirectoryName, StringComparison.OrdinalIgnoreCase)) + { + DeleteOldArchiveFile(fileName); + return null; + } + } + + var archiveFileName = archiveDate.HasValue ? fileArchiveStyle.GenerateArchiveFileName(archiveFilePattern, archiveDate.Value, existingArchiveFiles) : null; + if (archiveFileName is null) + return null; + + if (!initializedNewFile) + { + FinalizeFile(fileName, isArchiving: true); + } + + if (existingArchiveFiles.Count > 0) + { + CleanupOldArchiveFiles(fileInfo, archiveFilePattern, existingArchiveFiles, archiveFileName); + } + + return archiveFileName.FileName; + } + + private void CleanupOldArchiveFiles(FileInfo currentFile, string archiveFilePattern, List existingArchiveFiles, DateAndSequenceArchive newArchiveFile = null) + { + var fileArchiveStyle = GetFileArchiveHelper(archiveFilePattern); + + if (fileArchiveStyle.IsArchiveCleanupEnabled) + { + if (currentFile != null) + ExcludeActiveFileFromOldArchiveFiles(currentFile, existingArchiveFiles); + + if (newArchiveFile != null) + existingArchiveFiles.Add(newArchiveFile); + + var cleanupArchiveFiles = fileArchiveStyle.CheckArchiveCleanup(archiveFilePattern, existingArchiveFiles, MaxArchiveFiles, MaxArchiveDays); + foreach (var oldArchiveFile in cleanupArchiveFiles) + { + DeleteOldArchiveFile(oldArchiveFile.FileName); + } + } + } + + private static void ExcludeActiveFileFromOldArchiveFiles(FileInfo currentFile, List existingArchiveFiles) + { + if (existingArchiveFiles.Count > 0) + { + var archiveDirectory = Path.GetDirectoryName(existingArchiveFiles[0].FileName); + if (string.Equals(archiveDirectory, currentFile.DirectoryName, StringComparison.OrdinalIgnoreCase)) + { + // Extra handling when archive-directory is the same as logging-directory + for (int i = 0; i < existingArchiveFiles.Count; ++i) + { + if (string.Equals(existingArchiveFiles[i].FileName, currentFile.FullName, StringComparison.OrdinalIgnoreCase)) + { + existingArchiveFiles.RemoveAt(i); + break; + } + } + } + } + } + + /// + /// Gets the pattern that archive files will match + /// + /// Filename of the log file + /// Log event that the instance is currently processing. + /// A string with a pattern that will match the archive filenames + private string GetArchiveFileNamePattern(string fileName, LogEventInfo eventInfo) + { + if (_fullArchiveFileName is null) + { + if (EnableArchiveFileCompression) + return Path.ChangeExtension(fileName, ".zip"); + else + return fileName; + } + else + { + //The archive file name is given. There are two possibilities + //(1) User supplied the Filename with pattern + //(2) User supplied the normal filename + string archiveFileName = _fullArchiveFileName.Render(eventInfo); + return archiveFileName; + } + } + + /// + /// Archives the file if it should be archived. + /// + /// The file name to check for. + /// Log event that the instance is currently processing. + /// The size in bytes of the next chunk of data to be written in the file. + /// The DateTime of the previous log event for this file. + /// File has just been opened. + /// when archive operation of the file was completed (by this target or a concurrent target) + private bool TryArchiveFile(string fileName, LogEventInfo ev, int upcomingWriteSize, DateTime previousLogEventTimestamp, bool initializedNewFile) + { + if (!IsArchivingEnabled) + return false; + + string archiveFile = string.Empty; + + BaseFileAppender archivedAppender = null; + + try + { + archiveFile = GetArchiveFileName(fileName, ev, upcomingWriteSize, previousLogEventTimestamp, initializedNewFile); + if (!string.IsNullOrEmpty(archiveFile)) + { + archivedAppender = TryCloseFileAppenderBeforeArchive(fileName, archiveFile); + } + + // Closes all file handles if any archive operation has been detected by file-watcher + _fileAppenderCache.InvalidateAppendersForArchivedFiles(); + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "{0}: Failed to check archive for file '{1}'.", this, fileName); + if (LogManager.ThrowExceptions) + throw; + } + + if (string.IsNullOrEmpty(archiveFile)) + return false; + + try + { + try + { + if (archivedAppender is BaseMutexFileAppender mutexFileAppender && mutexFileAppender.ArchiveMutex != null) + { + mutexFileAppender.ArchiveMutex.WaitOne(); + } + else if (!IsSimpleKeepFileOpen) + { + InternalLogger.Debug("{0}: Archive mutex not available for file '{1}'", this, archiveFile); + } + } + catch (AbandonedMutexException) + { + // ignore the exception, another process was killed without properly releasing the mutex + // the mutex has been acquired, so proceed to writing + // See: https://msdn.microsoft.com/en-us/library/system.threading.abandonedmutexexception.aspx + } + + ArchiveFileAfterCloseFileAppender(archivedAppender, archiveFile, ev, upcomingWriteSize, previousLogEventTimestamp); + return true; + } + finally + { + if (archivedAppender is BaseMutexFileAppender mutexFileAppender) + mutexFileAppender.ArchiveMutex?.ReleaseMutex(); + + archivedAppender?.Dispose(); // Dispose of Archive Mutex + } + } + + /// + /// Closes any active file-appenders that matches the input filenames. + /// File-appender is requested to invalidate/close its filehandle, but keeping its archive-mutex alive + /// + private BaseFileAppender TryCloseFileAppenderBeforeArchive(string fileName, string archiveFile) + { + InternalLogger.Trace("{0}: Archive attempt for file '{1}'", this, archiveFile); + BaseFileAppender archivedAppender = _fileAppenderCache.InvalidateAppender(fileName); + if (fileName != archiveFile) + { + var fileAppender = _fileAppenderCache.InvalidateAppender(archiveFile); + archivedAppender = archivedAppender ?? fileAppender; + } + + if (!string.IsNullOrEmpty(_previousLogFileName) && _previousLogFileName != archiveFile && _previousLogFileName != fileName) + { + var fileAppender = _fileAppenderCache.InvalidateAppender(_previousLogFileName); + archivedAppender = archivedAppender ?? fileAppender; + } + + return archivedAppender; + } + + private void ArchiveFileAfterCloseFileAppender(BaseFileAppender archivedAppender, string archiveFile, LogEventInfo ev, int upcomingWriteSize, DateTime previousLogEventTimestamp) + { + try + { + DateTime fallbackFileCreationTimeSource = previousLogEventTimestamp; + + if (archivedAppender != null && IsSimpleKeepFileOpen) + { + var fileCreationTimeUtc = archivedAppender.GetFileCreationTimeUtc(); + if (fileCreationTimeUtc > DateTime.MinValue) + { + var fileCreationTimeSource = Time.TimeSource.Current.FromSystemTime(fileCreationTimeUtc.Value); + if (fileCreationTimeSource < fallbackFileCreationTimeSource || fallbackFileCreationTimeSource == DateTime.MinValue) + { + fallbackFileCreationTimeSource = fileCreationTimeSource; + } + } + } + + // Check again if archive is needed. We could have been raced by another process + var validatedArchiveFile = GetArchiveFileName(archiveFile, ev, upcomingWriteSize, fallbackFileCreationTimeSource, false); + if (string.IsNullOrEmpty(validatedArchiveFile)) + { + InternalLogger.Debug("{0}: Skip archiving '{1}' because no longer necessary", this, archiveFile); + _initializedFiles.Remove(archiveFile); + } + else + { + if (archiveFile != validatedArchiveFile) + { + _initializedFiles.Remove(archiveFile); + archiveFile = validatedArchiveFile; + } + _initializedFiles.Remove(archiveFile); + + DoAutoArchive(archiveFile, ev, previousLogEventTimestamp, false); + } + + if (_previousLogFileName == archiveFile) + { + _previousLogFileName = null; + _previousLogEventTimestamp = null; + } + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "{0}: Failed to archive file '{1}'.", this, archiveFile); + if (LogManager.ThrowExceptions) + throw; + } + } + + /// + /// Indicates if the automatic archiving process should be executed. + /// + /// File name to be written. + /// Log event that the instance is currently processing. + /// The size in bytes of the next chunk of data to be written in the file. + /// The DateTime of the previous log event for this file. + /// File has just been opened. + /// Filename to archive. If null, then nothing to archive. + private string GetArchiveFileName(string fileName, LogEventInfo ev, int upcomingWriteSize, DateTime previousLogEventTimestamp, bool initializedNewFile) + { + fileName = fileName ?? _previousLogFileName; + if (!string.IsNullOrEmpty(fileName)) + { + return GetArchiveFileNameBasedOnFileSize(fileName, upcomingWriteSize, initializedNewFile) ?? + GetArchiveFileNameBasedOnTime(fileName, ev, previousLogEventTimestamp, initializedNewFile); + } + + return null; + } + + /// + /// Returns the correct filename to archive + /// + private string GetPotentialFileForArchiving(string fileName) + { + if (!string.IsNullOrEmpty(fileName)) + { + return fileName; + } + + if (!string.IsNullOrEmpty(_previousLogFileName)) + { + return _previousLogFileName; + } + + return fileName; + } + + /// + /// Gets the file name for archiving, or null if archiving should not occur based on file size. + /// + /// File name to be written. + /// The size in bytes of the next chunk of data to be written in the file. + /// File has just been opened. + /// Filename to archive. If null, then nothing to archive. + private string GetArchiveFileNameBasedOnFileSize(string fileName, int upcomingWriteSize, bool initializedNewFile) + { + if (ArchiveAboveSize == ArchiveAboveSizeDisabled) + { + return null; + } + + var archiveFileName = GetPotentialFileForArchiving(fileName); + if (string.IsNullOrEmpty(archiveFileName)) + { + return null; + } + + //this is an expensive call + var fileLength = _fileAppenderCache.GetFileLength(archiveFileName); + if (!fileLength.HasValue) + { + archiveFileName = TryFallbackToPreviousLogFileName(archiveFileName, initializedNewFile); + if (!string.IsNullOrEmpty(archiveFileName)) + { + upcomingWriteSize = 0; + return GetArchiveFileNameBasedOnFileSize(archiveFileName, upcomingWriteSize, false); + } + else + { + return null; + } + } + + if (archiveFileName != fileName) + { + upcomingWriteSize = 0; // Not going to write to this file + } + + var shouldArchive = (fileLength.Value + upcomingWriteSize) > ArchiveAboveSize; + if (shouldArchive) + { + InternalLogger.Debug("{0}: Start archiving '{1}' because FileSize={2} + {3} is larger than ArchiveAboveSize={4}", this, archiveFileName, fileLength.Value, upcomingWriteSize, ArchiveAboveSize); + return archiveFileName; // Will re-check if archive is still necessary after flush/close file + } + + return null; + } + + /// + /// Check if archive operation should check previous filename, because FileAppenderCache tells us current filename no longer exists + /// + private string TryFallbackToPreviousLogFileName(string archiveFileName, bool initializedNewFile) + { + if (!initializedNewFile && _initializedFiles.Remove(archiveFileName)) + { + // Register current filename needs re-initialization + InternalLogger.Debug("{0}: Invalidate appender as archive file no longer exists: '{1}'", this, archiveFileName); + _fileAppenderCache.InvalidateAppender(archiveFileName)?.Dispose(); + } + + if (!string.IsNullOrEmpty(_previousLogFileName) && !string.Equals(archiveFileName, _previousLogFileName, StringComparison.OrdinalIgnoreCase)) + { + return _previousLogFileName; + } + + return string.Empty; + } + + /// + /// Returns the file name for archiving, or null if archiving should not occur based on date/time. + /// + /// File name to be written. + /// Log event that the instance is currently processing. + /// The DateTime of the previous log event for this file. + /// File has just been opened. + /// Filename to archive. If null, then nothing to archive. + private string GetArchiveFileNameBasedOnTime(string fileName, LogEventInfo logEvent, DateTime previousLogEventTimestamp, bool initializedNewFile) + { + if (ArchiveEvery == FileArchiveEveryPeriod.None) + { + return null; + } + + var archiveFileName = GetPotentialFileForArchiving(fileName); + if (string.IsNullOrEmpty(archiveFileName)) + { + return null; + } + + DateTime? creationTimeSource = TryGetArchiveFileCreationTimeSource(archiveFileName, previousLogEventTimestamp); + if (!creationTimeSource.HasValue) + { + archiveFileName = TryFallbackToPreviousLogFileName(archiveFileName, initializedNewFile); + if (!string.IsNullOrEmpty(archiveFileName)) + { + return GetArchiveFileNameBasedOnTime(archiveFileName, logEvent, previousLogEventTimestamp, false); + } + else + { + return null; + } + } + + DateTime fileCreateTime = TruncateArchiveTime(creationTimeSource.Value, ArchiveEvery); + DateTime logEventTime = TruncateArchiveTime(logEvent.TimeStamp, ArchiveEvery); + if (fileCreateTime != logEventTime) + { + string formatString = GetArchiveDateFormatString(string.Empty); + var validLogEventTime = EnsureValidLogEventTimeStamp(logEvent.TimeStamp, creationTimeSource.Value); + string fileCreated = creationTimeSource.Value.ToString(formatString, CultureInfo.InvariantCulture); + string logEventRecorded = validLogEventTime.ToString(formatString, CultureInfo.InvariantCulture); + var shouldArchive = fileCreated != logEventRecorded; + if (shouldArchive) + { + InternalLogger.Debug("{0}: Start archiving '{1}' because FileCreatedTime='{2}' is older than now '{3}' using ArchiveEvery='{4}'", this, archiveFileName, fileCreated, logEventRecorded, formatString); + return archiveFileName; // Will re-check if archive is still necessary after flush/close file + } + } + + return null; + } + + private DateTime? TryGetArchiveFileCreationTimeSource(string fileName, DateTime previousLogEventTimestamp) + { + // Linux FileSystems doesn't always have file-birth-time, so NLog tries to provide a little help + DateTime? fallbackTimeSourceLinux = (previousLogEventTimestamp != DateTime.MinValue && IsSimpleKeepFileOpen) ? previousLogEventTimestamp : (DateTime?)null; + var creationTimeSource = _fileAppenderCache.GetFileCreationTimeSource(fileName, fallbackTimeSourceLinux); + if (!creationTimeSource.HasValue) + return null; + + if (previousLogEventTimestamp > DateTime.MinValue && previousLogEventTimestamp < creationTimeSource) + { + if (TruncateArchiveTime(previousLogEventTimestamp, FileArchiveEveryPeriod.Minute) < TruncateArchiveTime(creationTimeSource.Value, FileArchiveEveryPeriod.Minute) && PlatformDetector.IsUnix) + { + if (IsSimpleKeepFileOpen) + { + InternalLogger.Debug("{0}: Adjusted file creation time from {1} to {2}. Linux FileSystem probably don't support file birthtime.", this, creationTimeSource, previousLogEventTimestamp); + creationTimeSource = previousLogEventTimestamp; + } + else + { + InternalLogger.Debug("{0}: File creation time {1} newer than previous file write time {2}. Linux FileSystem probably don't support file birthtime, unless multiple applications are writing to the same file. Configure ConcurrentFileTarget.KeepFileOpen=true AND ConcurrentFileTarget.ConcurrentWrites=false, so NLog can fix this.", this, creationTimeSource, previousLogEventTimestamp); + } + } + } + + return creationTimeSource; + } + + /// + /// Truncates the input-time, so comparison of low resolution times (like dates) are not affected by ticks + /// + /// High resolution Time + /// Time Resolution Level + /// Truncated Low Resolution Time + private static DateTime TruncateArchiveTime(DateTime input, FileArchiveEveryPeriod resolution) + { + switch (resolution) + { + case FileArchiveEveryPeriod.Year: + return new DateTime(input.Year, 1, 1, 0, 0, 0, 0, input.Kind); + case FileArchiveEveryPeriod.Month: + return new DateTime(input.Year, input.Month, 1, 0, 0, 0, input.Kind); + case FileArchiveEveryPeriod.Day: + return input.Date; + case FileArchiveEveryPeriod.Hour: + return input.AddTicks(-(input.Ticks % TimeSpan.TicksPerHour)); + case FileArchiveEveryPeriod.Minute: + return input.AddTicks(-(input.Ticks % TimeSpan.TicksPerMinute)); + case FileArchiveEveryPeriod.Sunday: + return CalculateNextWeekday(input.Date, DayOfWeek.Sunday); + case FileArchiveEveryPeriod.Monday: + return CalculateNextWeekday(input.Date, DayOfWeek.Monday); + case FileArchiveEveryPeriod.Tuesday: + return CalculateNextWeekday(input.Date, DayOfWeek.Tuesday); + case FileArchiveEveryPeriod.Wednesday: + return CalculateNextWeekday(input.Date, DayOfWeek.Wednesday); + case FileArchiveEveryPeriod.Thursday: + return CalculateNextWeekday(input.Date, DayOfWeek.Thursday); + case FileArchiveEveryPeriod.Friday: + return CalculateNextWeekday(input.Date, DayOfWeek.Friday); + case FileArchiveEveryPeriod.Saturday: + return CalculateNextWeekday(input.Date, DayOfWeek.Saturday); + default: + return input; // Unknown time-resolution-truncate, leave unchanged + } + } + + private void AutoCloseAppendersAfterArchive(object sender, EventArgs state) + { + bool lockTaken = Monitor.TryEnter(SyncRoot, TimeSpan.FromSeconds(2)); + if (!lockTaken) + return; // Archive events triggered by FileWatcher are important, but not life critical + + try + { + if (!IsInitialized) + { + return; + } + + InternalLogger.Trace("{0}: Auto Close FileAppenders after archive", this); + _fileAppenderCache.CloseExpiredAppenders(DateTime.MinValue); + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "{0}: Exception in AutoCloseAppendersAfterArchive", this); + } + finally + { + Monitor.Exit(SyncRoot); + } + } + + private void AutoClosingTimerCallback(object sender, EventArgs state) + { + bool lockTaken = Monitor.TryEnter(SyncRoot, TimeSpan.FromSeconds(0.5)); + if (!lockTaken) + return; // Timer will trigger again, no need for timers to queue up + + try + { + if (!IsInitialized) + { + return; + } + + if (OpenFileCacheTimeout > 0) + { + DateTime expireTimeUtc = DateTime.UtcNow.AddSeconds(-OpenFileCacheTimeout); + InternalLogger.Trace("{0}: Auto Close FileAppenders", this); + _fileAppenderCache.CloseExpiredAppenders(expireTimeUtc); + } + + if (OpenFileFlushTimeout > 0 && !AutoFlush) + { + ConditionalFlushOpenFileAppenders(); + } + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "{0}: Exception in AutoClosingTimerCallback", this); + } + finally + { + Monitor.Exit(SyncRoot); + } + } + + private void ConditionalFlushOpenFileAppenders() + { + DateTime flushTime = Time.TimeSource.Current.Time.AddSeconds(-Math.Max(OpenFileFlushTimeout, 5) * 2); + + bool flushAppenders = false; + foreach (var file in _initializedFiles) + { + if (file.Value > flushTime) + { + flushAppenders = true; + break; + } + } + + if (flushAppenders) + { + // Only request flush of file-handles, when something has been written + InternalLogger.Trace("{0}: Auto Flush FileAppenders", this); + _fileAppenderCache.FlushAppenders(); + } + } + + /// + /// Evaluates which parts of a file should be written (header, content, footer) based on various properties of + /// instance and writes them. + /// + /// File name to be written. + /// Raw sequence of to be written into the content part of the file. + /// File has just been opened. + private void WriteToFile(string fileName, ArraySegment bytes, bool initializedNewFile) + { + BaseFileAppender appender = _fileAppenderCache.AllocateAppender(fileName); + try + { + if (initializedNewFile) + { + WriteHeaderAndBom(appender); + } + + appender.Write(bytes.Array, bytes.Offset, bytes.Count); + + if (AutoFlush) + { + appender.Flush(); + } + } + catch (Exception ex) + { + InternalLogger.Error(ex, "{0}: Failed write to file '{1}'.", this, fileName); + _fileAppenderCache.InvalidateAppender(fileName)?.Dispose(); + throw; + } + } + + /// + /// Initialize a file to be used by the instance. Based on the number of initialized + /// files and the values of various instance properties clean up and/or archiving processes can be invoked. + /// + /// File name to be written. + /// Log event that the instance is currently processing. + /// The DateTime of the previous log event for this file (DateTime.MinValue if just initialized). + private DateTime InitializeFile(string fileName, LogEventInfo logEvent) + { + if (_initializedFiles.Count != 0 && logEvent.TimeStamp == _previousLogEventTimestamp && _previousLogFileName == fileName) + { + return logEvent.TimeStamp; + } + + var now = logEvent.TimeStamp; + if (!_initializedFiles.TryGetValue(fileName, out var lastTime)) + { + PrepareForNewFile(fileName, logEvent); + + _initializedFilesCounter++; + if (_initializedFilesCounter > OpenFileCacheSize) + { + // Attempt to write footer, before closing file appender + _initializedFilesCounter = 0; + CleanupInitializedFiles(); + _initializedFilesCounter = Math.Min(_initializedFiles.Count, OpenFileCacheSize / 2); + } + + _initializedFiles[fileName] = now; + return DateTime.MinValue; + } + else if (lastTime != now) + { + now = EnsureValidLogEventTimeStamp(now, lastTime); + _initializedFiles[fileName] = now; + } + + return lastTime; + } + + private static DateTime EnsureValidLogEventTimeStamp(DateTime logEventTimeStamp, DateTime previousTimeStamp) + { + // Truncating using DateTime.Date is "expensive", so first check if it look like it is from the past + if (logEventTimeStamp < previousTimeStamp && logEventTimeStamp.Date < previousTimeStamp.Date) + { + // Received LogEvent from the past when comparing to the previous timestamp + var currentTime = TimeSource.Current.Time; + if (logEventTimeStamp.Date < currentTime.AddMinutes(-1).Date) + { + // It is not because the machine-time has changed. Probably a LogEvent from the past + if (currentTime.Date < previousTimeStamp.Date) + return currentTime; // Previous timestamp is from the future. We choose machine-time + else + return previousTimeStamp; + } + } + + return logEventTimeStamp; + } + + private void CloseInvalidFileHandle(string fileName) + { + try + { + _fileAppenderCache.InvalidateAppender(fileName)?.Dispose(); + } + finally + { + _initializedFiles.Remove(fileName); // Skip finalize non-existing file + } + } + + /// + /// Writes the file footer and finalizes the file in instance internal structures. + /// + /// File name to close. + /// Indicates if the file is being finalized for archiving. + private void FinalizeFile(string fileName, bool isArchiving = false) + { + try + { + InternalLogger.Trace("{0}: FinalizeFile '{1}, isArchiving: {2}'", this, fileName, isArchiving); + if ((isArchiving) || (!WriteFooterOnArchivingOnly)) + WriteFooter(fileName); + } + finally + { + CloseInvalidFileHandle(fileName); + } + } + + /// + /// Writes the footer information to a file. + /// + /// The file path to write to. + private void WriteFooter(string fileName) + { + ArraySegment footerBytes = GetLayoutBytes(Footer); + if (footerBytes.Count > 0 && File.Exists(fileName)) + { + WriteToFile(fileName, footerBytes, false); + } + } + + /// + /// Decision logic whether to archive logfile on startup. + /// and properties. + /// + /// File name to be written. + /// Decision whether to archive or not. + internal bool ShouldArchiveOldFileOnStartup(string fileName) + { + if (_archiveOldFileOnStartup == false) + { + // explicitly disabled and not the default + return false; + } + + var aboveSizeSet = ArchiveOldFileOnStartupAboveSize > 0; + if (aboveSizeSet) + { + // Check whether size threshold exceeded + var length = _fileAppenderCache.GetFileLength(fileName); + return length.HasValue && length.Value > ArchiveOldFileOnStartupAboveSize; + } + + // No size threshold specified, use archiveOldFileOnStartup flag + return _archiveOldFileOnStartup == true; + } + + /// + /// Invokes the archiving and clean up of older archive file based on the values of + /// and + /// properties respectively. + /// + /// File name to be written. + /// Log event that the instance is currently processing. + private void PrepareForNewFile(string fileName, LogEventInfo logEvent) + { + InternalLogger.Debug("{0}: Preparing for new file: '{1}'", this, fileName); + RefreshArchiveFilePatternToWatch(fileName, logEvent); + + try + { + if (ShouldArchiveOldFileOnStartup(fileName)) + { + DoAutoArchive(fileName, logEvent, DateTime.MinValue, true); + } + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "{0}: Unable to archive old log file '{1}'.", this, fileName); + if (LogManager.ThrowExceptions) + throw; + } + + if (DeleteOldFileOnStartup) + { + DeleteOldArchiveFile(fileName); + } + + try + { + string archiveFilePattern = GetArchiveFileNamePattern(fileName, logEvent); + if (!string.IsNullOrEmpty(archiveFilePattern)) + { + var fileArchiveStyle = GetFileArchiveHelper(archiveFilePattern); + if (fileArchiveStyle.AttemptCleanupOnInitializeFile(archiveFilePattern, MaxArchiveFiles, MaxArchiveDays)) + { + var existingArchiveFiles = fileArchiveStyle.GetExistingArchiveFiles(archiveFilePattern); + if (existingArchiveFiles.Count > 0) + { + CleanupOldArchiveFiles(new FileInfo(fileName), archiveFilePattern, existingArchiveFiles); + } + } + } + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "FileTarget(Name={0}): Failed to cleanup old archive files when starting on new file: '{1}'", Name, fileName); + if (LogManager.ThrowExceptions) + throw; + } + } + + /// + /// Creates the file specified in and writes the file content in each entirety i.e. + /// Header, Content and Footer. + /// + /// The name of the file to be written. + /// Sequence of to be written in the content section of the file. + /// First attempt to write? + /// This method is used when the content of the log file is re-written on every write. + private void ReplaceFileContent(string fileName, ArraySegment bytes, bool firstAttempt) + { + try + { + using (FileStream fs = File.Create(fileName)) + { + ArraySegment headerBytes = GetLayoutBytes(Header); + if (headerBytes.Count > 0) + { + fs.Write(headerBytes.Array, headerBytes.Offset, headerBytes.Count); + } + + fs.Write(bytes.Array, bytes.Offset, bytes.Count); + + ArraySegment footerBytes = GetLayoutBytes(Footer); + if (footerBytes.Count > 0) + { + fs.Write(footerBytes.Array, footerBytes.Offset, footerBytes.Count); + } + } + } + catch (DirectoryNotFoundException) + { + if (!CreateDirs || !firstAttempt) + { + throw; + } + Directory.CreateDirectory(Path.GetDirectoryName(fileName)); + //retry. + ReplaceFileContent(fileName, bytes, false); + } + } + + private static bool InitialValueBom(Encoding encoding) + { + // Initial of true for UTF 16 and UTF 32 + const int utf16 = 1200; + const int utf16Be = 1201; + const int utf32 = 12000; + const int urf32Be = 12001; + var codePage = encoding?.CodePage ?? 0; + return codePage == utf16 + || codePage == utf16Be + || codePage == utf32 + || codePage == urf32Be; + } + + /// + /// Writes the header information and byte order mark to a file. + /// + /// File appender associated with the file. + private void WriteHeaderAndBom(BaseFileAppender appender) + { + //performance: cheap check before checking file info + if (Header is null && !WriteBom) return; + + var length = appender.GetFileLength(); + // File is empty or file info cannot be obtained + var isNewOrEmptyFile = length is null || length == 0; + + if (isNewOrEmptyFile && WriteBom) + { + InternalLogger.Trace("{0}: Write byte order mark from encoding={1}", this, Encoding); + var preamble = Encoding.GetPreamble(); + if (preamble.Length > 0) + appender.Write(preamble, 0, preamble.Length); + } + + if (Header != null && (isNewOrEmptyFile || WriteHeaderWhenInitialFileNotEmpty)) + { + InternalLogger.Trace("{0}: Write header", this); + ArraySegment headerBytes = GetLayoutBytes(Header); + if (headerBytes.Count > 0) + { + appender.Write(headerBytes.Array, headerBytes.Offset, headerBytes.Count); + } + } + } + + /// + /// The sequence of to be written in a file after applying any formatting and any + /// transformations required from the . + /// + /// The layout used to render output message. + /// Sequence of to be written. + /// Usually it is used to render the header and hooter of the files. + private ArraySegment GetLayoutBytes(Layout layout) + { + if (layout is null) + { + return default(ArraySegment); + } + + using (var targetBuilder = _reusableLayoutBuilder.Allocate()) + using (var targetBuffer = _reusableEncodingBuffer.Allocate()) + { + var nullEvent = LogEventInfo.CreateNullEvent(); + layout.Render(nullEvent, targetBuilder.Result); + targetBuilder.Result.Append(NewLineChars); + using (MemoryStream ms = new MemoryStream(targetBuilder.Result.Length)) + { + TransformBuilderToStream(nullEvent, targetBuilder.Result, targetBuffer.Result, ms); + return new ArraySegment(ms.ToArray()); + } + } + } + } +} diff --git a/src/NLog/Internal/FileAppenders/BaseFileAppender.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/BaseFileAppender.cs similarity index 96% rename from src/NLog/Internal/FileAppenders/BaseFileAppender.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/BaseFileAppender.cs index ef4d4e2847..f805c1f3ae 100644 --- a/src/NLog/Internal/FileAppenders/BaseFileAppender.cs +++ b/src/NLog.Targets.ConcurrentFile/FileAppenders/BaseFileAppender.cs @@ -156,7 +156,7 @@ public void Dispose() /// /// Releases unmanaged and - optionally - managed resources. /// - /// True to release both managed and unmanaged resources; false to release only unmanaged resources. + /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (disposing) @@ -192,7 +192,7 @@ protected FileStream CreateFileStream(bool allowFileSharedWriting, int overrideB int actualDelay = _random.Next(currentDelay); InternalLogger.Warn("{0}: Attempt #{1} to open {2} failed - {3} {4}. Sleeping for {5}ms", CreateFileParameters, i, FileName, ex.GetType(), ex.Message, actualDelay); currentDelay *= 2; - AsyncHelpers.WaitForDelay(TimeSpan.FromMilliseconds(actualDelay)); + System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(actualDelay)); } } @@ -233,7 +233,7 @@ private FileStream TryCreateDirectoryFileStream(bool allowFileSharedWriting, int } } -#if !MONO && !NETSTANDARD +#if NETFRAMEWORK && !MONO private FileStream WindowsCreateFile(bool allowFileSharedWriting, int bufferSize) { int fileShare = Win32FileNativeMethods.FILE_SHARE_READ; @@ -243,7 +243,7 @@ private FileStream WindowsCreateFile(bool allowFileSharedWriting, int bufferSize fileShare |= Win32FileNativeMethods.FILE_SHARE_WRITE; } - if (CreateFileParameters.EnableFileDelete && PlatformDetector.CurrentOS != RuntimeOS.Windows9x) + if (CreateFileParameters.EnableFileDelete && PlatformDetector.CurrentOS != PlatformSystem.Windows9x) { fileShare |= Win32FileNativeMethods.FILE_SHARE_DELETE; } @@ -287,7 +287,7 @@ private FileStream TryCreateFileStream(bool allowFileSharedWriting, int override { var bufferSize = overrideBufferSize > 0 ? overrideBufferSize : CreateFileParameters.BufferSize; -#if !MONO && !NETSTANDARD +#if NETFRAMEWORK && !MONO try { if (!CreateFileParameters.ForceManaged && PlatformDetector.IsWin32 && !PlatformDetector.IsMono) @@ -363,7 +363,7 @@ protected static void CloseFileSafe(ref FileStream fileStream, string fileName) { // Swallow exception as the file-stream now is in final state (broken instead of closed) InternalLogger.Warn(ex, "FileTarget: Failed to close file '{0}'", fileName); - AsyncHelpers.WaitForDelay(TimeSpan.FromMilliseconds(1)); // Artificial delay to avoid hammering a bad file location + System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(1)); // Artificial delay to avoid hammering a bad file location } finally { diff --git a/src/NLog/Internal/FileAppenders/BaseMutexFileAppender.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/BaseMutexFileAppender.cs similarity index 96% rename from src/NLog/Internal/FileAppenders/BaseMutexFileAppender.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/BaseMutexFileAppender.cs index 3172920126..0d56453e5b 100644 --- a/src/NLog/Internal/FileAppenders/BaseMutexFileAppender.cs +++ b/src/NLog.Targets.ConcurrentFile/FileAppenders/BaseMutexFileAppender.cs @@ -31,10 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 -#define SupportsMutex -#endif - namespace NLog.Internal.FileAppenders { using System; @@ -42,15 +38,12 @@ namespace NLog.Internal.FileAppenders using System.Security; using System.Threading; using System.Text; - using JetBrains.Annotations; -#if SupportsMutex -#if !NETSTANDARD +#if NETFRAMEWORK using System.Security.AccessControl; using System.Security.Principal; #endif using System.Security.Cryptography; -#endif using Common; /// @@ -74,9 +67,7 @@ protected BaseMutexFileAppender(string fileName, ICreateFileParameters createPar { if (MutexDetector.SupportsSharableMutex) { -#if SupportsMutex ArchiveMutex = CreateArchiveMutex(); -#endif } else { @@ -85,12 +76,10 @@ protected BaseMutexFileAppender(string fileName, ICreateFileParameters createPar } } -#if SupportsMutex /// /// Gets the mutually-exclusive lock for archiving files. /// /// The mutex for archiving. - [CanBeNull] public Mutex ArchiveMutex { get; private set; } private Mutex CreateArchiveMutex() @@ -108,7 +97,7 @@ private Mutex CreateArchiveMutex() } InternalLogger.Error(ex, "{0}: Failed to create global archive mutex: {1}", CreateFileParameters, FileName); - if (ex.MustBeRethrown()) + if (LogManager.ThrowExceptions) throw; return new Mutex(); } @@ -190,6 +179,5 @@ private string GetMutexName(string mutexNamePrefix) int cutOffIndex = canonicalName.Length - (maxMutexNameLength - mutexName.Length); return mutexName + canonicalName.Substring(cutOffIndex); } -#endif } } diff --git a/src/NLog/Internal/FileAppenders/CountingSingleProcessFileAppender.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/CountingSingleProcessFileAppender.cs similarity index 100% rename from src/NLog/Internal/FileAppenders/CountingSingleProcessFileAppender.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/CountingSingleProcessFileAppender.cs diff --git a/src/NLog/Internal/FileAppenders/FileAppenderCache.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/FileAppenderCache.cs similarity index 98% rename from src/NLog/Internal/FileAppenders/FileAppenderCache.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/FileAppenderCache.cs index 72a561b693..2bc2ae087b 100644 --- a/src/NLog/Internal/FileAppenders/FileAppenderCache.cs +++ b/src/NLog.Targets.ConcurrentFile/FileAppenders/FileAppenderCache.cs @@ -46,11 +46,9 @@ internal sealed class FileAppenderCache : IFileAppenderCache private readonly BaseFileAppender[] _appenders; private Timer _autoClosingTimer; -#if !NETSTANDARD1_3 private string _archiveFilePatternToWatch; private readonly MultiFileWatcher _externalFileArchivingWatcher = new MultiFileWatcher(NotifyFilters.DirectoryName | NotifyFilters.FileName); private bool _logFileWasArchived; -#endif /// /// An "empty" instance of the class with zero size and empty list of appenders. @@ -83,12 +81,9 @@ public FileAppenderCache(int size, IFileAppenderFactory appenderFactory, ICreate _autoClosingTimer = new Timer(AutoClosingTimerCallback, null, Timeout.Infinite, Timeout.Infinite); -#if !NETSTANDARD1_3 _externalFileArchivingWatcher.FileChanged += ExternalFileArchivingWatcher_OnFileChanged; -#endif } -#if !NETSTANDARD1_3 private void ExternalFileArchivingWatcher_OnFileChanged(object sender, FileSystemEventArgs e) { if (_logFileWasArchived || CheckCloseAppenders is null || _autoClosingTimer is null) @@ -163,7 +158,6 @@ public void InvalidateAppendersForArchivedFiles() CloseAppenders("Cleanup Archive"); } } -#endif private void AutoClosingTimerCallback(object state) { @@ -274,7 +268,6 @@ private BaseFileAppender CreateAppender(string fileName, int freeSpot) _appenders[0] = newAppender; appenderToWrite = newAppender; -#if !NETSTANDARD1_3 if (CheckCloseAppenders != null) { if (freeSpot == 0) @@ -292,7 +285,6 @@ private BaseFileAppender CreateAppender(string fileName, int freeSpot) } _externalFileArchivingWatcher.Watch(appenderToWrite.FileName); // Monitor the active file-appender } -#endif } catch (Exception ex) { @@ -332,14 +324,12 @@ private void CloseAllAppenders(int startIndex, string reason) /// The time which prior the appenders considered expired public void CloseExpiredAppenders(DateTime expireTimeUtc) { -#if !NETSTANDARD1_3 if (_logFileWasArchived) { _logFileWasArchived = false; CloseAppenders("Cleanup Timer"); } else -#endif { if (expireTimeUtc != DateTime.MinValue) { @@ -529,14 +519,12 @@ private void CloseAppender(BaseFileAppender appender, string reason, bool lastAp // No active appenders, deactivate background tasks _autoClosingTimer.Change(Timeout.Infinite, Timeout.Infinite); -#if !NETSTANDARD1_3 _externalFileArchivingWatcher.StopWatching(); _logFileWasArchived = false; } else { _externalFileArchivingWatcher.StopWatching(appender.FileName); -#endif } appender.Close(); @@ -546,16 +534,14 @@ public void Dispose() { CheckCloseAppenders = null; -#if !NETSTANDARD1_3 _externalFileArchivingWatcher.Dispose(); _logFileWasArchived = false; -#endif var currentTimer = _autoClosingTimer; if (currentTimer != null) { _autoClosingTimer = null; - currentTimer.WaitForDispose(TimeSpan.Zero); + currentTimer.Dispose(); } } } diff --git a/src/NLog/Internal/FileAppenders/ICreateFileParameters.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/ICreateFileParameters.cs similarity index 94% rename from src/NLog/Internal/FileAppenders/ICreateFileParameters.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/ICreateFileParameters.cs index 78fe71b254..48c9ccdebc 100644 --- a/src/NLog/Internal/FileAppenders/ICreateFileParameters.cs +++ b/src/NLog.Targets.ConcurrentFile/FileAppenders/ICreateFileParameters.cs @@ -33,7 +33,7 @@ namespace NLog.Internal.FileAppenders { - using Targets; + using NLog.Targets; /// /// Interface that provides parameters for create file function. @@ -41,13 +41,13 @@ namespace NLog.Internal.FileAppenders internal interface ICreateFileParameters { /// - /// Gets or sets the delay in milliseconds to wait before attempting to write to the file again. + /// Gets or sets the number of times the write is attempted on the file before NLog + /// discards the log message. /// int FileOpenRetryCount { get; } /// - /// Gets or sets the number of times the write is appended on the file before NLog - /// discards the log message. + /// Gets or sets the delay in milliseconds to wait before attempting to write to the file again. /// int FileOpenRetryDelay { get; } @@ -64,7 +64,7 @@ internal interface ICreateFileParameters /// Gets or sets a value indicating whether to create directories if they do not exist. /// /// - /// Setting this to false may improve performance a bit, but you'll receive an error + /// Setting this to may improve performance a bit, but you'll receive an error /// when attempting to write to a directory that's not present. /// bool CreateDirs { get; } diff --git a/src/NLog/Internal/FileAppenders/IFileAppenderCache.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/IFileAppenderCache.cs similarity index 99% rename from src/NLog/Internal/FileAppenders/IFileAppenderCache.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/IFileAppenderCache.cs index a929055354..8a16e134ab 100644 --- a/src/NLog/Internal/FileAppenders/IFileAppenderCache.cs +++ b/src/NLog.Targets.ConcurrentFile/FileAppenders/IFileAppenderCache.cs @@ -100,8 +100,6 @@ internal interface IFileAppenderCache : IDisposable /// File Appender that matched the filePath (null if none found) BaseFileAppender InvalidateAppender(string filePath); -#if !NETSTANDARD1_3 - /// /// The archive file path pattern that is used to detect when archiving occurs. /// @@ -111,7 +109,5 @@ internal interface IFileAppenderCache : IDisposable /// Invalidates appenders for all files that were archived. /// void InvalidateAppendersForArchivedFiles(); - -#endif } } diff --git a/src/NLog/Internal/FileAppenders/IFileAppenderFactory.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/IFileAppenderFactory.cs similarity index 100% rename from src/NLog/Internal/FileAppenders/IFileAppenderFactory.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/IFileAppenderFactory.cs diff --git a/src/NLog/Internal/FileAppenders/MutexMultiProcessFileAppender.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/MutexMultiProcessFileAppender.cs similarity index 98% rename from src/NLog/Internal/FileAppenders/MutexMultiProcessFileAppender.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/MutexMultiProcessFileAppender.cs index 700a3d9c99..9fb0d0133f 100644 --- a/src/NLog/Internal/FileAppenders/MutexMultiProcessFileAppender.cs +++ b/src/NLog.Targets.ConcurrentFile/FileAppenders/MutexMultiProcessFileAppender.cs @@ -31,12 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 -#define SupportsMutex -#endif - -#if SupportsMutex - namespace NLog.Internal.FileAppenders { using System; @@ -170,5 +164,3 @@ BaseFileAppender IFileAppenderFactory.Open(string fileName, ICreateFileParameter } } } - -#endif diff --git a/src/NLog/Internal/FileAppenders/NullAppender.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/NullAppender.cs similarity index 100% rename from src/NLog/Internal/FileAppenders/NullAppender.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/NullAppender.cs diff --git a/src/NLog/Internal/FileAppenders/RetryingMultiProcessFileAppender.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/RetryingMultiProcessFileAppender.cs similarity index 100% rename from src/NLog/Internal/FileAppenders/RetryingMultiProcessFileAppender.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/RetryingMultiProcessFileAppender.cs diff --git a/src/NLog/Internal/FileAppenders/SingleProcessFileAppender.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/SingleProcessFileAppender.cs similarity index 100% rename from src/NLog/Internal/FileAppenders/SingleProcessFileAppender.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/SingleProcessFileAppender.cs diff --git a/src/NLog/Internal/FileAppenders/UnixMultiProcessFileAppender.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/UnixMultiProcessFileAppender.cs similarity index 100% rename from src/NLog/Internal/FileAppenders/UnixMultiProcessFileAppender.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/UnixMultiProcessFileAppender.cs diff --git a/src/NLog/Internal/FileAppenders/WindowsMultiProcessFileAppender.cs b/src/NLog.Targets.ConcurrentFile/FileAppenders/WindowsMultiProcessFileAppender.cs similarity index 99% rename from src/NLog/Internal/FileAppenders/WindowsMultiProcessFileAppender.cs rename to src/NLog.Targets.ConcurrentFile/FileAppenders/WindowsMultiProcessFileAppender.cs index 06ed2f807b..c24171b8d8 100644 --- a/src/NLog/Internal/FileAppenders/WindowsMultiProcessFileAppender.cs +++ b/src/NLog.Targets.ConcurrentFile/FileAppenders/WindowsMultiProcessFileAppender.cs @@ -31,7 +31,7 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !MONO && NETFRAMEWORK +#if NETFRAMEWORK && !MONO namespace NLog.Internal.FileAppenders { diff --git a/src/NLog/Internal/IAppDomain.cs b/src/NLog.Targets.ConcurrentFile/FileArchiveEveryPeriod.cs similarity index 59% rename from src/NLog/Internal/IAppDomain.cs rename to src/NLog.Targets.ConcurrentFile/FileArchiveEveryPeriod.cs index e3d50b9dfe..3af9835963 100644 --- a/src/NLog/Internal/IAppDomain.cs +++ b/src/NLog.Targets.ConcurrentFile/FileArchiveEveryPeriod.cs @@ -31,59 +31,78 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.Internal.Fakeables +namespace NLog.Targets { - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Reflection; - /// - /// Interface for fakeable of the current AppDomain. + /// Modes of archiving files based on time. /// - [Obsolete("For unit testing only. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public interface IAppDomain + public enum FileArchiveEveryPeriod { /// - /// Gets or sets the base directory that the assembly resolver uses to probe for assemblies. + /// Don't archive based on time. + /// + None, + + /// + /// Archive every new year. + /// + Year, + + /// + /// Archive every new month. + /// + Month, + + /// + /// Archive every new day. + /// + Day, + + /// + /// Archive every new hour. + /// + Hour, + + /// + /// Archive every new minute. /// - string BaseDirectory { get; } + Minute, + #region Weekdays /// - /// Gets or sets the name of the configuration file for an application domain. + /// Archive every Sunday. /// - string ConfigurationFile { get; } + Sunday, /// - /// Gets or sets the list of directories under the application base directory that are probed for private assemblies. + /// Archive every Monday. /// - IEnumerable PrivateBinPath { get; } + Monday, /// - /// Gets or set the friendly name. + /// Archive every Tuesday. /// - string FriendlyName { get; } + Tuesday, /// - /// Gets an integer that uniquely identifies the application domain within the process. + /// Archive every Wednesday. /// - int Id { get; } + Wednesday, /// - /// Gets the assemblies that have been loaded into the execution context of this application domain. + /// Archive every Thursday. /// - /// A list of assemblies in this application domain. - IEnumerable GetAssemblies(); + Thursday, /// - /// Process exit event. + /// Archive every Friday. /// - event EventHandler ProcessExit; + Friday, /// - /// Domain unloaded event. + /// Archive every Saturday. /// - event EventHandler DomainUnload; + Saturday + #endregion } } diff --git a/src/NLog/Targets/FileArchiveModes/FileArchiveModeBase.cs b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeBase.cs similarity index 99% rename from src/NLog/Targets/FileArchiveModes/FileArchiveModeBase.cs rename to src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeBase.cs index 9dab892ab4..990eab96dc 100644 --- a/src/NLog/Targets/FileArchiveModes/FileArchiveModeBase.cs +++ b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeBase.cs @@ -60,7 +60,7 @@ protected FileArchiveModeBase(bool isArchiveCleanupEnabled) /// Base archive file pattern /// Maximum number of archive files that should be kept /// Maximum days of archive files that should be kept - /// True, when archive cleanup is needed + /// , when archive cleanup is needed public virtual bool AttemptCleanupOnInitializeFile(string archiveFilePath, int maxArchiveFiles, int maxArchiveDays) { if (maxArchiveFiles > 0 && _lastArchiveFileCount++ > maxArchiveFiles) diff --git a/src/NLog/Targets/FileArchiveModes/FileArchiveModeDate.cs b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeDate.cs similarity index 93% rename from src/NLog/Targets/FileArchiveModes/FileArchiveModeDate.cs rename to src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeDate.cs index fb253b335d..0649ef2c02 100644 --- a/src/NLog/Targets/FileArchiveModes/FileArchiveModeDate.cs +++ b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeDate.cs @@ -42,8 +42,8 @@ namespace NLog.Targets.FileArchiveModes /// Archives the log-files using a date style numbering. Archives will be stamped with the /// prior period (Year, Month, Day, Hour, Minute) datetime. /// - /// When the number of archive files exceed the obsolete archives are deleted. - /// When the age of archive files exceed the obsolete archives are deleted. + /// When the number of archive files exceed the obsolete archives are deleted. + /// When the age of archive files exceed the obsolete archives are deleted. /// internal sealed class FileArchiveModeDate : FileArchiveModeBase { @@ -65,7 +65,7 @@ public override List GetExistingArchiveFiles(string arch protected override DateAndSequenceArchive GenerateArchiveFileInfo(FileInfo archiveFile, FileNameTemplate fileTemplate) { - string archiveFileName = Path.GetFileName(archiveFile.FullName) ?? ""; + string archiveFileName = Path.GetFileName(archiveFile.FullName) ?? string.Empty; string fileNameMask = fileTemplate.ReplacePattern("*"); int lastIndexOfStar = fileNameMask.LastIndexOf('*'); diff --git a/src/NLog/Targets/FileArchiveModes/FileArchiveModeDateAndSequence.cs b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeDateAndSequence.cs similarity index 96% rename from src/NLog/Targets/FileArchiveModes/FileArchiveModeDateAndSequence.cs rename to src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeDateAndSequence.cs index 5761b02139..c103d975ff 100644 --- a/src/NLog/Targets/FileArchiveModes/FileArchiveModeDateAndSequence.cs +++ b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeDateAndSequence.cs @@ -44,8 +44,8 @@ namespace NLog.Targets.FileArchiveModes /// with the prior period (Year, Month, Day) datetime. The most recent archive has the highest number (in /// combination with the date). /// - /// When the number of archive files exceed the obsolete archives are deleted. - /// When the age of archive files exceed the obsolete archives are deleted. + /// When the number of archive files exceed the obsolete archives are deleted. + /// When the age of archive files exceed the obsolete archives are deleted. ///
internal sealed class FileArchiveModeDateAndSequence : FileArchiveModeBase { diff --git a/src/NLog/Targets/FileArchiveModes/FileArchiveModeDynamicSequence.cs b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeDynamicSequence.cs similarity index 97% rename from src/NLog/Targets/FileArchiveModes/FileArchiveModeDynamicSequence.cs rename to src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeDynamicSequence.cs index eef1f72c2a..d0b14ad4a5 100644 --- a/src/NLog/Targets/FileArchiveModes/FileArchiveModeDynamicSequence.cs +++ b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeDynamicSequence.cs @@ -50,8 +50,8 @@ namespace NLog.Targets.FileArchiveModes /// /// The most recent archive has the highest number. /// - /// When the number of archive files exceed the obsolete archives are deleted. - /// When the age of archive files exceed the obsolete archives are deleted. + /// When the number of archive files exceed the obsolete archives are deleted. + /// When the age of archive files exceed the obsolete archives are deleted. ///
internal sealed class FileArchiveModeDynamicSequence : FileArchiveModeBase { @@ -70,7 +70,7 @@ public FileArchiveModeDynamicSequence(ArchiveNumberingMode archiveNumbering, str private static bool RemoveNonLetters(string fileName, int startPosition, StringBuilder sb, out int digitsRemoved) { digitsRemoved = 0; - sb.ClearBuilder(); + sb.Length = 0; for (int i = 0; i < startPosition; i++) { @@ -143,7 +143,7 @@ protected override FileNameTemplate GenerateFileNameTemplate(string archiveFileP RemoveNonLetters(currentFileName, optimalStartPosition, sb, out digitsRemoved); if (digitsRemoved <= 1) { - sb.ClearBuilder(); + sb.Length = 0; sb.Append(currentFileName); } diff --git a/src/NLog/Targets/FileArchiveModes/FileArchiveModeDynamicTemplate.cs b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeDynamicTemplate.cs similarity index 100% rename from src/NLog/Targets/FileArchiveModes/FileArchiveModeDynamicTemplate.cs rename to src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeDynamicTemplate.cs diff --git a/src/NLog/Targets/FileArchiveModes/FileArchiveModeFactory.cs b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeFactory.cs similarity index 100% rename from src/NLog/Targets/FileArchiveModes/FileArchiveModeFactory.cs rename to src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeFactory.cs diff --git a/src/NLog/Targets/FileArchiveModes/FileArchiveModeRolling.cs b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeRolling.cs similarity index 98% rename from src/NLog/Targets/FileArchiveModes/FileArchiveModeRolling.cs rename to src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeRolling.cs index d6b02214ee..c6a868123d 100644 --- a/src/NLog/Targets/FileArchiveModes/FileArchiveModeRolling.cs +++ b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeRolling.cs @@ -41,7 +41,7 @@ namespace NLog.Targets.FileArchiveModes /// Archives the log-files using a rolling style numbering (the most recent is always #0 then /// #1, ..., #N. /// - /// When the number of archive files exceed the obsolete archives + /// When the number of archive files exceed the obsolete archives /// are deleted. /// internal sealed class FileArchiveModeRolling : IFileArchiveMode diff --git a/src/NLog/Targets/FileArchiveModes/FileArchiveModeSequence.cs b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeSequence.cs similarity index 94% rename from src/NLog/Targets/FileArchiveModes/FileArchiveModeSequence.cs rename to src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeSequence.cs index 20b537cc2e..897b786644 100644 --- a/src/NLog/Targets/FileArchiveModes/FileArchiveModeSequence.cs +++ b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/FileArchiveModeSequence.cs @@ -42,8 +42,8 @@ namespace NLog.Targets.FileArchiveModes /// /// Archives the log-files using a sequence style numbering. The most recent archive has the highest number. /// - /// When the number of archive files exceed the obsolete archives are deleted. - /// When the age of archive files exceed the obsolete archives are deleted. + /// When the number of archive files exceed the obsolete archives are deleted. + /// When the age of archive files exceed the obsolete archives are deleted. /// internal sealed class FileArchiveModeSequence : FileArchiveModeBase { @@ -62,7 +62,7 @@ public override bool AttemptCleanupOnInitializeFile(string archiveFilePath, int protected override DateAndSequenceArchive GenerateArchiveFileInfo(FileInfo archiveFile, FileNameTemplate fileTemplate) { - string baseName = Path.GetFileName(archiveFile.FullName) ?? ""; + string baseName = Path.GetFileName(archiveFile.FullName) ?? string.Empty; int trailerLength = fileTemplate.Template.Length - fileTemplate.EndAt; string number = baseName.Substring(fileTemplate.BeginAt, baseName.Length - trailerLength - fileTemplate.BeginAt); int num; diff --git a/src/NLog/Targets/IFileArchiveMode.cs b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/IFileArchiveMode.cs similarity index 97% rename from src/NLog/Targets/IFileArchiveMode.cs rename to src/NLog.Targets.ConcurrentFile/FileArchiveModes/IFileArchiveMode.cs index 4b5ea2dad0..52bdf68f2c 100644 --- a/src/NLog/Targets/IFileArchiveMode.cs +++ b/src/NLog.Targets.ConcurrentFile/FileArchiveModes/IFileArchiveMode.cs @@ -46,7 +46,7 @@ interface IFileArchiveMode /// Base archive file pattern /// Maximum number of archive files that should be kept /// Maximum days of archive files that should be kept - /// True, when archive cleanup is needed + /// , when archive cleanup is needed bool AttemptCleanupOnInitializeFile(string archiveFilePath, int maxArchiveFiles, int maxArchiveDays); /// diff --git a/src/NLog/Targets/FilePathKind.cs b/src/NLog.Targets.ConcurrentFile/FilePathKind.cs similarity index 100% rename from src/NLog/Targets/FilePathKind.cs rename to src/NLog.Targets.ConcurrentFile/FilePathKind.cs diff --git a/src/NLog/Targets/IArchiveFileCompressor.cs b/src/NLog.Targets.ConcurrentFile/IArchiveFileCompressor.cs similarity index 91% rename from src/NLog/Targets/IArchiveFileCompressor.cs rename to src/NLog.Targets.ConcurrentFile/IArchiveFileCompressor.cs index 3d64e5f1c4..714ea265d9 100644 --- a/src/NLog/Targets/IArchiveFileCompressor.cs +++ b/src/NLog.Targets.ConcurrentFile/IArchiveFileCompressor.cs @@ -34,8 +34,8 @@ namespace NLog.Targets { /// - /// may be configured to compress archived files in a custom way - /// by setting before logging your first event. + /// may be configured to compress archived files in a custom way + /// by setting before logging your first event. /// internal interface IArchiveFileCompressor : IFileCompressor { diff --git a/src/NLog/Targets/IFileCompressor.cs b/src/NLog.Targets.ConcurrentFile/IFileCompressor.cs similarity index 91% rename from src/NLog/Targets/IFileCompressor.cs rename to src/NLog.Targets.ConcurrentFile/IFileCompressor.cs index d5e7dc73de..6b9f74da4e 100644 --- a/src/NLog/Targets/IFileCompressor.cs +++ b/src/NLog.Targets.ConcurrentFile/IFileCompressor.cs @@ -34,8 +34,8 @@ namespace NLog.Targets { /// - /// may be configured to compress archived files in a custom way - /// by setting before logging your first event. + /// may be configured to compress archived files in a custom way + /// by setting before logging your first event. /// public interface IFileCompressor { diff --git a/src/NLog/Targets/DateAndSequenceArchive.cs b/src/NLog.Targets.ConcurrentFile/Internal/DateAndSequenceArchive.cs similarity index 94% rename from src/NLog/Targets/DateAndSequenceArchive.cs rename to src/NLog.Targets.ConcurrentFile/Internal/DateAndSequenceArchive.cs index 9276372210..94e07b591e 100644 --- a/src/NLog/Targets/DateAndSequenceArchive.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/DateAndSequenceArchive.cs @@ -34,7 +34,6 @@ namespace NLog.Targets { using System; - using NLog.Internal; /// /// A descriptor for an archive created with the DateAndSequence numbering mode. @@ -75,8 +74,8 @@ public bool HasSameFormattedDate(DateTime date) /// public DateAndSequenceArchive(string fileName, DateTime date, string dateFormat, int sequence) { - FileName = Guard.ThrowIfNull(fileName); - _dateFormat = Guard.ThrowIfNull(dateFormat); + FileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); + _dateFormat = dateFormat ?? throw new ArgumentNullException(nameof(dateFormat)); Date = date; Sequence = sequence; } diff --git a/src/NLog.Targets.ConcurrentFile/Internal/ExcludeFromCodeCoverageAttribute.cs b/src/NLog.Targets.ConcurrentFile/Internal/ExcludeFromCodeCoverageAttribute.cs new file mode 100644 index 0000000000..171c60257b --- /dev/null +++ b/src/NLog.Targets.ConcurrentFile/Internal/ExcludeFromCodeCoverageAttribute.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +#if NET35 + +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event, Inherited = false, AllowMultiple = false)] + internal sealed class ExcludeFromCodeCoverageAttribute : Attribute + { + // + // Summary: + // Initializes a new instance of the System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute + // class. + public ExcludeFromCodeCoverageAttribute() + { + } + } +} + +#endif diff --git a/src/NLog/Internal/FileInfoExt.cs b/src/NLog.Targets.ConcurrentFile/Internal/FileInfoExt.cs similarity index 67% rename from src/NLog/Internal/FileInfoExt.cs rename to src/NLog.Targets.ConcurrentFile/Internal/FileInfoExt.cs index c7cd0cebfa..a9cc2c3253 100644 --- a/src/NLog/Internal/FileInfoExt.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/FileInfoExt.cs @@ -31,11 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.IO; - namespace NLog.Internal { + using System; + using System.IO; + internal static class FileInfoExt { public static DateTime GetLastWriteTimeUtc(this FileInfo fileInfo) @@ -49,15 +49,31 @@ public static DateTime GetCreationTimeUtc(this FileInfo fileInfo) public static DateTime LookupValidFileCreationTimeUtc(this FileInfo fileInfo) { - return FileInfoHelper.LookupValidFileCreationTimeUtc(fileInfo, (f) => f.GetCreationTimeUtc(), (f) => f.GetLastWriteTimeUtc()).Value; + return LookupValidFileCreationTimeUtc(fileInfo, (f) => f.GetCreationTimeUtc(), (f) => f.GetLastWriteTimeUtc()).Value; } public static DateTime LookupValidFileCreationTimeUtc(this FileInfo fileInfo, DateTime? fallbackTime) { if (fallbackTime > DateTime.MinValue) - return FileInfoHelper.LookupValidFileCreationTimeUtc(fileInfo, (f) => f.GetCreationTimeUtc(), (f) => fallbackTime.Value, (f) => f.GetLastWriteTimeUtc()).Value; + return LookupValidFileCreationTimeUtc(fileInfo, (f) => f.GetCreationTimeUtc(), (f) => fallbackTime.Value, (f) => f.GetLastWriteTimeUtc()).Value; else return LookupValidFileCreationTimeUtc(fileInfo); } + + internal static DateTime? LookupValidFileCreationTimeUtc(T fileInfo, Func primary, Func fallback, Func finalFallback = null) + { + DateTime? fileCreationTime = primary(fileInfo); + + if (fileCreationTime.HasValue && fileCreationTime.Value.Year < 1980 && !PlatformDetector.IsWin32) + { + // Non-Windows-FileSystems doesn't always provide correct CreationTime/BirthTime + fileCreationTime = fallback(fileInfo); + if (finalFallback != null && (!fileCreationTime.HasValue || fileCreationTime.Value.Year < 1980)) + { + fileCreationTime = finalFallback(fileInfo); + } + } + return fileCreationTime; + } } } diff --git a/src/NLog/Internal/FilePathLayout.cs b/src/NLog.Targets.ConcurrentFile/Internal/FilePathLayout.cs similarity index 91% rename from src/NLog/Internal/FilePathLayout.cs rename to src/NLog.Targets.ConcurrentFile/Internal/FilePathLayout.cs index f33992a41e..cb833aa98e 100644 --- a/src/NLog/Internal/FilePathLayout.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/FilePathLayout.cs @@ -43,7 +43,7 @@ namespace NLog.Internal /// /// A layout that represents a filePath. /// - internal sealed class FilePathLayout : IRenderable + internal sealed class FilePathLayout { /// /// Cached directory separator char array to avoid memory allocation on each method call. @@ -87,14 +87,14 @@ internal sealed class FilePathLayout : IRenderable /// Initializes a new instance of the class. public FilePathLayout(Layout layout, bool cleanupInvalidChars, FilePathKind filePathKind) { - _layout = layout ?? new SimpleLayout(null); + _layout = layout ?? Layout.Empty; _cleanupInvalidChars = cleanupInvalidChars; _filePathKind = filePathKind == FilePathKind.Unknown ? DetectFilePathKind(_layout as SimpleLayout) : filePathKind; _cleanedFixedResult = (_layout as SimpleLayout)?.FixedText; if (_filePathKind == FilePathKind.Relative) { - _baseDir = LogFactory.DefaultAppEnvironment.AppDomainBaseDirectory; + _baseDir = AppDomain.CurrentDomain.BaseDirectory; InternalLogger.Debug("FileTarget FilePathLayout with FilePathKind.Relative using AppDomain.BaseDirectory: {0}", _baseDir); } @@ -118,14 +118,11 @@ public FilePathLayout(Layout layout, bool cleanupInvalidChars, FilePathKind file if (layout is SimpleLayout simpleLayout && simpleLayout.OriginalText?.IndexOfAny(new char[] { '\a', '\b', '\f', '\n', '\r', '\t', '\v', '\"' }) >= 0) { - if (_cleanedFixedResult is null) - InternalLogger.Warn("FileTarget FilePathLayout contains unexpected escape characters (Maybe change to forward-slash): {0}", simpleLayout.OriginalText); - else - InternalLogger.Warn("FileTarget FilePathLayout contains unexpected escape characters (Maybe change to forward-slash): {0}", _cleanedFixedResult); + InternalLogger.Warn("FileTarget FilePathLayout contains unexpected escape characters (Maybe change to forward-slash): {0}", _cleanedFixedResult ?? simpleLayout.OriginalText); } else if (_cleanedFixedResult != null) { - if (DetectFilePathKind(_cleanedFixedResult) != FilePathKind.Absolute && _cleanedFixedResult.IndexOfAny(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }) < 0) + if (DetectFilePathKind(_cleanedFixedResult) != FilePathKind.Absolute && _cleanedFixedResult.IndexOfAny(DirectorySeparatorChars) < 0) { InternalLogger.Warn("FileTarget FilePathLayout not recognized as absolute path (Maybe change to forward-slash): {0}", _cleanedFixedResult); } @@ -151,15 +148,6 @@ private string GetRenderedFileName(LogEventInfo logEvent, System.Text.StringBuil } else { - if (!_layout.ThreadAgnostic || _layout.ThreadAgnosticImmutable) - { - object cachedResult; - if (logEvent.TryGetCachedLayoutValue(_layout, out cachedResult)) - { - return cachedResult?.ToString() ?? string.Empty; - } - } - _layout.Render(logEvent, reusableBuilder); if (_cachedPrevRawFileName != null && reusableBuilder.EqualTo(_cachedPrevRawFileName)) @@ -249,7 +237,7 @@ internal static FilePathKind DetectFilePathKind(string path, bool isFixedText = { if (!string.IsNullOrEmpty(path)) { - path = path.TrimStart(ArrayHelper.Empty()); + path = path.TrimStart(); int length = path.Length; if (length >= 1) diff --git a/src/NLog.Targets.ConcurrentFile/Internal/MultiFileWatcher.cs b/src/NLog.Targets.ConcurrentFile/Internal/MultiFileWatcher.cs new file mode 100644 index 0000000000..4f0a53ae89 --- /dev/null +++ b/src/NLog.Targets.ConcurrentFile/Internal/MultiFileWatcher.cs @@ -0,0 +1,236 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Internal +{ + using System; + using System.Collections.Generic; + using System.IO; + using NLog.Common; + + /// + /// Watches multiple files at the same time and raises an event whenever + /// a single change is detected in any of those files. + /// + internal sealed class MultiFileWatcher : IDisposable + { + private readonly Dictionary _watcherMap = new Dictionary(); + + /// + /// The types of changes to watch for. + /// + public NotifyFilters NotifyFilters { get; set; } + + /// + /// Occurs when a change is detected in one of the monitored files. + /// + public event FileSystemEventHandler FileChanged; + + public MultiFileWatcher() : + this(NotifyFilters.LastWrite | NotifyFilters.CreationTime | NotifyFilters.Size | NotifyFilters.Security | NotifyFilters.Attributes) + { } + + public MultiFileWatcher(NotifyFilters notifyFilters) + { + NotifyFilters = notifyFilters; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + FileChanged = null; // Release event listeners + StopWatching(); + } + + /// + /// Stops watching all files. + /// + public void StopWatching() + { + lock (_watcherMap) + { + foreach (var watcher in _watcherMap) + { + StopWatching(watcher.Value); + } + _watcherMap.Clear(); + } + } + + /// + /// Stops watching the specified file. + /// + /// + public void StopWatching(string fileName) + { + lock (_watcherMap) + { + if (_watcherMap.TryGetValue(fileName, out var watcher)) + { + StopWatching(watcher); + _watcherMap.Remove(fileName); + } + } + } + + public void Watch(string fileName) + { + try + { + var directory = Path.GetDirectoryName(fileName); + var fileFilter = Path.GetFileName(fileName); + directory = Path.GetFullPath(directory); + if (!Directory.Exists(directory)) + { + InternalLogger.Warn("Cannot watch file-filter '{0}' when non-existing directory: {1}", fileFilter, directory); + return; + } + + if (TryAddWatch(fileName, directory, fileFilter)) + { + InternalLogger.Debug("Start watching file-filter '{0}' in directory: {1}", fileFilter, directory); + } + } + catch (Exception ex) + { + InternalLogger.Debug(ex, "Failed to start FileSystemWatcher with file-path: {0}", fileName); + if (LogManager.ThrowExceptions) + throw; + } + } + + private bool TryAddWatch(string fileName, string directory, string fileFilter) + { + lock (_watcherMap) + { + if (_watcherMap.ContainsKey(fileName)) + return false; + + FileSystemWatcher watcher = null; + + try + { + watcher = new FileSystemWatcher + { + Path = directory, + Filter = fileFilter, + NotifyFilter = NotifyFilters + }; + + watcher.Created += OnFileChanged; + watcher.Changed += OnFileChanged; + watcher.Deleted += OnFileChanged; + watcher.Renamed += OnFileChanged; + watcher.Error += OnWatcherError; + watcher.EnableRaisingEvents = true; + + _watcherMap.Add(fileName, watcher); + return true; + } + catch (Exception ex) + { + InternalLogger.Error(ex, "Failed to start FileSystemWatcher with file-filter '{0}' in directory: {1}", fileFilter, directory); + if (ex is System.Security.SecurityException || ex is UnauthorizedAccessException || ex is NotSupportedException || ex is NotImplementedException || ex is PlatformNotSupportedException) + return false; + + if (LogManager.ThrowExceptions) + throw; + + if (watcher != null) + { + StopWatching(watcher); + } + + return false; + } + } + } + + private void StopWatching(FileSystemWatcher watcher) + { + string fileFilter = string.Empty; + string fileDirectory = string.Empty; + + try + { + fileFilter = watcher.Filter; + fileDirectory = watcher.Path; + + InternalLogger.Debug("Stop watching file-filter '{0}' in directory: {1}", fileFilter, fileDirectory); + watcher.EnableRaisingEvents = false; + watcher.Created -= OnFileChanged; + watcher.Changed -= OnFileChanged; + watcher.Deleted -= OnFileChanged; + watcher.Renamed -= OnFileChanged; + watcher.Error -= OnWatcherError; + watcher.Dispose(); + } + catch (Exception ex) + { + InternalLogger.Error(ex, "Failed to stop FileSystemWatcher with file-filter '{0}' in directory: {1}", fileFilter, fileDirectory); + if (LogManager.ThrowExceptions) + throw; + } + } + + private static void OnWatcherError(object source, ErrorEventArgs e) + { + var watcher = source as FileSystemWatcher; + var fileFilter = watcher?.Filter ?? string.Empty; + var fileDirectory = watcher?.Path ?? string.Empty; + InternalLogger.Warn(e.GetException(), "Error from FileSystemWatcher with file-filter '{0}' in directory: {1}", fileFilter, fileDirectory); + } + + private void OnFileChanged(object source, FileSystemEventArgs e) + { + var changed = FileChanged; + if (changed != null) + { + try + { + changed(source, e); + } + catch (Exception ex) + { +#if DEBUG + if (LogManager.ThrowExceptions) + throw; // Throwing exceptions here might crash the entire application (.NET 2.0 behavior) +#endif + InternalLogger.Error(ex, "Error handling event from FileSystemWatcher with file-filter: '{0}' in directory: {1}", e.Name, e.FullPath); + } + } + } + } +} diff --git a/src/NLog/Internal/MutexDetector.cs b/src/NLog.Targets.ConcurrentFile/Internal/MutexDetector.cs similarity index 91% rename from src/NLog/Internal/MutexDetector.cs rename to src/NLog.Targets.ConcurrentFile/Internal/MutexDetector.cs index 39cf54b371..c0bcbf7a88 100644 --- a/src/NLog/Internal/MutexDetector.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/MutexDetector.cs @@ -31,10 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 -#define SupportsMutex -#endif - namespace NLog.Internal { using System; @@ -59,15 +55,9 @@ private static bool ResolveSupportsSharableMutex() { try { -#if SupportsMutex -#if !NETSTANDARD - if (Environment.Version.Major < 4 && PlatformDetector.IsMono) - return false; // MONO ver. 4 is needed for named Mutex to work -#endif var mutex = BaseMutexFileAppender.ForceCreateSharableMutex("NLogMutexTester"); mutex.Close(); //"dispose" return true; -#endif } catch (Exception ex) { diff --git a/src/NLog.Targets.ConcurrentFile/Internal/PlatformDetector.cs b/src/NLog.Targets.ConcurrentFile/Internal/PlatformDetector.cs new file mode 100644 index 0000000000..035e77369e --- /dev/null +++ b/src/NLog.Targets.ConcurrentFile/Internal/PlatformDetector.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Internal +{ + using System; + + /// + /// Detects the platform the NLog is running on. + /// + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal static class PlatformDetector + { + /// + /// Gets the current runtime OS. + /// + public static PlatformSystem CurrentOS => _currentOS ?? (_currentOS = GetCurrentRuntimeOS()).Value; + private static PlatformSystem? _currentOS; + + /// + /// Gets a value indicating whether current OS is Win32-based (desktop or mobile). + /// + public static bool IsWin32 => CurrentOS == PlatformSystem.WindowsNT || CurrentOS == PlatformSystem.Windows9x; + + /// + /// Gets a value indicating whether current OS is Unix-based. + /// + public static bool IsUnix => CurrentOS == PlatformSystem.Linux || CurrentOS == PlatformSystem.MacOSX; + +#if NETFRAMEWORK + /// + /// Gets a value indicating whether current runtime is Mono-based + /// + public static bool IsMono => _isMono ?? (_isMono = Type.GetType("Mono.Runtime") != null).Value; + private static bool? _isMono; +#endif + + private static PlatformSystem GetCurrentRuntimeOS() + { +#if NETFRAMEWORK + PlatformID platformID = Environment.OSVersion.Platform; + if ((int)platformID == 4 || (int)platformID == 128) + return PlatformSystem.Linux; + if (platformID == PlatformID.Win32Windows) + return PlatformSystem.Windows9x; + if (platformID == PlatformID.Win32NT) + return PlatformSystem.WindowsNT; +#else + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux)) + return PlatformSystem.Linux; + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX)) + return PlatformSystem.MacOSX; + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) + return PlatformSystem.WindowsNT; +#endif + return PlatformSystem.Unknown; + } + } +} diff --git a/src/NLog/Internal/Xamarin/PreserveAttribute.cs b/src/NLog.Targets.ConcurrentFile/Internal/PlatformSystem.cs similarity index 72% rename from src/NLog/Internal/Xamarin/PreserveAttribute.cs rename to src/NLog.Targets.ConcurrentFile/Internal/PlatformSystem.cs index 4730b71ca3..145461aab9 100644 --- a/src/NLog/Internal/Xamarin/PreserveAttribute.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/PlatformSystem.cs @@ -31,26 +31,40 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.Internal.Xamarin +namespace NLog.Internal { - using System; - /// - /// Prevents the Xamarin linker from linking the target. + /// Supported operating systems. /// /// - /// By applying this attribute all of the members of the target will be kept as if they had been referenced by the code. + /// If you add anything here, make sure to add the appropriate detection + /// code to /// - [AttributeUsage(AttributeTargets.All)] - public sealed class PreserveAttribute : Attribute + internal enum PlatformSystem { /// - /// Ensures that all members of this type are preserved + /// Unknown operating system. + /// + Unknown, + + /// + /// Unix/Linux operating systems. /// - public bool AllMembers { get; set; } = true; + Linux, + + /// + /// Desktop versions of Windows (95,98,ME). + /// + Windows9x, + + /// + /// Windows NT, 2000, 2003 and future versions based on NT technology. + /// + WindowsNT, + /// - /// Flags the method as a method to preserve during linking if the container class is pulled in. + /// Macintosh Mac OSX /// - public bool Conditional { get; set; } + MacOSX, } } diff --git a/src/NLog/Internal/AsyncHelpersTask.cs b/src/NLog.Targets.ConcurrentFile/Internal/ReusableBufferCreator.cs similarity index 80% rename from src/NLog/Internal/AsyncHelpersTask.cs rename to src/NLog.Targets.ConcurrentFile/Internal/ReusableBufferCreator.cs index 2294b3221d..85a851eac8 100644 --- a/src/NLog/Internal/AsyncHelpersTask.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/ReusableBufferCreator.cs @@ -31,24 +31,16 @@ // THE POSSIBILITY OF SUCH DAMAGE. // - namespace NLog.Internal { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - using AsyncDelegate = System.Threading.WaitCallback; -#else - using AsyncDelegate = System.Action; -#endif - /// - /// Forward declare of system delegate type for use by other classes + /// Controls a single allocated char[]-buffer for reuse (only one active user) /// - internal struct AsyncHelpersTask + internal sealed class ReusableBufferCreator : ReusableObjectCreator { - public readonly AsyncDelegate AsyncDelegate; - public AsyncHelpersTask(AsyncDelegate asyncDelegate) + public ReusableBufferCreator(int initialCapacity) + : base(() => new char[initialCapacity], (b) => { }) { - AsyncDelegate = asyncDelegate; } } } diff --git a/src/NLog/Internal/FactoryHelper.cs b/src/NLog.Targets.ConcurrentFile/Internal/ReusableBuilderCreator.cs similarity index 67% rename from src/NLog/Internal/FactoryHelper.cs rename to src/NLog.Targets.ConcurrentFile/Internal/ReusableBuilderCreator.cs index 16be081d1c..132c04a5f5 100644 --- a/src/NLog/Internal/FactoryHelper.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/ReusableBuilderCreator.cs @@ -33,28 +33,29 @@ namespace NLog.Internal { - using System; - using NLog.Config; + using System.Text; /// - /// Object construction helper. + /// Controls a single allocated StringBuilder for reuse (only one active user) /// - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - internal static class FactoryHelper + internal sealed class ReusableBuilderCreator : ReusableObjectCreator { - internal static readonly ConfigurationItemCreator CreateInstance = DefaultCreateInstance; + private const int MaxBuilderCapacity = 40960; // Avoid Large-Object-Heap (LOH) - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Ignore since obsolete", "IL2067")] - private static object DefaultCreateInstance(Type type) + public ReusableBuilderCreator() + : base(() => new StringBuilder(512), (sb) => ResetCapacity(sb)) { - try - { - return Activator.CreateInstance(type); - } - catch (MissingMethodException exception) + } + + private static void ResetCapacity(StringBuilder stringBuilder) + { + if (stringBuilder.Length > MaxBuilderCapacity && stringBuilder.Capacity > MaxBuilderCapacity * 10) { - throw new NLogConfigurationException($"Cannot access the constructor of type: {type.FullName}. Is the required permission granted?", exception); + stringBuilder.Remove(0, stringBuilder.Length - 1); // Attempt soft clear that skips re-allocation + stringBuilder.Capacity = MaxBuilderCapacity; } + + stringBuilder.Length = 0; } } } diff --git a/tests/PackageLoaderTestAssembly/NLogPackageLoaders.cs b/src/NLog.Targets.ConcurrentFile/Internal/ReusableObjectCreator.cs similarity index 52% rename from tests/PackageLoaderTestAssembly/NLogPackageLoaders.cs rename to src/NLog.Targets.ConcurrentFile/Internal/ReusableObjectCreator.cs index 8e3937346e..c4db88d976 100644 --- a/tests/PackageLoaderTestAssembly/NLogPackageLoaders.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/ReusableObjectCreator.cs @@ -31,97 +31,66 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Diagnostics; -using NLog; -using NLog.Config; - -namespace LoaderTestPublic +namespace NLog.Internal { - public sealed class NLogPackageLoader - { - public static void Preload() - { - // Nothing to do - } - } -} + using System; -namespace LoaderTestInternal -{ /// - /// private + /// Controls a single allocated object for reuse (only one active user) /// - internal sealed class NLogPackageLoader + internal class ReusableObjectCreator where T : class { - public static void Preload() - { - // Nothing to do - } - } -} + protected T _reusableObject; + private readonly Action _clearObject; + private readonly Func _createObject; -namespace LoaderTestPrivateNested -{ - internal sealed class SomeType - { - private sealed class NLogPackageLoader + protected ReusableObjectCreator(Func createObject, Action clearObject) { - public static void Preload(ConfigurationItemFactory fact) - { - if (fact is null) - { - throw new ArgumentNullException(nameof(fact)); - } - } + _reusableObject = createObject(); + _clearObject = clearObject; + _createObject = createObject; } - } -} -namespace LoaderTestPrivateNestedStatic -{ - internal sealed class SomeType - { - private static class NLogPackageLoader + /// + /// Creates handle to the reusable char[]-buffer for active usage + /// + /// Handle to the reusable item, that can release it again + public LockOject Allocate() { - public static void Preload() - { - // Nothing to do - } + var reusableObject = _reusableObject ?? _createObject(); + System.Diagnostics.Debug.Assert(_reusableObject != null); + _reusableObject = null; + return new LockOject(this, reusableObject); } - } -} -namespace LoaderTestWrong1 -{ - public sealed class NLogPackageLoader - { - [DebuggerStepThrough] - public static void Preload() + private void Deallocate(T reusableObject) { - throw new NLogRuntimeException("ow noos"); + _clearObject(reusableObject); + _reusableObject = reusableObject; } - } -} -namespace LoaderTestWrong2 -{ - public sealed class NLogPackageLoader - { - public void Preload() + public +#if !NETFRAMEWORK + readonly +#endif + struct LockOject : IDisposable { - //im not static - } - } -} + /// + /// Access the acquired reusable object + /// + public readonly T Result; + private readonly ReusableObjectCreator _owner; -namespace LoaderTestWrong3 -{ - public sealed class NLogPackageLoader - { - public static void Preload(int arg1, int arg2) - { - //I have args + public LockOject(ReusableObjectCreator owner, T reusableObject) + { + Result = reusableObject; + _owner = owner; + } + + public void Dispose() + { + _owner?.Deallocate(Result); + } } } } diff --git a/tests/NLog.UnitTests/LayoutRenderers/FileContentsTests.cs b/src/NLog.Targets.ConcurrentFile/Internal/ReusableStreamCreator.cs similarity index 60% rename from tests/NLog.UnitTests/LayoutRenderers/FileContentsTests.cs rename to src/NLog.Targets.ConcurrentFile/Internal/ReusableStreamCreator.cs index fe77fff374..d538f373bd 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/FileContentsTests.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/ReusableStreamCreator.cs @@ -31,52 +31,49 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.UnitTests.LayoutRenderers +namespace NLog.Internal { using System; using System.IO; - using System.Text; - using Xunit; - public class FileContentsTests : NLogTestBase + /// + /// Controls a single allocated MemoryStream for reuse (only one active user) + /// + internal sealed class ReusableStreamCreator : ReusableObjectCreator, IDisposable { - [Fact] - public void FileContentUnicodeTest() + public ReusableStreamCreator() + : base(() => new MemoryStream(4096), (ms) => ResetCapacity(ms)) { - string content = "12345"; - string fileName = Guid.NewGuid().ToString("N") + ".txt"; - using (StreamWriter sw = new StreamWriter(fileName, false, Encoding.Unicode)) - { - sw.Write(content); - } - - AssertLayoutRendererOutput("${file-contents:" + fileName + ":encoding=utf-16}", content); + } - File.Delete(fileName); + public ReusableStreamCreator(bool batchStream) + : base(() => new MemoryStream(4096), (ms) => ResetBatchCapacity(ms)) + { } - [Fact] - public void FileContentUTF8Test() + private static void ResetCapacity(MemoryStream memoryStream) { - string content = "12345"; - string fileName = Guid.NewGuid().ToString("N") + ".txt"; - using (StreamWriter sw = new StreamWriter(fileName, false, Encoding.UTF8)) + memoryStream.Position = 0; + memoryStream.SetLength(0); + if (memoryStream.Capacity > 1000000) { - sw.Write(content); + memoryStream.Capacity = 81920; // Avoid Large-Object-Heap (LOH) } - - AssertLayoutRendererOutput("${file-contents:" + fileName + ":encoding=utf-8}", content); - - File.Delete(fileName); } - [Fact] - public void FileContentTest2() + private static void ResetBatchCapacity(MemoryStream memoryStream) { - using (new NoThrowNLogExceptions()) + memoryStream.Position = 0; + memoryStream.SetLength(0); + if (memoryStream.Capacity > 10000000) { - AssertLayoutRendererOutput("${file-contents:nosuchfile.txt}", string.Empty); + memoryStream.Capacity = 81920; // Avoid Large-Object-Heap (LOH) } } + + void IDisposable.Dispose() + { + _reusableObject.Dispose(); + } } } diff --git a/src/NLog.Targets.ConcurrentFile/Internal/SortHelpers.cs b/src/NLog.Targets.ConcurrentFile/Internal/SortHelpers.cs new file mode 100644 index 0000000000..7a31cdf718 --- /dev/null +++ b/src/NLog.Targets.ConcurrentFile/Internal/SortHelpers.cs @@ -0,0 +1,264 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Internal +{ + using System; + using System.Collections; + using System.Collections.Generic; + using NLog.Common; + + /// + /// Provides helpers to sort log events and associated continuations. + /// + internal static class SortHelpers + { + /// + /// Key selector delegate. + /// + /// The type of the value. + /// The type of the key. + /// Value to extract key information from. + /// Key selected from log event. + internal delegate TKey KeySelector(TValue value); + + /// + /// Performs bucket sort (group by) on an array of items and returns a dictionary for easy traversal of the result set. + /// + /// The type of the value. + /// The type of the key. + /// The inputs. + /// The key selector function. + /// + /// Dictionary where keys are unique input keys, and values are lists of . + /// + public static ReadOnlySingleBucketGroupBy> BucketSort(this IList inputs, KeySelector keySelector) + { + return BucketSort(inputs, keySelector, EqualityComparer.Default); + } + + /// + /// Performs bucket sort (group by) on an array of items and returns a dictionary for easy traversal of the result set. + /// + /// The type of the value. + /// The type of the key. + /// The inputs. + /// The key selector function. + /// The key comparer function. + /// + /// Dictionary where keys are unique input keys, and values are lists of . + /// + public static ReadOnlySingleBucketGroupBy> BucketSort(this IList inputs, KeySelector keySelector, IEqualityComparer keyComparer) + { + if (inputs.Count == 0) + return new ReadOnlySingleBucketGroupBy>(singleBucket: null, keyComparer); + + Dictionary> buckets = null; + TKey singleBucketKey = keySelector(inputs[0]); + for (int i = 1; i < inputs.Count; i++) + { + TKey keyValue = keySelector(inputs[i]); + if (buckets is null) + { + if (!keyComparer.Equals(singleBucketKey, keyValue)) + { + // Multiple buckets needed, allocate full dictionary + buckets = CreateBucketDictionaryWithValue(inputs, keyComparer, i, singleBucketKey, keyValue); + } + } + else + { + if (!buckets.TryGetValue(keyValue, out var eventsInBucket)) + { + eventsInBucket = new List(); + buckets.Add(keyValue, eventsInBucket); + } + eventsInBucket.Add(inputs[i]); + } + } + + if (buckets is null) + return new ReadOnlySingleBucketGroupBy>(new KeyValuePair>(singleBucketKey, inputs), keyComparer); + else + return new ReadOnlySingleBucketGroupBy>(buckets, keyComparer); + } + + private static Dictionary> CreateBucketDictionaryWithValue(IList inputs, IEqualityComparer keyComparer, int currentIndex, TKey firstBucketKey, TKey nextBucketKey) + { + var buckets = new Dictionary>(keyComparer); + var firstBucket = new List(currentIndex); + for (int i = 0; i < currentIndex; i++) + { + firstBucket.Add(inputs[i]); + } + buckets[firstBucketKey] = firstBucket; + + var nextBucket = new List { inputs[currentIndex] }; + buckets[nextBucketKey] = nextBucket; + return buckets; + } + + /// + /// Single-Bucket optimized readonly dictionary. Uses normal internally Dictionary if multiple buckets are needed. + /// + /// Avoids allocating a new dictionary, when all items are using the same bucket + /// + /// The type of the key. + /// The type of the value. + public +#if !NETFRAMEWORK + readonly +#endif + struct ReadOnlySingleBucketGroupBy : IEnumerable> + { + private +#if !NETFRAMEWORK + readonly +#endif + KeyValuePair? _singleBucket; // Not readonly to avoid struct-copy, and to avoid VerificationException when medium-trust AppDomain + private readonly Dictionary _multiBucket; + private readonly IEqualityComparer _comparer; + public IEqualityComparer Comparer => _comparer; + + public ReadOnlySingleBucketGroupBy(KeyValuePair singleBucket) + : this(singleBucket, EqualityComparer.Default) + { + } + + public ReadOnlySingleBucketGroupBy(Dictionary multiBucket) + : this(multiBucket, EqualityComparer.Default) + { + } + + public ReadOnlySingleBucketGroupBy(KeyValuePair? singleBucket, IEqualityComparer comparer) + { + _comparer = comparer; + _multiBucket = null; + _singleBucket = singleBucket; + } + + public ReadOnlySingleBucketGroupBy(Dictionary multiBucket, IEqualityComparer comparer) + { + _comparer = comparer; + _multiBucket = multiBucket; + _singleBucket = default(KeyValuePair); + } + + /// + public int Count => _multiBucket?.Count ?? (_singleBucket.HasValue ? 1 : 0); + + /// + /// Non-Allocating struct-enumerator + /// + public struct Enumerator : IEnumerator> + { + bool _singleBucketFirstRead; + KeyValuePair _singleBucket; // Not readonly to avoid struct-copy, and to avoid VerificationException when medium-trust AppDomain + readonly IEnumerator> _multiBuckets; + + internal Enumerator(Dictionary multiBucket) + { + _singleBucketFirstRead = false; + _singleBucket = default(KeyValuePair); + _multiBuckets = multiBucket.GetEnumerator(); + } + + internal Enumerator(KeyValuePair singleBucket) + { + _singleBucketFirstRead = false; + _singleBucket = singleBucket; + _multiBuckets = null; + } + + public KeyValuePair Current + { + get + { + if (_multiBuckets != null) + return _multiBuckets.Current; + else + return new KeyValuePair(_singleBucket.Key, _singleBucket.Value); + } + } + + object IEnumerator.Current => Current; + + public void Dispose() + { + if (_multiBuckets != null) + _multiBuckets.Dispose(); + } + + public bool MoveNext() + { + if (_multiBuckets != null) + return _multiBuckets.MoveNext(); + else if (_singleBucketFirstRead) + return false; + else + return _singleBucketFirstRead = true; + } + + public void Reset() + { + if (_multiBuckets != null) + _multiBuckets.Reset(); + else + _singleBucketFirstRead = false; + } + } + + public Enumerator GetEnumerator() + { + if (_multiBucket != null) + return new Enumerator(_multiBucket); + else if (_singleBucket.HasValue) + return new Enumerator(_singleBucket.Value); + else + return new Enumerator(new Dictionary()); + } + + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + } +} diff --git a/src/NLog/Internal/StreamHelpers.cs b/src/NLog.Targets.ConcurrentFile/Internal/StreamHelpers.cs similarity index 70% rename from src/NLog/Internal/StreamHelpers.cs rename to src/NLog.Targets.ConcurrentFile/Internal/StreamHelpers.cs index 13b29e63d1..9f9186645c 100644 --- a/src/NLog/Internal/StreamHelpers.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/StreamHelpers.cs @@ -31,19 +31,24 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.IO; -using System.Linq; -using System.Text; -using NLog.Common; - namespace NLog.Internal { + using System; + using System.IO; + using System.Linq; + using System.Text; + using NLog.Common; + /// /// Stream helpers /// internal static class StreamHelpers { + /// + /// UTF-8 Byte Order Mark (BOM) bytes: EF BB BF (239, 187, 191). + /// + private static readonly byte[] Utf8BOM = Encoding.UTF8.GetPreamble(); + /// /// Copy to output stream and skip BOM if encoding is UTF8 /// @@ -52,13 +57,13 @@ internal static class StreamHelpers /// public static void CopyAndSkipBom(this Stream input, Stream output, Encoding encoding) { - var bomSize = EncodingHelpers.Utf8BOM.Length; + var bomSize = Utf8BOM.Length; var bomBuffer = new byte[bomSize]; var posBefore = input.Position; int bytesRead = input.Read(bomBuffer, 0, bomSize); //TODO support other BOMs, like UTF16 - if (bytesRead == bomSize && bomBuffer.SequenceEqual(EncodingHelpers.Utf8BOM)) + if (bytesRead == bomSize && bomBuffer.SequenceEqual(Utf8BOM)) { InternalLogger.Debug("input has UTF8 BOM"); //already skipped due to read @@ -111,5 +116,32 @@ public static void CopyWithOffset(this Stream input, Stream output, int offset) output.Write(buffer, 0, read); } } + + /// + /// Copies the contents of the StringBuilder to the MemoryStream using the specified encoding (Without BOM/Preamble) + /// + /// StringBuilder source + /// MemoryStream destination + /// Encoding used for converter string into byte-stream + /// Helper char-buffer to minimize memory allocations + public static void CopyToStream(this StringBuilder builder, MemoryStream ms, Encoding encoding, char[] transformBuffer) + { + int charCount; + int byteCount = encoding.GetMaxByteCount(builder.Length); + long position = ms.Position; + ms.SetLength(position + byteCount); + for (int i = 0; i < builder.Length; i += transformBuffer.Length) + { + charCount = Math.Min(builder.Length - i, transformBuffer.Length); + builder.CopyTo(i, transformBuffer, 0, charCount); + byteCount = encoding.GetBytes(transformBuffer, 0, charCount, ms.GetBuffer(), (int)position); + position += byteCount; + } + ms.Position = position; + if (position != ms.Length) + { + ms.SetLength(position); + } + } } } diff --git a/src/NLog.Targets.ConcurrentFile/Internal/StringHelpers.cs b/src/NLog.Targets.ConcurrentFile/Internal/StringHelpers.cs new file mode 100644 index 0000000000..2f75552012 --- /dev/null +++ b/src/NLog.Targets.ConcurrentFile/Internal/StringHelpers.cs @@ -0,0 +1,73 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets +{ + using System.Text; + + internal static class StringHelpers + { + /// + /// IsNullOrWhiteSpace, including for .NET 3.5 + /// + internal static bool IsNullOrWhiteSpace(string value) + { +#if !NET35 + return string.IsNullOrWhiteSpace(value); +#else + if (value is null || value.Length == 0) + return true; + return string.IsNullOrEmpty(value.Trim()); +#endif + } + + /// + /// Compares the contents of a StringBuilder and a String + /// + /// when content is the same + public static bool EqualTo(this StringBuilder builder, string other) + { + if (builder.Length != other.Length) + return false; + + int i = 0; + foreach (var chr in other) + { + if (builder[i++] != chr) + return false; + } + + return true; + } + } +} diff --git a/src/NLog/Internal/Win32FileNativeMethods.cs b/src/NLog.Targets.ConcurrentFile/Internal/Win32FileNativeMethods.cs similarity index 99% rename from src/NLog/Internal/Win32FileNativeMethods.cs rename to src/NLog.Targets.ConcurrentFile/Internal/Win32FileNativeMethods.cs index 045392948a..500b469ba3 100644 --- a/src/NLog/Internal/Win32FileNativeMethods.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/Win32FileNativeMethods.cs @@ -31,7 +31,7 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD +#if NETFRAMEWORK namespace NLog.Internal { diff --git a/src/NLog/Targets/ZipArchiveFileCompressor.cs b/src/NLog.Targets.ConcurrentFile/Internal/ZipArchiveFileCompressor.cs similarity index 93% rename from src/NLog/Targets/ZipArchiveFileCompressor.cs rename to src/NLog.Targets.ConcurrentFile/Internal/ZipArchiveFileCompressor.cs index 2fc0cd42e6..eaf67aa6d0 100644 --- a/src/NLog/Targets/ZipArchiveFileCompressor.cs +++ b/src/NLog.Targets.ConcurrentFile/Internal/ZipArchiveFileCompressor.cs @@ -40,8 +40,8 @@ namespace NLog.Targets /// /// Builtin IFileCompressor implementation utilizing the .Net4.5 specific - /// and is used as the default value for on .Net4.5. - /// So log files created via can be zipped when archived + /// and is used as the default value for on .Net4.5. + /// So log files created via can be zipped when archived /// w/o 3rd party zip library when run on .Net4.5 or higher. /// internal sealed class ZipArchiveFileCompressor : IArchiveFileCompressor diff --git a/src/NLog.Targets.ConcurrentFile/NLog.Targets.ConcurrentFile.csproj b/src/NLog.Targets.ConcurrentFile/NLog.Targets.ConcurrentFile.csproj new file mode 100644 index 0000000000..81c4870d4b --- /dev/null +++ b/src/NLog.Targets.ConcurrentFile/NLog.Targets.ConcurrentFile.csproj @@ -0,0 +1,93 @@ + + + + net35;net46;netstandard2.0 + + NLog.Targets.ConcurrentFile + NLog + FileTarget with support for ConcurrentWrites with Global Mutex and EnableArchiveFileCompression with ZIP + NLog.Targets.ConcurrentFile v$(ProductVersion) + $(ProductVersion) + Jarek Kowalski,Kim Christensen,Julian Verdurmen + $([System.DateTime]::Now.ToString(yyyy)) + Copyright (c) 2004-$(CurrentYear) NLog Project - https://nlog-project.org/ + + + FileTarget Docs: + https://github.com/NLog/NLog/wiki/File-target + + README.md + NLog;File;Archive;Compression;Zip;NTFS;logging;log + N.png + https://nlog-project.org/ + BSD-3-Clause + git + https://github.com/NLog/NLog.git + + true + 6.0.0.0 + ..\NLog.snk + true + + true + true + true + true + true + true + true + + + + NLog.Targets.ConcurrentFile for .NET Framework 4.6 + true + + + + NLog.Targets.ConcurrentFile for .NET Framework 4.5 + true + + + + NLog.Targets.ConcurrentFile for .NET Framework 3.5 + true + + + + NLog.Targets.ConcurrentFile for NetStandard 2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NLog.Targets.ConcurrentFile/Properties/AssemblyInfo.cs b/src/NLog.Targets.ConcurrentFile/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b905c62788 --- /dev/null +++ b/src/NLog.Targets.ConcurrentFile/Properties/AssemblyInfo.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] +[assembly: InternalsVisibleTo("NLog.Targets.ConcurrentFile.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ef8eab4fbdeb511eeb475e1659fe53f00ec1c1340700f1aa347bf3438455d71993b28b1efbed44c8d97a989e0cb6f01bcb5e78f0b055d311546f63de0a969e04cf04450f43834db9f909e566545a67e42822036860075a1576e90e1c43d43e023a24c22a427f85592ae56cac26f13b7ec2625cbc01f9490d60f16cfbb1bc34d9")] +[assembly: AllowPartiallyTrustedCallers] +#if !NET35 +[assembly: SecurityRules(SecurityRuleSet.Level1)] +#endif +// NSubstitute +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/NLog.Targets.ConcurrentFile/README.md b/src/NLog.Targets.ConcurrentFile/README.md new file mode 100644 index 0000000000..9cb8cba78e --- /dev/null +++ b/src/NLog.Targets.ConcurrentFile/README.md @@ -0,0 +1,25 @@ +# NLog Concurrent File Target + +NLog File Target with support for ConcurrentWrites where multiple processes can write to the same file. + +If having trouble with output, then check [NLog InternalLogger](https://github.com/NLog/NLog/wiki/Internal-Logging) for clues. See also [Troubleshooting NLog](https://github.com/NLog/NLog/wiki/Logging-Troubleshooting) + +See the [NLog Wiki](https://github.com/NLog/NLog/wiki/File-target) for available options and examples. + +## Register Extension + +NLog will only recognize type-alias `File` when loading from `NLog.config`-file, if having added extension to `NLog.config`-file: + +```xml + + + +``` + +Alternative register from code using [fluent configuration API](https://github.com/NLog/NLog/wiki/Fluent-Configuration-API): + +```csharp +LogManager.Setup().SetupExtensions(ext => { + ext.RegisterTarget(); +}); +``` diff --git a/src/NLog/Targets/Win32FileAttributes.cs b/src/NLog.Targets.ConcurrentFile/Win32FileAttributes.cs similarity index 100% rename from src/NLog/Targets/Win32FileAttributes.cs rename to src/NLog.Targets.ConcurrentFile/Win32FileAttributes.cs diff --git a/src/NLog.Targets.GZipFile/GZipFileTarget.cs b/src/NLog.Targets.GZipFile/GZipFileTarget.cs new file mode 100644 index 0000000000..e81d774ed3 --- /dev/null +++ b/src/NLog.Targets.GZipFile/GZipFileTarget.cs @@ -0,0 +1,105 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets +{ + using System.IO; + using System.IO.Compression; + + /// + /// Extended standard FileTarget with GZip compression as part of file-logging + /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + [Target("GZipFile")] + public class GZipFileTarget : FileTarget + { + /// + /// Initializes a new instance of the class. + /// + public GZipFileTarget() + { + ArchiveOldFileOnStartup = true; // Not possible to append to existing file using GZip, must read the entire file into memory and rewrite everything again + } + + /// + /// Initializes a new instance of the class. + /// + public GZipFileTarget(string name) : this() + { + Name = name; + } + + /// + /// Gets or sets whether to enable file-compression using + /// + public bool EnableArchiveFileCompression { get; set; } = true; + + /// + /// Gets or sets whether to emphasize Fastest-speed or Optimal-compression + /// + public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Fastest; + + /// + protected override void InitializeTarget() + { + base.InitializeTarget(); + + if (!KeepFileOpen) + throw new NLogConfigurationException("GZipFileTarget requires KeepFileOpen = true"); + + if (!ArchiveOldFileOnStartup) + throw new NLogConfigurationException("GZipFileTarget requires ArchiveOldFileOnStartup = true"); + } + + /// + protected override Stream CreateFileStream(string filePath, int bufferSize) + { + if (!EnableArchiveFileCompression || CompressionLevel == CompressionLevel.NoCompression || !ArchiveOldFileOnStartup || !KeepFileOpen) + { + NLog.Common.InternalLogger.Debug("{0}: File Compression has been disabled, fallback to normal FileStream", this); + return base.CreateFileStream(filePath, bufferSize); + } + + var underlyingStream = base.CreateFileStream(filePath, bufferSize); + var compressStream = new GZipStream(underlyingStream, CompressionLevel); + + if (!AutoFlush && BufferSize > 0) + return new BufferedStream(compressStream, BufferSize); + else + return compressStream; + } + } +} diff --git a/src/NLog.Targets.GZipFile/NLog.Targets.GZipFile.csproj b/src/NLog.Targets.GZipFile/NLog.Targets.GZipFile.csproj new file mode 100644 index 0000000000..fcca620b86 --- /dev/null +++ b/src/NLog.Targets.GZipFile/NLog.Targets.GZipFile.csproj @@ -0,0 +1,83 @@ + + + + 17.0 + net46;netstandard2.0 + net46;netstandard2.0;netstandard2.1 + + NLog.Targets.GZipFile + NLog + FileTarget with support for writing GZip FileStream + NLog.Targets.GZipFile v$(ProductVersion) + $(ProductVersion) + Jarek Kowalski,Kim Christensen,Julian Verdurmen + $([System.DateTime]::Now.ToString(yyyy)) + Copyright (c) 2004-$(CurrentYear) NLog Project - https://nlog-project.org/ + + + GZipFile Target Docs: + https://github.com/nlog/nlog/wiki/GZip-File-target + + NLog;File;Archive;GZip;logging;log + N.png + https://nlog-project.org/ + BSD-3-Clause + git + https://github.com/NLog/NLog.git + + true + 6.0.0.0 + ..\NLog.snk + true + + true + true + true + enable + 9 + true + true + true + true + + + + NLog.Targets.GZipFile for .NET Framework 4.6 + true + + + + NLog.Targets.GZipFile for .NET Framework 4.5 + true + + + + NLog.Targets.GZipFile for NetStandard 2.0 + + + + NLog.Targets.GZipFile for NetStandard 2.1 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NLog.Targets.GZipFile/Properties/AssemblyInfo.cs b/src/NLog.Targets.GZipFile/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3c9d9e1f4a --- /dev/null +++ b/src/NLog.Targets.GZipFile/Properties/AssemblyInfo.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security; + +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] +[assembly: AllowPartiallyTrustedCallers] +#if !NET35 +[assembly: SecurityRules(SecurityRuleSet.Level1)] +#endif diff --git a/src/NLog/Internal/ISmtpClient.cs b/src/NLog.Targets.Mail/ISmtpClient.cs similarity index 98% rename from src/NLog/Internal/ISmtpClient.cs rename to src/NLog.Targets.Mail/ISmtpClient.cs index 34bf7ca384..395171f828 100644 --- a/src/NLog/Internal/ISmtpClient.cs +++ b/src/NLog.Targets.Mail/ISmtpClient.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.Internal { using System; @@ -86,5 +84,3 @@ internal interface ISmtpClient : IDisposable string PickupDirectoryLocation { get; set; } } } - -#endif diff --git a/src/NLog/Targets/MailTarget.cs b/src/NLog.Targets.Mail/MailTarget.cs similarity index 91% rename from src/NLog/Targets/MailTarget.cs rename to src/NLog.Targets.Mail/MailTarget.cs index b7ffb4fbbc..625619309d 100644 --- a/src/NLog/Targets/MailTarget.cs +++ b/src/NLog.Targets.Mail/MailTarget.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.Targets { using System; @@ -86,13 +84,10 @@ namespace NLog.Targets [Target("Mail")] [Target("Email")] [Target("Smtp")] - [Target("SmtpClient")] public class MailTarget : TargetWithLayoutHeaderAndFooter { private const string RequiredPropertyIsEmptyFormat = "After the processing of the MailTarget's '{0}' property it appears to be empty. The email message will not be sent."; - private Layout _from; - /// /// Initializes a new instance of the class. /// @@ -117,14 +112,14 @@ public MailTarget(string name) : this() } #if NETFRAMEWORK - private SmtpSection _currentailSettings; + private SmtpSection? _currentailSettings; /// /// Gets the mailSettings/smtp configuration from app.config in cases when we need those configuration. /// E.g when UseSystemNetMailSettings is enabled and we need to read the From attribute from system.net/mailSettings/smtp /// /// Internal for mocking - internal SmtpSection SmtpSection + internal SmtpSection? SmtpSection { get { @@ -155,7 +150,6 @@ internal SmtpSection SmtpSection /// Gets or sets sender's email address (e.g. joe@domain.com). /// /// - [RequiredParameter] public Layout From { get @@ -167,40 +161,42 @@ public Layout From // It will do so only if the 'From' attribute in system.net/mailSettings/smtp is not empty. //only use from config when not set in current - if (UseSystemNetMailSettings && _from is null) + if (UseSystemNetMailSettings && (_from is null || ReferenceEquals(_from, Layout.Empty))) { - var from = SmtpSection.From; - return from; + var from = SmtpSection?.From; + if (from is null || string.IsNullOrEmpty(from)) + return Layout.Empty; + _from = from; } #endif return _from; } set { _from = value; } } + private Layout _from = Layout.Empty; /// /// Gets or sets recipients' email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). /// /// - [RequiredParameter] - public Layout To { get; set; } + public Layout To { get; set; } = Layout.Empty; /// /// Gets or sets CC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). /// /// - public Layout CC { get; set; } + public Layout CC { get; set; } = Layout.Empty; /// /// Gets or sets BCC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). /// /// - public Layout Bcc { get; set; } + public Layout Bcc { get; set; } = Layout.Empty; /// /// Gets or sets a value indicating whether to add new lines between log entries. /// - /// A value of true if new lines should be added; otherwise, false. + /// A value of if new lines should be added; otherwise, . /// public bool AddNewLines { get; set; } @@ -208,7 +204,6 @@ public Layout From /// Gets or sets the mail subject. /// /// - [RequiredParameter] public Layout Subject { get; set; } = "Message from NLog on ${machinename}"; /// @@ -238,7 +233,7 @@ public Layout Body /// Gets or sets SMTP Server to be used for sending. /// /// - public Layout SmtpServer { get; set; } + public Layout SmtpServer { get; set; } = Layout.Empty; /// /// Gets or sets SMTP Authentication mode. @@ -250,13 +245,13 @@ public Layout Body /// Gets or sets the username used to connect to SMTP server (used when SmtpAuthentication is set to "basic"). /// /// - public Layout SmtpUserName { get; set; } + public Layout SmtpUserName { get; set; } = Layout.Empty; /// /// Gets or sets the password used to authenticate against SMTP server (used when SmtpAuthentication is set to "basic"). /// /// - public Layout SmtpPassword { get; set; } + public Layout SmtpPassword { get; set; } = Layout.Empty; /// /// Gets or sets a value indicating whether SSL (secure sockets layer) should be used when communicating with SMTP server. @@ -286,18 +281,18 @@ public Layout Body /// Gets or sets the folder where applications save mail messages to be processed by the local SMTP server. /// /// - public Layout PickupDirectoryLocation { get; set; } + public Layout PickupDirectoryLocation { get; set; } = Layout.Empty; /// /// Gets or sets the priority used for sending mails. /// /// - public Layout Priority { get; set; } + public Layout Priority { get; set; } = MailPriority.Normal; /// /// Gets or sets a value indicating whether NewLine characters in the body should be replaced with
tags. ///
- /// Only happens when is set to true. + /// Only happens when is set to . /// public bool ReplaceNewlineWithBrTagInHtml { get; set; } @@ -531,14 +526,24 @@ internal static string ConvertDirectoryLocation(string pickupDirectoryLocation) private void CheckRequiredParameters() { - if (!UseSystemNetMailSettings && DeliveryMethod == SmtpDeliveryMethod.Network && SmtpServer is null) + if (To is null || ReferenceEquals(To, Layout.Empty)) + { + throw new NLogConfigurationException($"MailTarget '{nameof(To)}'-property must be assigned. Destination To-address required for email."); + } + + if (From is null || ReferenceEquals(From, Layout.Empty)) { - throw new NLogConfigurationException($"The MailTarget's '{nameof(SmtpServer)}' properties are not set - but needed because useSystemNetMailSettings=false and DeliveryMethod=Network. The email message will not be sent."); + throw new NLogConfigurationException($"MailTarget '{nameof(From)}'-property must be assigned. Sender From-address required for email."); } - if (!UseSystemNetMailSettings && DeliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory && PickupDirectoryLocation is null) + if (!UseSystemNetMailSettings && DeliveryMethod == SmtpDeliveryMethod.Network && (SmtpServer is null || ReferenceEquals(SmtpServer, Layout.Empty))) { - throw new NLogConfigurationException($"The MailTarget's '{nameof(PickupDirectoryLocation)}' properties are not set - but needed because useSystemNetMailSettings=false and DeliveryMethod=SpecifiedPickupDirectory. The email message will not be sent."); + throw new NLogConfigurationException($"MailTarget '{nameof(SmtpServer)}'-property must be assigned. Required because useSystemNetMailSettings=false and DeliveryMethod=Network."); + } + + if (!UseSystemNetMailSettings && DeliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory && (PickupDirectoryLocation is null || ReferenceEquals(PickupDirectoryLocation, Layout.Empty))) + { + throw new NLogConfigurationException($"MailTarget '{nameof(PickupDirectoryLocation)}'-property must be assigned. Required because useSystemNetMailSettings=false and DeliveryMethod=SpecifiedPickupDirectory."); } } @@ -640,5 +645,3 @@ private bool AddAddresses(MailAddressCollection mailAddressCollection, Layout la } } } - -#endif diff --git a/src/NLog/Internal/MySmtpClient.cs b/src/NLog.Targets.Mail/MySmtpClient.cs similarity index 98% rename from src/NLog/Internal/MySmtpClient.cs rename to src/NLog.Targets.Mail/MySmtpClient.cs index 1730720ef1..ebbe4fb69b 100644 --- a/src/NLog/Internal/MySmtpClient.cs +++ b/src/NLog.Targets.Mail/MySmtpClient.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.Internal { using System.Net.Mail; @@ -62,5 +60,3 @@ public void Dispose() #endif } } - -#endif diff --git a/src/NLog.Targets.Mail/NLog.Targets.Mail.csproj b/src/NLog.Targets.Mail/NLog.Targets.Mail.csproj new file mode 100644 index 0000000000..3b9616eccf --- /dev/null +++ b/src/NLog.Targets.Mail/NLog.Targets.Mail.csproj @@ -0,0 +1,89 @@ + + + + 17.0 + net35;net46;netstandard2.0 + net35;net46;netstandard2.0;netstandard2.1 + + NLog.Targets.Mail + NLog + NLog.Targets.Mail for sending emails using the .NET SmtpClient + NLog.Targets.Mail v$(ProductVersion) + $(ProductVersion) + Jarek Kowalski,Kim Christensen,Julian Verdurmen + $([System.DateTime]::Now.ToString(yyyy)) + Copyright (c) 2004-$(CurrentYear) NLog Project - https://nlog-project.org/ + + + MailTarget Docs: + https://github.com/nlog/NLog/wiki/Mail-target + + README.md + NLog;Smtp;SmtpClient;mail;email;logging;log + N.png + https://nlog-project.org/ + BSD-3-Clause + git + https://github.com/NLog/NLog.git + + true + 6.0.0.0 + ..\NLog.snk + true + + true + true + true + enable + 9 + true + true + true + + + + NLog.Targets.Mail for .NET Framework 4.6 + true + + + + NLog.Targets.Mail for .NET Framework 4.5 + true + + + + NLog.Targets.Mail for .NET Framework 3.5 + true + + + + NLog.Targets.Mail for NetStandard 2.0 + + + + NLog.Targets.Mail for NetStandard 2.1 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NLog.Targets.Mail/Properties/AssemblyInfo.cs b/src/NLog.Targets.Mail/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..31d35aba33 --- /dev/null +++ b/src/NLog.Targets.Mail/Properties/AssemblyInfo.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] +[assembly: InternalsVisibleTo("NLog.Targets.Mail.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ef8eab4fbdeb511eeb475e1659fe53f00ec1c1340700f1aa347bf3438455d71993b28b1efbed44c8d97a989e0cb6f01bcb5e78f0b055d311546f63de0a969e04cf04450f43834db9f909e566545a67e42822036860075a1576e90e1c43d43e023a24c22a427f85592ae56cac26f13b7ec2625cbc01f9490d60f16cfbb1bc34d9")] +[assembly: AllowPartiallyTrustedCallers] +#if !NET35 +[assembly: SecurityRules(SecurityRuleSet.Level1)] +#endif diff --git a/src/NLog.Targets.Mail/README.md b/src/NLog.Targets.Mail/README.md new file mode 100644 index 0000000000..0f8613b8e0 --- /dev/null +++ b/src/NLog.Targets.Mail/README.md @@ -0,0 +1,25 @@ +# NLog Mail Target + +NLog Mail Target for sending email using .NET SmtpClient for each logevent. + +If having trouble with output, then check [NLog InternalLogger](https://github.com/NLog/NLog/wiki/Internal-Logging) for clues. See also [Troubleshooting NLog](https://github.com/NLog/NLog/wiki/Logging-Troubleshooting) + +See the [NLog Wiki](https://github.com/NLog/NLog/wiki/Mail-target) for available options and examples. + +## Register Extension + +NLog will only recognize type-alias `Mail` when loading from `NLog.config`-file, if having added extension to `NLog.config`-file: + +```xml + + + +``` + +Alternative register from code using [fluent configuration API](https://github.com/NLog/NLog/wiki/Fluent-Configuration-API): + +```csharp +LogManager.Setup().SetupExtensions(ext => { + ext.RegisterTarget(); +}); +``` diff --git a/src/NLog/Targets/SmtpAuthenticationMode.cs b/src/NLog.Targets.Mail/SmtpAuthenticationMode.cs similarity index 97% rename from src/NLog/Targets/SmtpAuthenticationMode.cs rename to src/NLog.Targets.Mail/SmtpAuthenticationMode.cs index 2360cac3fa..372545df99 100644 --- a/src/NLog/Targets/SmtpAuthenticationMode.cs +++ b/src/NLog.Targets.Mail/SmtpAuthenticationMode.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.Targets { /// @@ -56,5 +54,3 @@ public enum SmtpAuthenticationMode Ntlm, } } - -#endif diff --git a/src/NLog/Internal/INetworkInterfaceRetriever.cs b/src/NLog.Targets.Network/INetworkInterfaceRetriever.cs similarity index 97% rename from src/NLog/Internal/INetworkInterfaceRetriever.cs rename to src/NLog.Targets.Network/INetworkInterfaceRetriever.cs index e7ec1b7ef2..3d6dc245bb 100644 --- a/src/NLog/Internal/INetworkInterfaceRetriever.cs +++ b/src/NLog.Targets.Network/INetworkInterfaceRetriever.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.Internal { using System.Net.NetworkInformation; @@ -48,4 +46,3 @@ internal interface INetworkInterfaceRetriever NetworkInterface[] AllNetworkInterfaces { get; } } } -#endif diff --git a/src/NLog/Targets/ChainsawTarget.cs b/src/NLog.Targets.Network/Internal/PlatformDetector.cs similarity index 54% rename from src/NLog/Targets/ChainsawTarget.cs rename to src/NLog.Targets.Network/Internal/PlatformDetector.cs index 3791b5820c..14f314873b 100644 --- a/src/NLog/Targets/ChainsawTarget.cs +++ b/src/NLog.Targets.Network/Internal/PlatformDetector.cs @@ -31,45 +31,41 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.Targets +namespace NLog.Targets.Internal { + using System; + /// - /// Sends log messages to the remote instance of Chainsaw application from log4j. + /// Detects the platform the NLog is running on. /// - /// - /// See NLog Wiki - /// - /// Documentation on NLog Wiki - /// - ///

- /// To set up the target in the configuration file, - /// use the following syntax: - ///

- /// - ///

- /// To set up the log target programmatically use code like this: - ///

- /// - ///
- [Target("Chainsaw")] - public class ChainsawTarget : NLogViewerTarget + internal static class PlatformDetector { /// - /// Initializes a new instance of the class. + /// Gets the current runtime OS. /// - public ChainsawTarget() - { - IncludeNLogData = false; - IncludeEventProperties = true; - } + public static PlatformOS CurrentOS => _currentOS ?? (_currentOS = GetCurrentPlatformOS()).Value; + private static PlatformOS? _currentOS; - /// - /// Initializes a new instance of the class with a name. - /// - /// Name of the target. - public ChainsawTarget(string name) : this() + + private static PlatformOS GetCurrentPlatformOS() { - Name = name; +#if NETFRAMEWORK + PlatformID platformID = Environment.OSVersion.Platform; + if ((int)platformID == 4 || (int)platformID == 128) + return PlatformOS.Linux; + if (platformID == PlatformID.Win32NT || platformID == PlatformID.Win32Windows) + return PlatformOS.Windows; +#else + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux)) + return PlatformOS.Linux; + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX)) + return PlatformOS.MacOSX; + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) + return PlatformOS.Windows; + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Create("ANDROID"))) + return PlatformOS.Linux; +#endif + return PlatformOS.Unknown; } } } diff --git a/src/NLog/Config/ConfigurationItemCreator.cs b/src/NLog.Targets.Network/Internal/PlatformOS.cs similarity index 74% rename from src/NLog/Config/ConfigurationItemCreator.cs rename to src/NLog.Targets.Network/Internal/PlatformOS.cs index d8e1bea7a3..1bfa073180 100644 --- a/src/NLog/Config/ConfigurationItemCreator.cs +++ b/src/NLog.Targets.Network/Internal/PlatformOS.cs @@ -31,16 +31,31 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.Config +namespace NLog.Targets.Internal { - using System; - /// - /// Obsolete since dynamic tyope loading is not compatible with publish as trimmed application. - /// Constructs a new instance the configuration item (target, layout, layout renderer, etc.) given its type. + /// Supported operating systems. /// - /// Type of the item. - /// Created object of the specified type. - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - public delegate object ConfigurationItemCreator(Type itemType); + internal enum PlatformOS + { + /// + /// Unknown operating system. + /// + Unknown, + + /// + /// Unix/Linux operating systems. + /// + Linux, + + /// + /// Windows operating systems. + /// + Windows, + + /// + /// Macintosh Mac OSX + /// + MacOSX, + } } diff --git a/src/NLog.Targets.Network/Layouts/GelfLayout.cs b/src/NLog.Targets.Network/Layouts/GelfLayout.cs new file mode 100644 index 0000000000..b4735872af --- /dev/null +++ b/src/NLog.Targets.Network/Layouts/GelfLayout.cs @@ -0,0 +1,558 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Layouts +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using NLog.Config; + using NLog.LayoutRenderers; + using NLog.Targets; + + /// + /// GELF (Graylog Extended Log Format) is a JSON-based, structured log format for Graylog Log Management. + /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + /// + /// { + /// "version": "1.1", + /// "host": "example.org", + /// "short_message": "A short message that helps you identify what is going on", + /// "full_message": "Backtrace here\n\nmore stuff", + /// "timestamp": 1385053862.3072, + /// "level": 1, + /// "_user_id": 9001, + /// "_some_info": "foo", + /// "_some_env_var": "bar" + /// } + /// + [Layout("GelfLayout")] + [ThreadAgnostic] + [AppDomainFixedOutput] + public class GelfLayout : CompoundLayout + { + private IJsonConverter JsonConverter => _jsonConverter ?? (_jsonConverter = ResolveService()); + private IJsonConverter? _jsonConverter; + + /// + /// Gets or sets Graylog Message Host-field + /// + public Layout GelfHostName + { + get => _gelfHostName; + set + { + _gelfHostName = value; + _gelfHostNameString = null; + } + } + private Layout _gelfHostName; + private string? _gelfHostNameString; + + /// + /// Gets or sets the Graylog Message Short-Message-field + /// + /// Will truncate when longer than 250 chars + public Layout GelfShortMessage { get; set; } + + /// + /// Gets or sets the Graylog Message Full-Message-field + /// + public Layout GelfFullMessage { get; set; } + + /// + /// Gets or sets whether to activate the legacy Graylog Message Facility-field + /// + /// + /// Activated legacy GELF v1.0 format + /// + public Layout GelfFacility + { + get => _gelfFacility; + set + { + _gelfFacility = value; + _gelfFacilityString = null; + } + } + private Layout _gelfFacility = Layout.Empty; + private string? _gelfFacilityString; + + /// + /// Gets or sets GELF additional fields + /// + [ArrayParameter(typeof(TargetPropertyWithContext), "GelfField")] + public List GelfFields { get; } = new List(); + + /// + /// Gets or sets the option to include all properties from the log events + /// + public bool IncludeEventProperties { get; set; } + + /// + /// Gets or sets whether to include the contents of the properties-dictionary. + /// + public bool IncludeScopeProperties { get; set; } + + /// + /// Gets or sets the option to exclude null/empty properties from the log event (as JSON) + /// + public bool ExcludeEmptyProperties { get; set; } + + /// + /// List of property names to exclude when is + /// +#if NET35 + public HashSet ExcludeProperties { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase); +#else + public ISet ExcludeProperties { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase); +#endif + + /// + /// List of property names to include when is + /// +#if NET35 + public HashSet IncludeProperties { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase); +#else + public ISet IncludeProperties { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase); +#endif + + /// + /// Disables to capture ScopeContext-properties from active thread context + /// + public LayoutRenderer? DisableThreadAgnostic => IncludeScopeProperties ? _disableThreadAgnostic : (IncludeEventProperties ? _enableThreadAgnosticImmutable : null); + private static readonly LayoutRenderer _disableThreadAgnostic = new FuncLayoutRenderer(string.Empty, (evt, cfg) => string.Empty); + private static readonly LayoutRenderer _enableThreadAgnosticImmutable = new ExceptionDataLayoutRenderer() { Item = " " }; + + /// + /// Initializes a new instance of the class. + /// + public GelfLayout() + { + _gelfHostName = GelfHostName = "${hostname}"; + GelfShortMessage = GelfFullMessage = "${message}"; + } + + /// + protected override void InitializeLayout() + { + if (GelfFields.Count == 0) + { + GelfFields.Add(new TargetPropertyWithContext("_loglevel", "${level}")); + GelfFields.Add(new TargetPropertyWithContext("_logger", "${logger}")); + GelfFields.Add(new TargetPropertyWithContext("_exception_type", "${exception:Format=Type}")); + GelfFields.Add(new TargetPropertyWithContext("_exception_msg", "${exception:Format=Message}")); + GelfFields.Add(new TargetPropertyWithContext("_exception", "${exception:Format=ToString}")); + } + + // CompoundLayout includes optimization, so only doing precalculate/caching of relevant Layouts (instead of the entire GELF-message) + Layouts.Clear(); + if (!IncludeEventProperties && !IncludeScopeProperties) + { + if (GelfFacility != null) + Layouts.Add(GelfFacility); + if (GelfHostName != null) + Layouts.Add(GelfHostName); + if (GelfShortMessage != null) + Layouts.Add(GelfShortMessage); + if (GelfFullMessage != null) + Layouts.Add(GelfFullMessage); + for (int i = 0; i < GelfFields.Count; ++i) + Layouts.Add(GelfFields[i].Layout); + } + + base.InitializeLayout(); + + _gelfHostNameString = ResolveJsonFixedString(_gelfHostName); + _gelfFacilityString = ResolveJsonFixedString(_gelfFacility); + } + + /// + protected override void CloseLayout() + { + base.CloseLayout(); + _jsonConverter = null; + } + + private string? ResolveJsonFixedString(Layout layout) + { + if (layout is SimpleLayout simpleLayout && simpleLayout.IsFixedText && !ReferenceEquals(layout, Layout.Empty)) + { + var stringBuilder = new StringBuilder(); + JsonConverter.SerializeObject(simpleLayout.FixedText, stringBuilder); + return stringBuilder.ToString(); + } + return null; + } + + /// + protected override string GetFormattedMessage(LogEventInfo logEvent) + { + var stringBuilder = new StringBuilder(); + RenderFormattedMessage(logEvent, stringBuilder); + return stringBuilder.ToString(); + } + + /// + protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target) + { + // GELF ver. 1.1 + target.Append(_beginJsonMessage); + target.Append("version"); + target.Append(_completeJsonPropertyName); + target.Append('"'); + target.Append(GelfVersion11); + target.Append('"'); + + target.Append(_beginJsonPropertyName); + target.Append("host"); + target.Append(_completeJsonPropertyName); + if (_gelfHostNameString is null) + JsonConverter.SerializeObject(GelfHostName?.Render(logEvent) ?? string.Empty, target); + else + target.Append(_gelfHostNameString); + + var shortMessage = GelfShortMessage?.Render(logEvent) ?? string.Empty; + target.Append(_beginJsonPropertyName); + target.Append("short_message"); + target.Append(_completeJsonPropertyName); + JsonConverter.SerializeObject(shortMessage.Length > ShortMessageMaxLength ? shortMessage.Substring(0, ShortMessageMaxLength) : shortMessage, target); + + var fullMessage = GelfFullMessage?.Render(logEvent) ?? string.Empty; + if (string.IsNullOrEmpty(fullMessage) && shortMessage.Length > ShortMessageMaxLength) + fullMessage = shortMessage; + else if (fullMessage.Length < ShortMessageMaxLength && string.Equals(fullMessage, shortMessage, StringComparison.Ordinal)) + fullMessage = string.Empty; + if (!string.IsNullOrEmpty(fullMessage)) + { + target.Append(_beginJsonPropertyName); + target.Append("full_message"); + target.Append(_completeJsonPropertyName); + JsonConverter.SerializeObject(fullMessage.Length > FullMessageMaxLength ? fullMessage.Substring(0, FullMessageMaxLength) : fullMessage, target); + } + + var unixTimestamp = ToUnixTimeStamp(logEvent.TimeStamp); + target.Append(_beginJsonPropertyName); + target.Append("timestamp"); + target.Append(_completeJsonPropertyName); + target.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "{0}", unixTimestamp); + + var gelfSeverity = ToSyslogLevel(logEvent.Level); + target.Append(_beginJsonPropertyName); + target.Append("level"); + target.Append(_completeJsonPropertyName); + target.Append((int)gelfSeverity); + + if (_gelfFacility != null && !ReferenceEquals(_gelfFacility, Layout.Empty)) + { + target.Append(_beginJsonPropertyName); + target.Append("facility"); + target.Append(_completeJsonPropertyName); + if (_gelfFacilityString is null) + JsonConverter.SerializeObject(_gelfFacility?.Render(logEvent) ?? string.Empty, target); + else + target.Append(_gelfFacilityString); + + var callerLineNumber = logEvent.CallerLineNumber; + if (callerLineNumber > 0) + { + target.Append(_beginJsonPropertyName); + target.Append("line"); + target.Append(_completeJsonPropertyName); + target.Append(callerLineNumber); + } + + var callerFileName = logEvent.CallerFilePath ?? logEvent.CallerClassName ?? logEvent.Exception?.TargetSite?.ToString() ?? logEvent.LoggerName; + if (!string.IsNullOrEmpty(callerFileName)) + { + target.Append(_beginJsonPropertyName); + target.Append("file"); + target.Append(_completeJsonPropertyName); + JsonConverter.SerializeObject(callerFileName, target); + } + } + + var eventProperties = IncludeEventProperties ? ResolveEventProperties(logEvent) : null; + var scopePropertyList = IncludeScopeProperties ? ResolveScopePropertyList() : null; + bool filterGelfFields = eventProperties?.Count > 0 || scopePropertyList?.Count > 0; + + foreach (var field in GelfFields) + { + var fieldName = field.Name; + if (string.IsNullOrEmpty(fieldName)) + continue; + + if (filterGelfFields && ExcludeGelfField(fieldName, eventProperties, scopePropertyList)) + continue; + + var fieldValue = field.RenderValue(logEvent); + if (!field.IncludeEmptyValue && (fieldValue is null || ReferenceEquals(string.Empty, fieldValue))) + continue; + + BeginJsonProperty(target, fieldName, fieldValue); + } + + if (scopePropertyList?.Count > 0) + { + var filterScopeProperties = eventProperties?.Count > 0 || ExcludeProperties?.Count > 0; + for (int i = 0; i < scopePropertyList.Count; ++i) + { + var scopeProperty = scopePropertyList[i]; + if (ExcludeEmptyProperties && (scopeProperty.Value is null || ReferenceEquals(scopeProperty.Value, string.Empty))) + continue; + + if (filterScopeProperties && ExcludeScopeProperty(scopeProperty.Key, eventProperties, ExcludeProperties)) + continue; + + BeginJsonProperty(target, scopeProperty.Key, scopeProperty.Value); + } + } + + if (eventProperties?.Count > 0) + { + var excludeProperties = ExcludeProperties?.Count > 0 ? ExcludeProperties : null; + foreach (var eventProperty in eventProperties) + { + if (ExcludeEmptyProperties && (eventProperty.Value is null || ReferenceEquals(eventProperty.Value, string.Empty))) + continue; + + var eventPropertyName = eventProperty.Key?.ToString() ?? string.Empty; + if (excludeProperties?.Contains(eventPropertyName) == true) + continue; + + BeginJsonProperty(target, eventPropertyName, eventProperty.Value); + } + } + + target.Append(_completeJsonMessage); + } + +#if NET35 + private static bool ExcludeScopeProperty(string propertyName, IDictionary? eventProperties, HashSet? excludeProperties) +#else + private static bool ExcludeScopeProperty(string propertyName, IDictionary? eventProperties, ISet? excludeProperties) +#endif + { + if (excludeProperties?.Contains(propertyName) == true) + return true; + if (eventProperties?.ContainsKey(propertyName) == true) + return true; + return false; + } + + private static bool ExcludeGelfField(string gelfFieldName, IDictionary? eventProperties, IList>? scopeProperties) + { + if (string.IsNullOrEmpty(gelfFieldName)) + return true; + + if (eventProperties?.ContainsKey(gelfFieldName) == true) + return true; + + var normalPropertyName = gelfFieldName[0] == '_' ? gelfFieldName.Substring(1) : null; + if (normalPropertyName != null && eventProperties?.ContainsKey(normalPropertyName) == true) + return true; + + if (scopeProperties?.Count > 0) + { + for (int i = 0; i < scopeProperties.Count; ++i) + { + if (gelfFieldName.Equals(scopeProperties[i].Key, StringComparison.OrdinalIgnoreCase)) + return true; + if (normalPropertyName?.Equals(scopeProperties[i].Key, StringComparison.OrdinalIgnoreCase) == true) + return true; + } + } + + return false; + } + + private IDictionary? ResolveEventProperties(LogEventInfo logEvent) + { + if (!logEvent.HasProperties) + return null; + + var eventProperties = logEvent.Properties; + if (IncludeProperties?.Count > 0) + { + bool filterProperty = false; + bool foundProperty = false; + foreach (var eventProperty in logEvent.Properties) + { + if (IncludeProperties.Contains(eventProperty.Key?.ToString() ?? string.Empty)) + foundProperty = true; + else + filterProperty = true; + + if (filterProperty && foundProperty) + break; + } + + if (filterProperty) + return foundProperty ? eventProperties.Where(p => IncludeProperties.Contains(p.Key?.ToString() ?? string.Empty)).ToDictionary(p => p.Key, p => p.Value) : null; + } + + return eventProperties; + } + + private IList>? ResolveScopePropertyList() + { + var scopeProperties = ScopeContext.GetAllProperties(); + if (scopeProperties is IList> scopePropertyList) + { + if (scopePropertyList.Count == 0) + return null; + + if (IncludeProperties?.Count > 0) + { + bool filterProperty = false; + bool foundProperty = false; + for (int i = 0; i < scopePropertyList.Count; ++i) + { + if (!IncludeProperties.Contains(scopePropertyList[i].Key)) + filterProperty = true; + else + foundProperty = true; + + if (filterProperty && foundProperty) + break; + } + + if (filterProperty) + return foundProperty ? scopePropertyList.Where(p => IncludeProperties.Contains(p.Key)).ToList() : null; + } + + return scopePropertyList; + } + + var scopePropertyCollection = IncludeProperties?.Count > 0 + ? scopeProperties?.Where(p => IncludeProperties.Contains(p.Key)).ToList() + : scopeProperties?.ToList(); + return scopePropertyCollection?.Count > 0 ? scopePropertyCollection : null; + } + + private void BeginJsonProperty(StringBuilder sb, string propName, object? propertyValue) + { + if (ExcludeEmptyProperties && (propertyValue is null || ReferenceEquals(propertyValue, string.Empty))) + return; + + var initialLength = sb.Length; + + sb.Append(_beginJsonPropertyName); + + sb.Append(EscapePropertyName(propName)); + + sb.Append(_completeJsonPropertyName); + + if (!JsonConverter.SerializeObject(propertyValue, sb)) + { + sb.Length = initialLength; + } + + if (ExcludeEmptyProperties && sb[sb.Length - 1] == '"' && sb[sb.Length - 2] == '"' && sb[sb.Length - 3] != '\\') + { + sb.Length = initialLength; + } + } + + internal static decimal ToUnixTimeStamp(DateTime timeStamp) + { + return Convert.ToDecimal(timeStamp.ToUniversalTime().Subtract(UnixDateStart).TotalSeconds); + } + + internal static SyslogLevel ToSyslogLevel(LogLevel logLevel) + { + try + { + return _logLevelMapping[logLevel.Ordinal]; + } + catch (IndexOutOfRangeException) + { + return SyslogLevel.Emergency; + } + } + + private static readonly SyslogLevel[] _logLevelMapping = new [] + { + NLog.Layouts.SyslogLevel.Debug, + NLog.Layouts.SyslogLevel.Debug, + NLog.Layouts.SyslogLevel.Informational, + NLog.Layouts.SyslogLevel.Warning, + NLog.Layouts.SyslogLevel.Error, + NLog.Layouts.SyslogLevel.Emergency, + NLog.Layouts.SyslogLevel.Emergency, + }; + + private readonly string _beginJsonMessage = "{\""; + private readonly string _completeJsonMessage = "}"; + private readonly string _beginJsonPropertyName = ",\""; + private readonly string _completeJsonPropertyName = "\":"; + private const string GelfVersion11 = "1.1"; + private static readonly DateTime UnixDateStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private const int ShortMessageMaxLength = 250; + private const int FullMessageMaxLength = 16383; // Truncate due to: https://github.com/Graylog2/graylog2-server/issues/873 + + private static string EscapePropertyName(string propertyName) + { + if (string.IsNullOrEmpty(propertyName)) + return string.Empty; + + foreach (var chr in propertyName) + { + if (RequiresJsonEscape(chr, true) || char.IsWhiteSpace(chr)) + { + propertyName = new string(propertyName.Select(c => char.IsWhiteSpace(c) || RequiresJsonEscape(c, true) ? '_' : c).ToArray()).Replace("__", "_").Trim('_'); + return string.IsNullOrEmpty(propertyName) ? string.Empty : $"_{propertyName}"; + } + } + + if (propertyName[0] == '_') + return propertyName; + else + return $"_{propertyName}"; + } + + private static bool RequiresJsonEscape(char ch, bool escapeUnicode) + { + if (ch < 32) + return true; + if (ch > 127) + return escapeUnicode; + return ch == '"' || ch == '\\'; + } + } +} diff --git a/src/NLog.Targets.Network/Layouts/Log4JXmlEventLayout.cs b/src/NLog.Targets.Network/Layouts/Log4JXmlEventLayout.cs new file mode 100644 index 0000000000..165b5ca45e --- /dev/null +++ b/src/NLog.Targets.Network/Layouts/Log4JXmlEventLayout.cs @@ -0,0 +1,339 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Layouts +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Text; + using NLog.Config; + + /// + /// A specialized layout that renders Log4j-compatible XML events. + /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + [Layout("Log4JXmlEventLayout")] + [Layout("Log4JXmlLayout")] + [ThreadAgnostic] + public class Log4JXmlEventLayout : CompoundLayout + { + private static readonly DateTime UnixDateStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private readonly XmlLayout _innerXml = new XmlLayout() { ElementName = "log4j:event" }; + + /// + /// Gets the collection of parameters. Each parameter contains a mapping + /// between NLog layout and a named parameter. + /// + [ArrayParameter(typeof(Log4JXmlEventParameter), "parameter")] + public IList Parameters { get; } = new List(); + + /// + /// Gets or sets the option to include all properties from the log events + /// + /// + public bool IncludeEventProperties { get; set; } + + /// + /// Gets or sets whether to include the contents of the properties-dictionary. + /// + /// + public bool IncludeScopeProperties { get => _includeScopeProperties ?? (_includeMdlc == true || _includeMdc == true); set => _includeScopeProperties = value; } + private bool? _includeScopeProperties; + + /// + /// Gets or sets whether to include log4j:NDC in output from nested context. + /// + /// + public bool IncludeScopeNested { get => _includeScopeNested ?? (_includeNdlc == true || _includeNdc == true); set => _includeScopeNested = value; } + private bool? _includeScopeNested; + + /// + /// Gets or sets a value indicating whether to include NLog-specific extensions to log4j schema. + /// + /// + [Obsolete("Non standard extension to the Log4j-XML format. Marked obsolete with NLog v5.4")] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IncludeNLogData { get; set; } + + /// + /// Obsolete and replaced by with NLog v5. + /// + /// Gets or sets the stack separator for log4j:NDC in output from nested context. + /// + /// + [Obsolete("Replaced by NdcItemSeparator. Marked obsolete on NLog 5.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + public string NdlcItemSeparator { get => ScopeNestedSeparator; set => ScopeNestedSeparator = value; } + + /// + /// Gets or sets the stack separator for log4j:NDC in output from nested context. + /// + /// + public string ScopeNestedSeparator { get; set; } = string.Empty; + + /// + /// Gets or sets the stack separator for log4j:NDC in output from nested context. + /// + /// + public string NdcItemSeparator { get => ScopeNestedSeparator; set => ScopeNestedSeparator = value; } + + /// + /// Obsolete and replaced by with NLog v5. + /// + /// Gets or sets the option to include all properties from the log events + /// + /// + [Obsolete("Replaced by IncludeEventProperties. Marked obsolete on NLog 5.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IncludeAllProperties { get => IncludeEventProperties; set => IncludeEventProperties = value; } + + /// + /// Obsolete and replaced by with NLog v5. + /// + /// Gets or sets a value indicating whether to include contents of the dictionary. + /// + /// + [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IncludeMdc { get => _includeMdc ?? false; set => _includeMdc = value; } + private bool? _includeMdc; + + /// + /// Gets or sets whether to include log4j:NDC in output from nested context. + /// + /// + public bool IncludeNdc { get => _includeNdc ?? false; set => _includeNdc = value; } + private bool? _includeNdc; + + /// + /// Obsolete and replaced by with NLog v5. + /// + /// Gets or sets a value indicating whether to include contents of the dictionary. + /// + /// + [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IncludeMdlc { get => _includeMdlc ?? false; set => _includeMdlc = value; } + private bool? _includeMdlc; + + /// + /// Obsolete and replaced by with NLog v5. + /// + /// Gets or sets a value indicating whether to include contents of the stack. + /// + /// + [Obsolete("Replaced by IncludeNdc. Marked obsolete on NLog 5.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IncludeNdlc { get => _includeNdlc ?? false; set => _includeNdlc = value; } + private bool? _includeNdlc; + + /// + /// Gets or sets the log4j:event logger-xml-attribute. Default: ${logger} + /// + /// + public Layout LoggerName { get => _loggerName.Layout; set => _loggerName.Layout = value; } + private readonly XmlAttribute _loggerName = new XmlAttribute("logger", "${logger}"); + + private readonly XmlAttribute _levelValue = new XmlAttribute("level", "${level:uppercase=true}"); + + private readonly XmlAttribute _timestampValue = new XmlAttribute("timestamp", Layout.FromMethod(evt => (long)(evt.TimeStamp.ToUniversalTime() - UnixDateStart).TotalMilliseconds, LayoutRenderOptions.ThreadAgnostic)); + + private readonly XmlAttribute _threadIdValue = new XmlAttribute("thread", "${threadid}"); + + /// + /// Gets or sets the log4j:event message-xml-element. Default: ${message} + /// + /// + public Layout FormattedMessage { get => _formattedMessage.Layout; set => _formattedMessage.Layout = value; } + private readonly XmlElement _formattedMessage = new XmlElement("log4j:message", "${message}"); + + /// + /// Gets or sets the log4j:event log4japp-xml-element. By default it's the friendly name of the current AppDomain. + /// + /// + public Layout AppInfo { get => _log4jAppName.Attributes[1].Layout; set => _log4jAppName.Attributes[1].Layout = value; } + private readonly XmlElement _log4jAppName = new XmlElement("log4j:data", Layout.Empty) + { + Attributes = + { + new XmlAttribute("name", "log4japp"), + new XmlAttribute("value", "${appdomain:format=Friendly}(${processid})") + } + }; + + private readonly XmlElement _log4jMachineName = new XmlElement("log4j:data", Layout.Empty) + { + Attributes = + { + new XmlAttribute("name", "log4jmachinename"), + new XmlAttribute("value", "${hostname}") + } + }; + + /// + /// Gets or sets whether the log4j:throwable xml-element should be written as CDATA + /// + /// + public bool WriteThrowableCData { get => _exceptionThrowable.CDataEncode; set => _exceptionThrowable.CDataEncode = value; } + private readonly XmlElement _exceptionThrowable = new XmlElement("log4j:throwable", "${exception:format=ToString}"); + + /// + /// Gets or sets a value indicating whether to include call site (class and method name) in the information sent over the network. + /// + /// + public bool IncludeCallSite { get; set; } + + /// + /// Gets or sets a value indicating whether to include source info (file name and line number) in the information sent over the network. + /// + /// + public bool IncludeSourceInfo { get; set; } + + /// + protected override void InitializeLayout() + { + _innerXml.Attributes.Clear(); + _innerXml.Elements.Clear(); + + _innerXml.Attributes.Add(_loggerName); + _innerXml.Attributes.Add(_levelValue); + _innerXml.Attributes.Add(_timestampValue); + _innerXml.Attributes.Add(_threadIdValue); + + _innerXml.Elements.Add(_formattedMessage); + _innerXml.Elements.Add(_exceptionThrowable); + + if (IncludeCallSite || IncludeSourceInfo) + { + var locationInfo = new XmlElement("log4j:locationInfo", Layout.Empty) + { + Attributes = + { + new XmlAttribute("class", "${callsite:methodName=false}"), + new XmlAttribute("method", "${callsite:className=false}") + } + }; + + if (IncludeSourceInfo) + { + locationInfo.Attributes.Add(new XmlAttribute("file", "${callsite-filename}")); + locationInfo.Attributes.Add(new XmlAttribute("line", "${callsite-linenumber}")); + } + + _innerXml.Elements.Add(locationInfo); + } + +#pragma warning disable CS0618 // Type or member is obsolete + if (IncludeNLogData) + { + _innerXml.Elements.Add(new XmlElement("nlog:eventSequenceNumber", "${sequenceid}")); + _innerXml.Elements.Add(new XmlElement("nlog:locationInfo", Layout.Empty) + { + Attributes = + { + new XmlAttribute("assembly", "${callsite:fileName=true:className=false:methodName=false}") + } + }); + _innerXml.Elements.Add(new XmlElement("nlog:properties", Layout.Empty) + { + PropertiesElementName = "nlog:data", + PropertiesElementKeyAttribute = "name", + PropertiesElementValueAttribute = "value", + IncludeEventProperties = true, + MaxRecursionLimit = 0, // ToString for everything + }); + } +#pragma warning restore CS0618 // Type or member is obsolete + + if (IncludeScopeNested) + { + var separator = ScopeNestedSeparator; + Layout scopeNested = string.IsNullOrEmpty(separator) ? "${scopenested}" : ("${scopenested:separator=" + separator.Replace(":", "\\:") + "}"); + _innerXml.Elements.Add(new XmlElement("log4j:NDC", scopeNested)); + } + + var dataProperties = new XmlElement("log4j:properties", Layout.Empty) + { + PropertiesElementName = "log4j:data", + PropertiesElementKeyAttribute = "name", + PropertiesElementValueAttribute = "value", + IncludeEventProperties = IncludeEventProperties, + IncludeScopeProperties = IncludeScopeProperties, + MaxRecursionLimit = 0, // ToString for everything + }; + + dataProperties.ContextProperties?.Clear(); + if (Parameters.Count > 0) + { + if (dataProperties.ContextProperties is null) + dataProperties.ContextProperties = new List(); + foreach (var parameter in Parameters) + { + dataProperties.ContextProperties.Add(new Targets.TargetPropertyWithContext(parameter.Name, parameter.Layout) { IncludeEmptyValue = parameter.IncludeEmptyValue }); + } + } + + dataProperties.Elements.Add(_log4jAppName); + dataProperties.Elements.Add(_log4jMachineName); + + _innerXml.Elements.Add(dataProperties); + + // CompoundLayout includes optimization, so only doing precalculate/caching of relevant Layouts (instead of the entire LOG4J-message) + Layouts.Clear(); + foreach (var xmlAttribute in _innerXml.Attributes) + Layouts.Add(xmlAttribute.Layout); + foreach (var xmlElement in _innerXml.Elements) + Layouts.Add(xmlElement); + + base.InitializeLayout(); + } + + /// + protected override string GetFormattedMessage(LogEventInfo logEvent) + { + var sb = new StringBuilder(1024); + RenderFormattedMessage(logEvent, sb); + return sb.ToString(); + } + + /// + protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target) + { + _innerXml.Render(logEvent, target); + } + } +} diff --git a/src/NLog/Targets/NLogViewerParameterInfo.cs b/src/NLog.Targets.Network/Layouts/Log4JXmlEventParameter.cs similarity index 69% rename from src/NLog/Targets/NLogViewerParameterInfo.cs rename to src/NLog.Targets.Network/Layouts/Log4JXmlEventParameter.cs index c4bc582e88..9586298a1a 100644 --- a/src/NLog/Targets/NLogViewerParameterInfo.cs +++ b/src/NLog.Targets.Network/Layouts/Log4JXmlEventParameter.cs @@ -31,43 +31,43 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.Targets +namespace NLog.Layouts { using NLog.Config; - using NLog.Internal; - using NLog.Layouts; /// - /// Represents a parameter to a NLogViewer target. + /// Represents a parameter for the /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki [NLogConfigurationItem] - public class NLogViewerParameterInfo + public class Log4JXmlEventParameter { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public NLogViewerParameterInfo() + public Log4JXmlEventParameter() { } /// - /// Gets or sets viewer parameter name. + /// Gets or sets log4j:data property-name. /// /// - [RequiredParameter] - public string Name { get => _name; set => _name = XmlHelper.XmlConvertToStringSafe(value); } - private string _name; + public string Name { get; set; } = string.Empty; /// - /// Gets or sets the layout that should be use to calculate the value for the parameter. + /// Gets or sets the layout used for rendering the log4j:data property-value. /// /// - [RequiredParameter] - public Layout Layout { get; set; } + public Layout Layout { get; set; } = Layout.Empty; /// - /// Gets or sets whether an attribute with empty value should be included in the output + /// Gets or sets whether empty property-value should be included in the output. Default = false /// + /// Empty value is either null or empty string /// public bool IncludeEmptyValue { get; set; } } diff --git a/src/NLog.Targets.Network/Layouts/SplunkLayout.cs b/src/NLog.Targets.Network/Layouts/SplunkLayout.cs new file mode 100644 index 0000000000..50ae3e23e1 --- /dev/null +++ b/src/NLog.Targets.Network/Layouts/SplunkLayout.cs @@ -0,0 +1,275 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Layouts +{ + using System; + using System.Collections.Generic; + using NLog.Config; + + /// + /// Splunk JSON-based, structured log format for Splunk Log Management. + /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + /// + /// { + /// "time": 1426279439, // epoch time + /// "host": "localhost", + /// "source": "random-data-generator", + /// "sourcetype": "my_sample_data", + /// "index": "main", + /// "event": { + /// "message": "Something happened", + /// "level": "Info" + /// } + /// } + /// + [Layout("SplunkLayout")] + [ThreadAgnostic] + [AppDomainFixedOutput] + public class SplunkLayout : JsonLayout + { + private static readonly DateTime UnixDateStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + /// + /// Gets the array of attributes for the "event"-section + /// + [ArrayParameter(typeof(JsonAttribute), "splunkevent")] + public IList? SplunkEvents + { + get + { + var index = LookupNamedAttributeIndex("event"); + return index >= 0 ? (Attributes[index]?.Layout as JsonLayout)?.Attributes : null; + } + } + + /// + /// Gets or sets Splunk Message Host-attribute + /// + public Layout SplunkHostName + { + get + { + var index = LookupNamedAttributeIndex("host"); + return index >= 0 ? Attributes[index].Layout : Layout.Empty; + } + set + { + var index = LookupNamedAttributeIndex("host"); + if (index >= 0) + Attributes[index].Layout = value; + } + } + + /// + /// Gets or sets Splunk Message Source-attribute. Example the name of the application. + /// + public Layout SplunkSourceName + { + get + { + var index = LookupNamedAttributeIndex("source"); + return index >= 0 ? Attributes[index].Layout : Layout.Empty; + } + set + { + var index = LookupNamedAttributeIndex("source"); + if (index >= 0) + Attributes[index].Layout = value; + } + } + + /// + /// Gets or sets Splunk Message SourceType-attribute. SourceType can be used hint for choosing Splunk Indexer + /// + public Layout SplunkSourceType + { + get + { + var index = LookupNamedAttributeIndex("sourcetype"); + return index >= 0 ? Attributes[index].Layout : Layout.Empty; + } + set + { + var index = LookupNamedAttributeIndex("sourcetype"); + if (index >= 0) + Attributes[index].Layout = value; + } + } + + /// + /// Gets or sets Splunk Message Index-attribute, that controls which event data is to be indexed. + /// + public Layout SplunkIndex + { + get + { + var index = LookupNamedAttributeIndex("index"); + return index >= 0 ? Attributes[index].Layout : Layout.Empty; + } + set + { + var index = LookupNamedAttributeIndex("index"); + if (index >= 0) + Attributes[index].Layout = value; + } + } + + /// + /// Gets or sets the option to include all properties from the log events + /// + public new bool IncludeEventProperties + { + get + { + var index = LookupNamedAttributeIndex("event"); + return index >= 0 && (Attributes[index].Layout as JsonLayout)?.IncludeEventProperties == true; + } + set + { + var index = LookupNamedAttributeIndex("event"); + if (index >= 0 && Attributes[index].Layout is JsonLayout jsonLayout) + jsonLayout.IncludeEventProperties = value; + } + } + + /// + /// Gets or sets whether to include the contents of the properties-dictionary. + /// + public new bool IncludeScopeProperties + { + get + { + var index = LookupNamedAttributeIndex("event"); + return index >= 0 && (Attributes[index].Layout as JsonLayout)?.IncludeScopeProperties == true; + } + set + { + var index = LookupNamedAttributeIndex("event"); + if (index >= 0 && Attributes[index].Layout is JsonLayout jsonLayout) + jsonLayout.IncludeScopeProperties = value; + } + } + + /// + /// Gets or sets the option to exclude null/empty properties from the log event (as JSON) + /// + public new bool ExcludeEmptyProperties + { + get + { + var index = LookupNamedAttributeIndex("event"); + return index >= 0 && (Attributes[index].Layout as JsonLayout)?.ExcludeEmptyProperties == true; + } + set + { + var index = LookupNamedAttributeIndex("event"); + if (index >= 0 && Attributes[index].Layout is JsonLayout jsonLayout) + jsonLayout.ExcludeEmptyProperties = value; + } + } + + /// + /// List of property names to exclude when is + /// +#if NET35 + public new HashSet? ExcludeProperties +#else + public new ISet? ExcludeProperties +#endif + { + get + { + var index = LookupNamedAttributeIndex("event"); + return index >= 0 && Attributes[index].Layout is JsonLayout jsonLayout ? jsonLayout.ExcludeProperties : null; + } + set + { + var index = LookupNamedAttributeIndex("event"); + if (index >= 0 && Attributes[index].Layout is JsonLayout jsonLayout && value != null) + jsonLayout.ExcludeProperties = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public SplunkLayout() + { + Attributes.Add(new JsonAttribute("time", Layout.FromMethod((evt) => ToUnixTimeStamp(evt.TimeStamp))) { Encode = false }); + Attributes.Add(new JsonAttribute("host", "${hostname}")); + Attributes.Add(new JsonAttribute("source", "${processname}")); + Attributes.Add(new JsonAttribute("sourcetype", Layout.Empty)); + Attributes.Add(new JsonAttribute("index", Layout.Empty)); + Attributes.Add(new JsonAttribute("event", new JsonLayout() { IncludeEventProperties = true }) { Encode = false }); + } + + /// + protected override void InitializeLayout() + { + var index = LookupNamedAttributeIndex("event"); + if (index >= 0 && Attributes[index].Layout is JsonLayout jsonLayout && jsonLayout.Attributes.Count == 0) + { + jsonLayout.Attributes.Add(new JsonAttribute("level", "${level}")); + jsonLayout.Attributes.Add(new JsonAttribute("message", "${message}")); + jsonLayout.Attributes.Add(new JsonAttribute("logger", "${logger}")); + jsonLayout.Attributes.Add(new JsonAttribute("exception_type", "${exception:Format=Type}")); + jsonLayout.Attributes.Add(new JsonAttribute("exception_msg", "${exception:Format=Message}")); + jsonLayout.Attributes.Add(new JsonAttribute("exception", "${exception:Format=ToString}")); + } + + base.InitializeLayout(); + } + + internal static decimal ToUnixTimeStamp(DateTime timeStamp) + { + return Convert.ToDecimal(timeStamp.ToUniversalTime().Subtract(UnixDateStart).TotalSeconds); + } + + private int LookupNamedAttributeIndex(string attributeName) + { + for (int i = 0; i < Attributes.Count; ++i) + { + if (attributeName.Equals(Attributes[i].Name, StringComparison.OrdinalIgnoreCase)) + { + return i; + } + } + return -1; + } + } +} diff --git a/src/NLog.Targets.Network/Layouts/SyslogFacility.cs b/src/NLog.Targets.Network/Layouts/SyslogFacility.cs new file mode 100644 index 0000000000..6bfa67c994 --- /dev/null +++ b/src/NLog.Targets.Network/Layouts/SyslogFacility.cs @@ -0,0 +1,111 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Layouts +{ + /// Syslog facilities + public enum SyslogFacility + { + /// Kernel messages - LOG_KERN + Kernel = 0, + + /// Random user-level messages - LOG_USER + User = 1, + + /// Mail system - LOG_MAIL + Mail = 2, + + /// System daemons - LOG_DAEMON + Daemons = 3, + + /// Security/authorization messages - LOG_AUTH + Authorization = 4, + + /// Messages generated internally by syslogd - LOG_SYSLOG + Syslog = 5, + + /// Line printer subsystem - LOG_LPR + Printer = 6, + + /// Network news subsystem - LOG_NEWS + News = 7, + + /// UUCP subsystem - LOG_UUCP + Uucp = 8, + + /// Clock (cron/at) daemon - LOG_CRON + Clock = 9, + + /// Security/authorization messages (private) - LOG_AUTHPRIV + Authorization2 = 10, + + /// FTP daemon - LOG_FTP + Ftp = 11, + + /// NTP subsystem - LOG_NTP + Ntp = 12, + + /// Log audit - LOG_SECURITY + Audit = 13, + + /// Log alert - LOG_CONSOLE + Alert = 14, + + /// Clock daemon / Scheduling daemon + Clock2 = 15, + + /// Reserved for local use - LOG_LOCAL0 + Local0 = 16, + + /// Reserved for local use - LOG_LOCAL1 + Local1 = 17, + + /// Reserved for local use - LOG_LOCAL2 + Local2 = 18, + + /// Reserved for local use - LOG_LOCAL3 + Local3 = 19, + + /// Reserved for local use - LOG_LOCAL4 + Local4 = 20, + + /// Reserved for local use - LOG_LOCAL5 + Local5 = 21, + + /// Reserved for local use - LOG_LOCAL6 + Local6 = 22, + + /// Reserved for local use - LOG_LOCAL7 + Local7 = 23 + } +} diff --git a/src/NLog.Targets.Network/Layouts/SyslogLayout.cs b/src/NLog.Targets.Network/Layouts/SyslogLayout.cs new file mode 100644 index 0000000000..f06ece418a --- /dev/null +++ b/src/NLog.Targets.Network/Layouts/SyslogLayout.cs @@ -0,0 +1,555 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Layouts +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using NLog.Config; + using NLog.LayoutRenderers; + using NLog.Targets; + + /// + /// A specialized layout that renders Syslog-formatted events in format Rfc3164 / Rfc5424 + /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + [Layout("SyslogLayout")] + [ThreadAgnostic] + [AppDomainFixedOutput] + public class SyslogLayout : CompoundLayout + { + private const string Rfc5424DefaultVersion = "1"; + private const string Rfc3164TimestampFormat = "{0:MMM} {0,11:d HH:mm:ss}"; + private const string NilValue = "-"; + private const int HostNameMaxLength = 255; + private const int AppNameMaxLength = 48; + private const int ProcessIdMaxLength = 128; + + private IValueFormatter ValueFormatter => _valueFormatter ?? (_valueFormatter = ResolveService()); + private IValueFormatter? _valueFormatter; + + /// + /// Gets or sets a value indicating whether to use RFC 3164 for Syslog Format + /// + /// + public bool Rfc3164 { get; set; } + + /// + /// Gets or sets a value indicating whether to use RFC 5424 for Syslog Format + /// + /// + public bool Rfc5424 { get; set; } + + /// + /// Gets or sets a value indicating what DateTime format should be used when = + /// + public Layout SyslogTimestamp { get; set; } = "${date:format=o}"; + + /// + /// The FQDN or IPv4 address or IPv6 address or hostname of the sender machine (Optional) + /// + /// + /// RFC 5424 - NILVALUE or 1 to 255 PRINTUSASCII + /// + public Layout SyslogHostName + { + get => _hostName; + set + { + _hostName = value; + _hostNameString = _hostName is SimpleLayout simpleLayout && simpleLayout.FixedText is not null ? EscapePropertyName(simpleLayout.FixedText, HostNameMaxLength) : null; + } + } + private Layout _hostName; + private string? _hostNameString; + + /// + /// Name of the device / application / process sending the Syslog-message (Optional) + /// + /// + /// RFC 3164 - Tag-Name - Alphanumeric string not exceeding 32 characters + /// RFC 5424 - NILVALUE or 1 to 48 PRINTUSASCII + /// + public Layout SyslogAppName + { + get => _appName; + set + { + _appName = value; + _appNameString = _appName is SimpleLayout simpleLayout && simpleLayout.FixedText is not null ? EscapePropertyName(simpleLayout.FixedText, AppNameMaxLength) : null; + } + } + private Layout _appName; + private string? _appNameString; + + /// + /// Process Id or Process Name or Logger Name (Optional) + /// + /// + /// RFC 5424 - NILVALUE or 1 to 128 PRINTUSASCII + /// + public Layout SyslogProcessId + { + get => _processId; + set + { + _processId = value; + _processIdString = _processId is SimpleLayout simpleLayout && simpleLayout.FixedText is not null ? EscapePropertyName(simpleLayout.FixedText, ProcessIdMaxLength) : null; + } + } + private Layout _processId; + private string? _processIdString; + + /// + /// The type of message that should be the same for events with the same semantics. Ex ${event-properties:EventId} (Optional) + /// + /// + /// RFC 5424 - NILVALUE or 1 to 32 PRINTUSASCII + /// + public Layout? SyslogMessageId { get; set; } + + /// + /// Mesage Payload + /// + public Layout SyslogMessage { get; set; } = "${message}${onexception:|}${exception:format=shortType,message}"; + + /// + /// Message Severity + /// + public Layout SyslogLevel { get; set; } = Layout.FromMethod(l => ToSyslogLevel(l.Level), LayoutRenderOptions.ThreadAgnostic); + + /// + /// Device Facility + /// + public SyslogFacility SyslogFacility { get; set; } = SyslogFacility.User; + + /// + /// Gets or sets the prefix for StructuredData when = + /// + public Layout StructuredDataId { get; set; } = "meta"; + + /// + /// Gets or sets a value indicating whether LogEvent Properties should be included for StructuredData when = + /// + public bool IncludeEventProperties { get; set; } + + /// + /// List of StructuredData Parameters to include when = + /// + [ArrayParameter(typeof(TargetPropertyWithContext), "StructuredDataParam")] + public List StructuredDataParams { get; } = new List(); + + private KeyValuePair> _priValueMapping; + + /// + /// Disables to capture volatile LogEvent-properties from active thread context + /// + public LayoutRenderer? DisableThreadAgnostic => IncludeEventProperties ? _enableThreadAgnosticImmutable : null; + private static readonly LayoutRenderer _enableThreadAgnosticImmutable = new ExceptionDataLayoutRenderer() { Item = " " }; + + /// + /// Initializes a new instance of the class. + /// + public SyslogLayout() + { + _hostName = SyslogHostName = "${hostname}"; + _appName = SyslogAppName = "${processname}"; + _processId = SyslogProcessId = "${processid}"; + } + + /// + protected override void InitializeLayout() + { + // CompoundLayout includes optimization, so only doing precalculate/caching of relevant Layouts (instead of the entire SysLog-message) + Layouts.Clear(); + if (!IncludeEventProperties) + { + if (SyslogTimestamp != null) + Layouts.Add(SyslogTimestamp); + if (SyslogHostName != null) + Layouts.Add(SyslogHostName); + if (SyslogAppName != null) + Layouts.Add(SyslogAppName); + if (SyslogProcessId != null) + Layouts.Add(SyslogProcessId); + if (SyslogMessageId != null) + Layouts.Add(SyslogMessageId); + if (SyslogMessage != null) + Layouts.Add(SyslogMessage); + if (SyslogLevel != null) + Layouts.Add(SyslogLevel); + if (StructuredDataId != null) + Layouts.Add(StructuredDataId); + for (int i = 0; i < StructuredDataParams.Count; ++i) + Layouts.Add(StructuredDataParams[i].Layout); + } + + base.InitializeLayout(); + } + + /// + protected override void CloseLayout() + { + _valueFormatter = null; + base.CloseLayout(); + } + + /// + protected override string GetFormattedMessage(LogEventInfo logEvent) + { + var stringBuilder = new StringBuilder(128); + RenderFormattedMessage(logEvent, stringBuilder); + return stringBuilder.ToString(); + } + + /// + protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target) + { + var syslogLevel = SyslogLevel.RenderValue(logEvent); + var facility = SyslogFacility; + + var priValueMapper = _priValueMapping; + if (priValueMapper.Key != facility || priValueMapper.Value is null) + { + priValueMapper = new KeyValuePair>(facility, ResolveFacilityMapper(facility)); + _priValueMapping = priValueMapper; + } + + var priValue = priValueMapper.Value[syslogLevel]; + var hostName = _hostNameString ?? EscapePropertyName(SyslogHostName?.Render(logEvent) ?? string.Empty, HostNameMaxLength); + var appName = _appNameString ?? EscapePropertyName(SyslogAppName?.Render(logEvent) ?? string.Empty, AppNameMaxLength); + var processId = _processIdString ?? EscapePropertyName(SyslogProcessId?.Render(logEvent) ?? string.Empty, ProcessIdMaxLength); + + if (Rfc3164 || !Rfc5424) + { + Render_Rfc3164(logEvent, target, priValue, hostName, appName, processId); + } + else + { + Render_Rfc5424(logEvent, target, priValue, hostName, appName, processId); + } + } + + private void Render_Rfc3164(LogEventInfo logEvent, StringBuilder target, string priValue, string hostName, string appName, string processId) + { + target.Append(priValue); + target.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, Rfc3164TimestampFormat, logEvent.TimeStamp); + target.Append(' '); + target.Append(string.IsNullOrEmpty(hostName) ? NilValue : hostName); + target.Append(' '); + if (appName.Length > 32) + target.Append(appName, 0, 32); // Rfc3164 TAG + else + target.Append(string.IsNullOrEmpty(appName) ? NilValue : appName); // Rfc3164 TAG + + if (!string.IsNullOrEmpty(processId)) + { + target.Append('['); + target.Append(processId); + target.Append(']'); + } + target.Append(':'); + target.Append(' '); + + int startIndex = target.Length; + SyslogMessage.Render(logEvent, target); + + bool removeDuplicates = false; + + for (int i = startIndex; i < target.Length; ++i) + { + var chr = target[i]; + if (ToAscii(chr) != chr) + { + target[i] = ToAscii(chr); + removeDuplicates = true; + } + } + + if (removeDuplicates) + { + var fixedString = target.ToString(startIndex, target.Length - startIndex); + fixedString = fixedString.Replace("??", "?").Replace(" ", " "); + if (fixedString.Length >= 1000) + fixedString = fixedString.Trim(' ', '?', '_'); + target.Length = startIndex; + target.Append(fixedString); + } + } + + private void Render_Rfc5424(LogEventInfo logEvent, StringBuilder target, string priValue, string hostName, string appName, string processId) + { + var msgId = EscapePropertyName(SyslogMessageId?.Render(logEvent) ?? string.Empty, 32); + if (msgId?.IndexOf(' ') >= 0) + msgId = msgId.Trim().Replace(' ', '_'); + + target.Append(priValue); + target.Append(Rfc5424DefaultVersion); + target.Append(' '); + SyslogTimestamp.Render(logEvent, target); + target.Append(' '); + target.Append(string.IsNullOrEmpty(hostName) ? NilValue : hostName); + target.Append(' '); + target.Append(string.IsNullOrEmpty(appName) ? NilValue : appName); + target.Append(' '); + target.Append(string.IsNullOrEmpty(processId) ? NilValue : processId); + target.Append(' '); + target.Append(string.IsNullOrEmpty(msgId) ? NilValue : msgId); + + var structuredDataId = EscapePropertyName(StructuredDataId?.Render(logEvent) ?? string.Empty); + if (!string.IsNullOrEmpty(structuredDataId)) + { + if (IncludeEventProperties && logEvent.HasProperties) + { + foreach (var eventProperty in logEvent.Properties) + { + var propertyName = EscapePropertyName(eventProperty.Key?.ToString() ?? string.Empty, 32); + if (string.IsNullOrEmpty(propertyName)) + continue; + + structuredDataId = AppendPropertyName(target, structuredDataId, propertyName); + + var propertyValue = eventProperty.Value; + AppendPropertyValue(target, propertyValue); + } + } + + foreach (var sdParam in StructuredDataParams) + { + var sdParamName = EscapePropertyName(sdParam.Name ?? string.Empty, 32); + if (string.IsNullOrEmpty(sdParamName)) + continue; + + var sdParamValue = sdParam.RenderValue(logEvent); + if (!sdParam.IncludeEmptyValue && (sdParamValue is null || string.Empty.Equals(sdParamValue))) + continue; + + structuredDataId = AppendPropertyName(target, structuredDataId, sdParamName); + AppendPropertyValue(target, sdParamValue); + } + + if (string.IsNullOrEmpty(structuredDataId)) + { + target.Append(']'); + } + } + + target.Append(' '); + SyslogMessage.Render(logEvent, target); + } + + private static string AppendPropertyName(StringBuilder target, string structuredDataId, string propertyName) + { + if (!string.IsNullOrEmpty(structuredDataId)) + { + target.Append(' '); + target.Append('['); + target.Append(structuredDataId); + structuredDataId = string.Empty; + } + target.Append(' '); + target.Append(propertyName); + target.Append('='); + return structuredDataId; + } + + private void AppendPropertyValue(StringBuilder target, object? propertyValue) + { + target.Append('"'); + + if (propertyValue is IConvertible convertPropertyValue) + { + propertyValue = EscapePropertyValue(convertPropertyValue); + ValueFormatter.FormatValue(propertyValue, null, MessageTemplates.CaptureType.Unknown, System.Globalization.CultureInfo.InvariantCulture, target); + } + else + { + var propertyStartIndex = target.Length; + ValueFormatter.FormatValue(propertyValue, null, MessageTemplates.CaptureType.Unknown, System.Globalization.CultureInfo.InvariantCulture, target); + for (int i = propertyStartIndex; i < target.Length; ++i) + { + var chr = target[i]; + if (IsSpecialChar(chr)) + { + var stringValue = target.ToString(propertyStartIndex, target.Length - propertyStartIndex); + target.Length = propertyStartIndex; + target.Append(EscapePropertyValue(stringValue).ToString()); + break; + } + } + } + + target.Append('"'); + } + + private static IConvertible EscapePropertyValue(IConvertible propertyValue) + { + var typeCode = propertyValue.GetTypeCode(); + switch (typeCode) + { + case TypeCode.Char: + { + var charValue = propertyValue.ToChar(System.Globalization.CultureInfo.CurrentCulture); + if (IsSpecialChar(charValue)) + return char.IsWhiteSpace(charValue) ? " " : $"\\{charValue}"; + else + return propertyValue; + } + case TypeCode.String: + { + var stringValue = propertyValue.ToString(); + if (stringValue.IndexOfAny(SpecialChars) >= 0) + return stringValue.Replace("\\", "\\\\").Replace("[", "\\[").Replace("]", "\\]").Replace("=", "\\=").Replace("\"", "\\\"").Replace("\n", " ").Replace("\r", ""); + else + return stringValue; + } + case TypeCode.DBNull: return string.Empty; + case TypeCode.Empty: return string.Empty; + default: + return propertyValue; + } + } + + private static string EscapePropertyName(string unicodeValue, int maxLength = 0) + { + if (string.IsNullOrEmpty(unicodeValue)) + return string.Empty; + + foreach (var chr in unicodeValue) + { + if (ToAscii(chr) == chr && !IsSpecialChar(chr) && chr != ' ') + continue; + + unicodeValue = new string(unicodeValue.Select(c => ToAsciiFixWhiteSpace(c)).Where(c => !IsSpecialChar(c)).ToArray()).Replace("??", "?").Trim('_').Replace("__", "_"); + break; + } + + if (maxLength > 0 && unicodeValue.Length > maxLength) + { + unicodeValue = unicodeValue.Substring(0, maxLength); + } + + return unicodeValue; + } + + private static readonly char[] SpecialChars = new[] { '[', ']', '"', '\\', '=', '\r', '\n' }; + + private static bool IsSpecialChar(char c) + { + switch (c) + { + case '[': + case ']': + case '"': + case '\'': + case '=': + case '\r': + case '\n': + return true; + default: + return false; + } + } + + private static char ToAsciiFixWhiteSpace(char c) + { + c = ToAscii(c); + return c == ' ' ? '_' : c; + } + + private static char ToAscii(char c) + { + if (char.IsWhiteSpace(c)) + return ' '; // newlines are also whitespace + if (!IsAscii(c)) + return '?'; + if (c <= 31) + return '?'; // control characters + return c; + } + + /// + /// Per http://www.unicode.org/glossary/#ASCII, ASCII is only U+0000..U+007F. + /// + private static bool IsAscii(char c) => (uint)c <= '\x007f'; + + private static readonly SyslogLevel[] _loglevelMappings = new [] + { + NLog.Layouts.SyslogLevel.Debug, + NLog.Layouts.SyslogLevel.Debug, + NLog.Layouts.SyslogLevel.Informational, + NLog.Layouts.SyslogLevel.Warning, + NLog.Layouts.SyslogLevel.Error, + NLog.Layouts.SyslogLevel.Emergency, + NLog.Layouts.SyslogLevel.Emergency, + }; + + private static SyslogLevel ToSyslogLevel(LogLevel logLevel) + { + try + { + return _loglevelMappings[logLevel.Ordinal]; + } + catch (IndexOutOfRangeException) + { + return NLog.Layouts.SyslogLevel.Emergency; + } + } + + private static Dictionary ResolveFacilityMapper(SyslogFacility facility) + { + return (new SyslogLevel[] + { + NLog.Layouts.SyslogLevel.Emergency, + NLog.Layouts.SyslogLevel.Alert, + NLog.Layouts.SyslogLevel.Critical, + NLog.Layouts.SyslogLevel.Error, + NLog.Layouts.SyslogLevel.Warning, + NLog.Layouts.SyslogLevel.Notice, + NLog.Layouts.SyslogLevel.Informational, + NLog.Layouts.SyslogLevel.Debug + }).ToDictionary(s => s, s => ResolvePriHeader(facility, s)); + } + + private static string ResolvePriHeader(SyslogFacility facility, SyslogLevel severity) + { + var priVal = (int)facility * 8 + (int)severity; + return $"<{priVal}>"; + } + } +} diff --git a/src/NLog.Targets.Network/Layouts/SyslogLevel.cs b/src/NLog.Targets.Network/Layouts/SyslogLevel.cs new file mode 100644 index 0000000000..1cba78d2b7 --- /dev/null +++ b/src/NLog.Targets.Network/Layouts/SyslogLevel.cs @@ -0,0 +1,63 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Layouts +{ + /// Syslog Log Levels for severity + public enum SyslogLevel + { + /// System is unusable - LOG_EMERG + Emergency = 0, + + /// Action must be taken immediately - LOG_ALERT + Alert = 1, + + /// Critical conditions - LOG_CRIT + Critical = 2, + + /// Error conditions - LOG_ERR + Error = 3, + + /// Warning conditions - LOG_WARNING + Warning = 4, + + /// Normal but significant condition - LOG_NOTICE + Notice = 5, + + /// Informational severity - LOG_INFO + Informational = 6, + + /// Debug messages - LOG_DEBUG + Debug = 7 + } +} diff --git a/src/NLog/LayoutRenderers/LocalIpAddressLayoutRenderer.cs b/src/NLog.Targets.Network/LocalIpAddressLayoutRenderer.cs similarity index 99% rename from src/NLog/LayoutRenderers/LocalIpAddressLayoutRenderer.cs rename to src/NLog.Targets.Network/LocalIpAddressLayoutRenderer.cs index b661607403..e9119be571 100644 --- a/src/NLog/LayoutRenderers/LocalIpAddressLayoutRenderer.cs +++ b/src/NLog.Targets.Network/LocalIpAddressLayoutRenderer.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.LayoutRenderers { using System; @@ -236,5 +234,3 @@ private static bool IsLoopbackAddressValue(IPAddress ipAddress) } } } - -#endif diff --git a/src/NLog.Targets.Network/NLog.Targets.Network.csproj b/src/NLog.Targets.Network/NLog.Targets.Network.csproj new file mode 100644 index 0000000000..2a1f6e764d --- /dev/null +++ b/src/NLog.Targets.Network/NLog.Targets.Network.csproj @@ -0,0 +1,83 @@ + + + + 17.0 + net35;net46;netstandard2.0 + net35;net46;netstandard2.0;netstandard2.1 + + NLog.Targets.Network + NLog + NetworkTarget for sending messages using TCP / UDP sockets with support for SSL / TSL + NLog.Targets.Network v$(ProductVersion) + $(ProductVersion) + Jarek Kowalski,Kim Christensen,Julian Verdurmen + $([System.DateTime]::Now.ToString(yyyy)) + Copyright (c) 2004-$(CurrentYear) NLog Project - https://nlog-project.org/ + + + NetworkTarget Docs: + https://github.com/NLog/NLog/wiki/Network-target + + README.md + NLog;TCPIP;TCP;UDP;Network;logging;log + N.png + https://nlog-project.org/ + BSD-3-Clause + git + https://github.com/NLog/NLog.git + + true + 6.0.0.0 + ..\NLog.snk + true + + true + true + true + enable + 9 + true + true + true + + + + NLog.Targets.Network for .NET Standard 2.1 + + + + NLog.Targets.Network for .NET Standard 2.0 + + + + NLog.Targets.Network for .NET Framework 4.6 + true + + + + NLog.Targets.Network for .NET Framework 4.5 + true + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NLog/Internal/NetworkInterfaceRetriever.cs b/src/NLog.Targets.Network/NetworkInterfaceRetriever.cs similarity index 97% rename from src/NLog/Internal/NetworkInterfaceRetriever.cs rename to src/NLog.Targets.Network/NetworkInterfaceRetriever.cs index d31407f902..fb1d95d07a 100644 --- a/src/NLog/Internal/NetworkInterfaceRetriever.cs +++ b/src/NLog.Targets.Network/NetworkInterfaceRetriever.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.Internal { using System.Net.NetworkInformation; @@ -48,4 +46,3 @@ internal sealed class NetworkInterfaceRetriever : INetworkInterfaceRetriever public NetworkInterface[] AllNetworkInterfaces => NetworkInterface.GetAllNetworkInterfaces(); } } -#endif diff --git a/src/NLog/Internal/NetworkSenders/HttpNetworkSender.cs b/src/NLog.Targets.Network/NetworkSenders/HttpNetworkSender.cs similarity index 79% rename from src/NLog/Internal/NetworkSenders/HttpNetworkSender.cs rename to src/NLog.Targets.Network/NetworkSenders/HttpNetworkSender.cs index 45b8f47978..8a50effd68 100644 --- a/src/NLog/Internal/NetworkSenders/HttpNetworkSender.cs +++ b/src/NLog.Targets.Network/NetworkSenders/HttpNetworkSender.cs @@ -34,6 +34,9 @@ namespace NLog.Internal.NetworkSenders { using System; + using System.Net; + using System.Net.Security; + using System.Security.Cryptography.X509Certificates; /// /// Network sender which uses HTTP or HTTPS POST. @@ -46,6 +49,8 @@ internal sealed class HttpNetworkSender : QueuedNetworkSender internal TimeSpan SendTimeout { get; set; } + internal X509Certificate2Collection? SslCertificateOverride { get; set; } + /// /// Initializes a new instance of the class. /// @@ -65,12 +70,23 @@ protected override void BeginRequest(NetworkRequestArgs eventArgs) var webRequest = HttpRequestFactory.CreateWebRequest(_addressUri); webRequest.Method = "POST"; -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 if (SendTimeout > TimeSpan.Zero) { webRequest.Timeout = (int)SendTimeout.TotalMilliseconds; } + + if (SslCertificateOverride != null) + { + if (webRequest is HttpWebRequest httpWebRequest) + { + if (SslCertificateOverride.Count > 0) + httpWebRequest.ClientCertificates = SslCertificateOverride; +#if NET45_OR_GREATER || !NETFRAMEWORK + httpWebRequest.ServerCertificateValidationCallback = UserCertificateValidationCallback; #endif + } + } + AsyncCallback onResponse = r => { @@ -87,7 +103,7 @@ protected override void BeginRequest(NetworkRequestArgs eventArgs) catch (Exception ex) { #if DEBUG - if (ex.MustBeRethrownImmediately()) + if (LogManager.ThrowExceptions) { throw; // Throwing exceptions here will crash the entire application (.NET 2.0 behavior) } @@ -112,7 +128,7 @@ protected override void BeginRequest(NetworkRequestArgs eventArgs) catch (Exception ex) { #if DEBUG - if (ex.MustBeRethrownImmediately()) + if (LogManager.ThrowExceptions) { throw; // Throwing exceptions here will crash the entire application (.NET 2.0 behavior) } @@ -133,5 +149,17 @@ private void CompleteRequest(Common.AsyncContinuation asyncContinuation) BeginRequest(nextRequest.Value); } } + + private static bool UserCertificateValidationCallback(object sender, object certificate, object chain, SslPolicyErrors sslPolicyErrors) + { + if (sslPolicyErrors == SslPolicyErrors.None) + return true; + + Common.InternalLogger.Warn("SSL certificate errors were encountered when establishing connection to the server: {0}, Certificate: {1}", sslPolicyErrors, certificate); + if (certificate is null) + return false; + + return true; + } } } diff --git a/src/NLog/Internal/NetworkSenders/INetworkSenderFactory.cs b/src/NLog.Targets.Network/NetworkSenders/INetworkSenderFactory.cs similarity index 76% rename from src/NLog/Internal/NetworkSenders/INetworkSenderFactory.cs rename to src/NLog.Targets.Network/NetworkSenders/INetworkSenderFactory.cs index 7827ae2d5a..78486d55c9 100644 --- a/src/NLog/Internal/NetworkSenders/INetworkSenderFactory.cs +++ b/src/NLog.Targets.Network/NetworkSenders/INetworkSenderFactory.cs @@ -34,6 +34,7 @@ namespace NLog.Internal.NetworkSenders { using System; + using System.Security.Cryptography.X509Certificates; using NLog.Targets; /// @@ -45,15 +46,11 @@ internal interface INetworkSenderFactory /// Creates a new instance of the network sender based on a network URL. /// /// URL that determines the network sender to be created. - /// The maximum queue size. - /// The overflow action when reaching maximum queue size. - /// The maximum message size. - /// SSL protocols for TCP - /// KeepAliveTime for TCP - /// SendTimeout for TCP + /// SSL Certificate override + /// NetworkTarget that requests the network connection. /// /// A newly created network sender. /// - QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQueueOverflowAction onQueueOverflow, int maxMessageSize, System.Security.Authentication.SslProtocols sslProtocols, TimeSpan keepAliveTime, TimeSpan sendTimeout); + QueuedNetworkSender Create(string url, X509Certificate2Collection? sslCertificateOverride, NetworkTarget networkTarget); } } diff --git a/src/NLog/Internal/NetworkSenders/ISocket.cs b/src/NLog.Targets.Network/NetworkSenders/ISocket.cs similarity index 100% rename from src/NLog/Internal/NetworkSenders/ISocket.cs rename to src/NLog.Targets.Network/NetworkSenders/ISocket.cs diff --git a/src/NLog/Internal/NetworkSenders/IWebRequestFactory.cs b/src/NLog.Targets.Network/NetworkSenders/IWebRequestFactory.cs similarity index 98% rename from src/NLog/Internal/NetworkSenders/IWebRequestFactory.cs rename to src/NLog.Targets.Network/NetworkSenders/IWebRequestFactory.cs index f6b8154ccb..69731b3a5f 100644 --- a/src/NLog/Internal/NetworkSenders/IWebRequestFactory.cs +++ b/src/NLog.Targets.Network/NetworkSenders/IWebRequestFactory.cs @@ -31,11 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Net; - namespace NLog.Internal.NetworkSenders { + using System; + using System.Net; + internal interface IWebRequestFactory { WebRequest CreateWebRequest(Uri address); diff --git a/src/NLog/Internal/NetworkSenders/NetworkSender.cs b/src/NLog.Targets.Network/NetworkSenders/NetworkSender.cs similarity index 97% rename from src/NLog/Internal/NetworkSenders/NetworkSender.cs rename to src/NLog.Targets.Network/NetworkSenders/NetworkSender.cs index bc70194f72..eb939d39ae 100644 --- a/src/NLog/Internal/NetworkSenders/NetworkSender.cs +++ b/src/NLog.Targets.Network/NetworkSenders/NetworkSender.cs @@ -174,11 +174,7 @@ protected virtual IPAddress ResolveIpAddress(Uri uri, AddressFamily addressFamil default: { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 var addresses = Dns.GetHostEntry(uri.Host).AddressList; // Dns.GetHostEntry returns IPv6 + IPv4 addresses, but Dns.GetHostAddresses() might only return IPv6 addresses -#else - var addresses = Dns.GetHostEntryAsync(uri.Host).ConfigureAwait(false).GetAwaiter().GetResult().AddressList; -#endif if (addressFamily == AddressFamily.Unspecified && addresses.Length > 1) { Array.Sort(addresses, IPAddressComparer.Default); // Prioritize IPv4 addresses over IPv6, unless explicitly specified @@ -197,7 +193,7 @@ protected virtual IPAddress ResolveIpAddress(Uri uri, AddressFamily addressFamil } } - public virtual ISocket CheckSocket() + public virtual ISocket? CheckSocket() { return null; } diff --git a/src/NLog/Internal/NetworkSenders/NetworkSenderFactory.cs b/src/NLog.Targets.Network/NetworkSenders/NetworkSenderFactory.cs similarity index 57% rename from src/NLog/Internal/NetworkSenders/NetworkSenderFactory.cs rename to src/NLog.Targets.Network/NetworkSenders/NetworkSenderFactory.cs index 8840b8783d..bfdb4e2e47 100644 --- a/src/NLog/Internal/NetworkSenders/NetworkSenderFactory.cs +++ b/src/NLog.Targets.Network/NetworkSenders/NetworkSenderFactory.cs @@ -35,6 +35,7 @@ namespace NLog.Internal.NetworkSenders { using System; using System.Net.Sockets; + using System.Security.Cryptography.X509Certificates; using NLog.Targets; /// @@ -45,17 +46,19 @@ internal sealed class NetworkSenderFactory : INetworkSenderFactory public static readonly INetworkSenderFactory Default = new NetworkSenderFactory(); /// - public QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQueueOverflowAction onQueueOverflow, int maxMessageSize, System.Security.Authentication.SslProtocols sslProtocols, TimeSpan keepAliveTime, TimeSpan sendTimeout) + public QueuedNetworkSender Create(string url, X509Certificate2Collection? sslCertificateOverride, NetworkTarget networkTarget) { if (url.StartsWith("tcp://", StringComparison.OrdinalIgnoreCase)) { return new TcpNetworkSender(url, AddressFamily.Unspecified) { - MaxQueueSize = maxQueueSize, - OnQueueOverflow = onQueueOverflow, - SslProtocols = sslProtocols, - KeepAliveTime = keepAliveTime, - SendTimeout = sendTimeout, + MaxQueueSize = networkTarget.MaxQueueSize, + OnQueueOverflow = networkTarget.OnQueueOverflow, + NoDelay = networkTarget.NoDelay, + KeepAliveTime = TimeSpan.FromSeconds(networkTarget.KeepAliveTimeSeconds), + SendTimeout = TimeSpan.FromSeconds(networkTarget.SendTimeoutSeconds), + SslProtocols = networkTarget.SslProtocols, + SslCertificateOverride = sslCertificateOverride, }; } @@ -63,11 +66,13 @@ public QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQue { return new TcpNetworkSender(url, AddressFamily.InterNetwork) { - MaxQueueSize = maxQueueSize, - OnQueueOverflow = onQueueOverflow, - SslProtocols = sslProtocols, - KeepAliveTime = keepAliveTime, - SendTimeout = sendTimeout, + MaxQueueSize = networkTarget.MaxQueueSize, + OnQueueOverflow = networkTarget.OnQueueOverflow, + NoDelay = networkTarget.NoDelay, + KeepAliveTime = TimeSpan.FromSeconds(networkTarget.KeepAliveTimeSeconds), + SendTimeout = TimeSpan.FromSeconds(networkTarget.SendTimeoutSeconds), + SslProtocols = networkTarget.SslProtocols, + SslCertificateOverride = sslCertificateOverride, }; } @@ -75,11 +80,13 @@ public QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQue { return new TcpNetworkSender(url, AddressFamily.InterNetworkV6) { - MaxQueueSize = maxQueueSize, - OnQueueOverflow = onQueueOverflow, - SslProtocols = sslProtocols, - KeepAliveTime = keepAliveTime, - SendTimeout = sendTimeout, + MaxQueueSize = networkTarget.MaxQueueSize, + OnQueueOverflow = networkTarget.OnQueueOverflow, + NoDelay = networkTarget.NoDelay, + KeepAliveTime = TimeSpan.FromSeconds(networkTarget.KeepAliveTimeSeconds), + SendTimeout = TimeSpan.FromSeconds(networkTarget.SendTimeoutSeconds), + SslProtocols = networkTarget.SslProtocols, + SslCertificateOverride = sslCertificateOverride, }; } @@ -87,9 +94,9 @@ public QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQue { return new UdpNetworkSender(url, AddressFamily.Unspecified) { - MaxQueueSize = maxQueueSize, - OnQueueOverflow = onQueueOverflow, - MaxMessageSize = maxMessageSize, + MaxQueueSize = networkTarget.MaxQueueSize, + OnQueueOverflow = networkTarget.OnQueueOverflow, + MaxMessageSize = networkTarget.MaxMessageSize, }; } @@ -97,9 +104,9 @@ public QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQue { return new UdpNetworkSender(url, AddressFamily.InterNetwork) { - MaxQueueSize = maxQueueSize, - OnQueueOverflow = onQueueOverflow, - MaxMessageSize = maxMessageSize, + MaxQueueSize = networkTarget.MaxQueueSize, + OnQueueOverflow = networkTarget.OnQueueOverflow, + MaxMessageSize = networkTarget.MaxMessageSize, }; } @@ -107,9 +114,9 @@ public QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQue { return new UdpNetworkSender(url, AddressFamily.InterNetworkV6) { - MaxQueueSize = maxQueueSize, - OnQueueOverflow = onQueueOverflow, - MaxMessageSize = maxMessageSize, + MaxQueueSize = networkTarget.MaxQueueSize, + OnQueueOverflow = networkTarget.OnQueueOverflow, + MaxMessageSize = networkTarget.MaxMessageSize, }; } @@ -117,9 +124,10 @@ public QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQue { return new HttpNetworkSender(url) { - MaxQueueSize = maxQueueSize, - OnQueueOverflow = onQueueOverflow, - SendTimeout = sendTimeout, + MaxQueueSize = networkTarget.MaxQueueSize, + OnQueueOverflow = networkTarget.OnQueueOverflow, + SendTimeout = TimeSpan.FromSeconds(networkTarget.SendTimeoutSeconds), + SslCertificateOverride = sslCertificateOverride, }; } @@ -127,9 +135,10 @@ public QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQue { return new HttpNetworkSender(url) { - MaxQueueSize = maxQueueSize, - OnQueueOverflow = onQueueOverflow, - SendTimeout = sendTimeout, + MaxQueueSize = networkTarget.MaxQueueSize, + OnQueueOverflow = networkTarget.OnQueueOverflow, + SendTimeout = TimeSpan.FromSeconds(networkTarget.SendTimeoutSeconds), + SslCertificateOverride = sslCertificateOverride, }; } diff --git a/src/NLog/Internal/NetworkSenders/QueuedNetworkSender.cs b/src/NLog.Targets.Network/NetworkSenders/QueuedNetworkSender.cs similarity index 96% rename from src/NLog/Internal/NetworkSenders/QueuedNetworkSender.cs rename to src/NLog.Targets.Network/NetworkSenders/QueuedNetworkSender.cs index 3f797fa8bb..cfba1a5c41 100644 --- a/src/NLog/Internal/NetworkSenders/QueuedNetworkSender.cs +++ b/src/NLog.Targets.Network/NetworkSenders/QueuedNetworkSender.cs @@ -61,10 +61,10 @@ public NetworkRequestArgs(byte[] buffer, int offset, int length, AsyncContinuati private readonly Queue _pendingRequests = new Queue(); private readonly Queue _activeRequests = new Queue(); - private Exception _pendingError; + private Exception? _pendingError; private bool _asyncOperationInProgress; - private AsyncContinuation _closeContinuation; - private AsyncContinuation _flushContinuation; + private AsyncContinuation? _closeContinuation; + private AsyncContinuation? _flushContinuation; /// /// Initializes a new instance of the class. @@ -79,12 +79,12 @@ protected QueuedNetworkSender(string url) public NetworkTargetQueueOverflowAction OnQueueOverflow { get; set; } - public event EventHandler LogEventDropped; + public event EventHandler? LogEventDropped; protected override void DoSend(byte[] bytes, int offset, int length, AsyncContinuation asyncContinuation) { NetworkRequestArgs? eventArgs = new NetworkRequestArgs(bytes, offset, length, asyncContinuation); - AsyncContinuation failedContinuation = null; + AsyncContinuation? failedContinuation = null; lock (_pendingRequests) { @@ -195,7 +195,7 @@ protected void BeginInitialize() } } - protected NetworkRequestArgs? EndRequest(AsyncContinuation asyncContinuation, Exception pendingException) + protected NetworkRequestArgs? EndRequest(AsyncContinuation? asyncContinuation, Exception? pendingException) { if (pendingException != null) { @@ -213,7 +213,7 @@ protected void BeginInitialize() catch (Exception ex) { #if DEBUG - if (ex.MustBeRethrownImmediately()) + if (LogManager.ThrowExceptions) { throw; // Throwing exceptions here will crash the entire application } @@ -231,8 +231,8 @@ protected void BeginInitialize() private NetworkRequestArgs? DequeueNextItem() { - AsyncContinuation closeContinuation; - AsyncContinuation flushContinuation; + AsyncContinuation? closeContinuation; + AsyncContinuation? flushContinuation; lock (_activeRequests) { diff --git a/src/NLog/Internal/NetworkSenders/SocketProxy.cs b/src/NLog.Targets.Network/NetworkSenders/SocketProxy.cs similarity index 100% rename from src/NLog/Internal/NetworkSenders/SocketProxy.cs rename to src/NLog.Targets.Network/NetworkSenders/SocketProxy.cs diff --git a/src/NLog/Internal/NetworkSenders/SslSocketProxy.cs b/src/NLog.Targets.Network/NetworkSenders/SslSocketProxy.cs similarity index 76% rename from src/NLog/Internal/NetworkSenders/SslSocketProxy.cs rename to src/NLog.Targets.Network/NetworkSenders/SslSocketProxy.cs index d5783b1860..5839a61fbd 100644 --- a/src/NLog/Internal/NetworkSenders/SslSocketProxy.cs +++ b/src/NLog.Targets.Network/NetworkSenders/SslSocketProxy.cs @@ -31,28 +31,29 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.Internal.NetworkSenders { using System; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; + using System.Security.Cryptography.X509Certificates; internal sealed class SslSocketProxy : ISocket, IDisposable { - readonly AsyncCallback _sendCompleted; - readonly SocketProxy _socketProxy; - readonly string _host; - readonly SslProtocols _sslProtocol; - SslStream _sslStream; - - public SslSocketProxy(string host, SslProtocols sslProtocol, SocketProxy socketProxy) + private readonly AsyncCallback _sendCompleted; + private readonly SocketProxy _socketProxy; + private readonly string _host; + private readonly SslProtocols _sslProtocol; + private readonly X509Certificate2Collection? _sslCertificateOverride; + SslStream? _sslStream; + + public SslSocketProxy(string host, SslProtocols sslProtocol, SocketProxy socketProxy, X509Certificate2Collection? sslCertificateOverride) { _socketProxy = socketProxy; _host = host; _sslProtocol = sslProtocol; + _sslCertificateOverride = sslCertificateOverride; _sendCompleted = (ar) => SocketProxySendCompleted(ar); } @@ -83,7 +84,7 @@ private void SocketProxySendCompleted(IAsyncResult asyncResult) var proxyArgs = asyncResult.AsyncState as TcpNetworkSender.MySocketAsyncEventArgs; try { - _sslStream.EndWrite(asyncResult); + _sslStream?.EndWrite(asyncResult); } catch (SocketException ex) { @@ -127,19 +128,19 @@ private void SocketProxyConnectCompleted(object sender, SocketAsyncEventArgs e) ReadTimeout = 20000 }; - AuthenticateAsClient(_sslStream, _host, _sslProtocol); + AuthenticateAsClient(_sslStream, _host, _sslProtocol, _sslCertificateOverride); } catch (SocketException ex) { if (proxyArgs != null) proxyArgs.SocketError = ex.SocketErrorCode; + NLog.Common.InternalLogger.Error(ex, "NetworkTarget: Failed to complete SSL handshake"); } catch (Exception ex) { if (proxyArgs != null) - { proxyArgs.SocketError = GetSocketError(ex); - } + NLog.Common.InternalLogger.Error(ex, "NetworkTarget: Failed to complete SSL handshake"); } finally { @@ -163,26 +164,35 @@ private static SocketError GetSocketError(Exception ex) return socketError; } - private static void AuthenticateAsClient(SslStream sslStream, string targetHost, SslProtocols enabledSslProtocols) + private static void AuthenticateAsClient(SslStream sslStream, string targetHost, SslProtocols enabledSslProtocols, X509Certificate2Collection? sslCertificateOverride) { - if (enabledSslProtocols != SslProtocols.Default) - sslStream.AuthenticateAsClient(targetHost, null, enabledSslProtocols, false); +#pragma warning disable CS0618 // Type or member is obsolete + if (enabledSslProtocols != SslProtocols.Default || sslCertificateOverride != null) +#pragma warning restore CS0618 // Type or member is obsolete + { + sslStream.AuthenticateAsClient(targetHost, sslCertificateOverride?.Count > 0 ? sslCertificateOverride : null, enabledSslProtocols, false); + } else + { sslStream.AuthenticateAsClient(targetHost); + } } - private static bool UserCertificateValidationCallback(object sender, object certificate, object chain, SslPolicyErrors sslPolicyErrors) + private bool UserCertificateValidationCallback(object sender, object certificate, object chain, SslPolicyErrors sslPolicyErrors) { if (sslPolicyErrors == SslPolicyErrors.None) return true; - Common.InternalLogger.Debug("SSL certificate errors were encountered when establishing connection to the server: {0}, Certificate: {1}", sslPolicyErrors, certificate); - return false; + Common.InternalLogger.Warn("SSL certificate errors were encountered when establishing connection to the server: {0}, Certificate: {1}", sslPolicyErrors, certificate); + if (certificate is null) + return false; + + return _sslCertificateOverride != null; } public bool SendAsync(SocketAsyncEventArgs args) { - _sslStream.BeginWrite(args.Buffer, args.Offset, args.Count, _sendCompleted, args); + _sslStream?.BeginWrite(args.Buffer, args.Offset, args.Count, _sendCompleted, args); return true; } @@ -200,5 +210,3 @@ public void Dispose() } } } - -#endif diff --git a/src/NLog/Internal/NetworkSenders/TcpNetworkSender.cs b/src/NLog.Targets.Network/NetworkSenders/TcpNetworkSender.cs similarity index 88% rename from src/NLog/Internal/NetworkSenders/TcpNetworkSender.cs rename to src/NLog.Targets.Network/NetworkSenders/TcpNetworkSender.cs index 4873bd2623..8ccd23713b 100644 --- a/src/NLog/Internal/NetworkSenders/TcpNetworkSender.cs +++ b/src/NLog.Targets.Network/NetworkSenders/TcpNetworkSender.cs @@ -36,7 +36,9 @@ namespace NLog.Internal.NetworkSenders using System; using System.IO; using System.Net.Sockets; + using System.Security.Cryptography.X509Certificates; using NLog.Common; + using NLog.Targets.Internal; /// /// Sends messages over a TCP network connection. @@ -44,9 +46,9 @@ namespace NLog.Internal.NetworkSenders internal class TcpNetworkSender : QueuedNetworkSender { private static bool? EnableKeepAliveSuccessful; - private ISocket _socket; + private ISocket? _socket; private readonly EventHandler _socketOperationCompletedAsync; - private AsyncHelpersTask? _asyncBeginRequest; + System.Threading.WaitCallback? _asyncBeginRequest; /// /// Initializes a new instance of the class. @@ -64,10 +66,14 @@ public TcpNetworkSender(string url, AddressFamily addressFamily) internal System.Security.Authentication.SslProtocols SslProtocols { get; set; } + internal X509Certificate2Collection? SslCertificateOverride { get; set; } + internal TimeSpan KeepAliveTime { get; set; } internal TimeSpan SendTimeout { get; set; } + internal bool NoDelay { get; set; } + /// /// Creates the socket with given parameters. /// @@ -75,28 +81,32 @@ public TcpNetworkSender(string url, AddressFamily addressFamily) /// The address family. /// Type of the socket. /// Type of the protocol. - /// Socket SendTimeout. + /// /// Instance of which represents the socket. - protected internal virtual ISocket CreateSocket(string host, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, TimeSpan sendTimeout) + protected internal virtual ISocket CreateSocket(string host, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) { var socketProxy = new SocketProxy(addressFamily, socketType, protocolType); + if (NoDelay) + { + socketProxy.UnderlyingSocket.NoDelay = true; + } + if (KeepAliveTime.TotalSeconds >= 1.0 && EnableKeepAliveSuccessful != false) { EnableKeepAliveSuccessful = TryEnableKeepAlive(socketProxy.UnderlyingSocket, (int)KeepAliveTime.TotalSeconds); } - if (sendTimeout > TimeSpan.Zero) + if (SendTimeout > TimeSpan.Zero) { - socketProxy.UnderlyingSocket.SendTimeout = (int)sendTimeout.TotalMilliseconds; + socketProxy.UnderlyingSocket.SendTimeout = (int)SendTimeout.TotalMilliseconds; } -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 if (SslProtocols != System.Security.Authentication.SslProtocols.None) { - return new SslSocketProxy(host, SslProtocols, socketProxy); + return new SslSocketProxy(host, SslProtocols, socketProxy, SslCertificateOverride); } -#endif + return socketProxy; } @@ -111,7 +121,7 @@ private static bool TryEnableKeepAlive(Socket underlyingSocket, int keepAliveTim SocketOptionName TcpKeepAliveTime = (SocketOptionName)0x3; SocketOptionName TcpKeepAliveInterval = (SocketOptionName)0x11; - if (PlatformDetector.CurrentOS == RuntimeOS.Linux) + if (PlatformDetector.CurrentOS == PlatformOS.Linux) { // https://github.com/torvalds/linux/blob/v4.16/include/net/tcp.h // #define TCP_KEEPIDLE 4 /* Start keepalives after this period */ @@ -119,7 +129,7 @@ private static bool TryEnableKeepAlive(Socket underlyingSocket, int keepAliveTim TcpKeepAliveTime = (SocketOptionName)0x4; TcpKeepAliveInterval = (SocketOptionName)0x5; } - else if (PlatformDetector.CurrentOS == RuntimeOS.MacOSX) + else if (PlatformDetector.CurrentOS == PlatformOS.MacOSX) { // https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/netinet/tcp.h.auto.html // #define TCP_KEEPALIVE 0x10 /* idle time used when SO_KEEPALIVE is enabled */ @@ -176,7 +186,7 @@ protected override void DoInitialize() args.Completed += _socketOperationCompletedAsync; args.UserToken = null; - _socket = CreateSocket(uri.Host, args.RemoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp, SendTimeout); + _socket = CreateSocket(uri.Host, args.RemoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); base.BeginInitialize(); bool asyncOperation = false; @@ -207,7 +217,7 @@ protected override void DoClose(AsyncContinuation continuation) base.DoClose(ex => CloseSocket(continuation, ex)); } - private void CloseSocket(AsyncContinuation continuation, Exception pendingException) + private void CloseSocket(AsyncContinuation continuation, Exception? pendingException) { try { @@ -219,7 +229,7 @@ private void CloseSocket(AsyncContinuation continuation, Exception pendingExcept } catch (Exception exception) { - if (exception.MustBeRethrown()) + if (LogManager.ThrowExceptions) { throw; } @@ -236,8 +246,8 @@ protected override void BeginRequest(NetworkRequestArgs eventArgs) // Schedule async network operation to avoid blocking socket-operation (Allow adding more request) if (_asyncBeginRequest is null) - _asyncBeginRequest = new AsyncHelpersTask(BeginRequestAsync); - AsyncHelpers.StartAsyncTask(_asyncBeginRequest.Value, socketEventArgs); + _asyncBeginRequest = BeginRequestAsync; + System.Threading.ThreadPool.QueueUserWorkItem(_asyncBeginRequest, socketEventArgs); } private void BeginRequestAsync(object state) @@ -253,7 +263,7 @@ private void BeginSocketRequest(SocketAsyncEventArgs args) { try { - asyncOperation = _socket.SendAsync(args); + asyncOperation = _socket?.SendAsync(args) ?? false; } catch (SocketException ex) { @@ -269,12 +279,16 @@ private void BeginSocketRequest(SocketAsyncEventArgs args) args.SocketError = SocketError.OperationAborted; } - args = asyncOperation ? null : SocketOperationCompleted(args); + var nextArgs = asyncOperation ? null : SocketOperationCompleted(args); + if (nextArgs is null) + break; + + args = nextArgs; } while (args != null); } - private void SetSocketNetworkRequest(SocketAsyncEventArgs socketEventArgs, NetworkRequestArgs networkRequest) + private static void SetSocketNetworkRequest(SocketAsyncEventArgs socketEventArgs, NetworkRequestArgs networkRequest) { socketEventArgs.SetBuffer(networkRequest.RequestBuffer, networkRequest.RequestBufferOffset, networkRequest.RequestBufferLength); socketEventArgs.UserToken = networkRequest.AsyncContinuation; @@ -289,9 +303,9 @@ private void SocketOperationCompletedAsync(object sender, SocketAsyncEventArgs a } } - private SocketAsyncEventArgs SocketOperationCompleted(SocketAsyncEventArgs args) + private SocketAsyncEventArgs? SocketOperationCompleted(SocketAsyncEventArgs args) { - Exception socketException = null; + Exception? socketException = null; if (args.SocketError != SocketError.Success) { socketException = new IOException($"Error: {args.SocketError.ToString()}, Address: {Address}"); @@ -312,7 +326,7 @@ private SocketAsyncEventArgs SocketOperationCompleted(SocketAsyncEventArgs args) } } - public override ISocket CheckSocket() + public override ISocket? CheckSocket() { if (_socket is null) { diff --git a/src/NLog/Internal/NetworkSenders/UdpNetworkSender.cs b/src/NLog.Targets.Network/NetworkSenders/UdpNetworkSender.cs similarity index 90% rename from src/NLog/Internal/NetworkSenders/UdpNetworkSender.cs rename to src/NLog.Targets.Network/NetworkSenders/UdpNetworkSender.cs index 207199fed9..4822afd1b2 100644 --- a/src/NLog/Internal/NetworkSenders/UdpNetworkSender.cs +++ b/src/NLog.Targets.Network/NetworkSenders/UdpNetworkSender.cs @@ -44,10 +44,10 @@ namespace NLog.Internal.NetworkSenders /// internal class UdpNetworkSender : QueuedNetworkSender { - private ISocket _socket; - private EndPoint _endpoint; + private ISocket? _socket; + private EndPoint? _endpoint; private readonly EventHandler _socketOperationCompletedAsync; - private AsyncHelpersTask? _asyncBeginRequest; + System.Threading.WaitCallback? _asyncBeginRequest; /// /// Initializes a new instance of the class. @@ -93,7 +93,7 @@ protected override void DoClose(AsyncContinuation continuation) base.DoClose(ex => CloseSocket(continuation, ex)); } - private void CloseSocket(AsyncContinuation continuation, Exception pendingException) + private void CloseSocket(AsyncContinuation continuation, Exception? pendingException) { try { @@ -105,7 +105,7 @@ private void CloseSocket(AsyncContinuation continuation, Exception pendingExcept } catch (Exception exception) { - if (exception.MustBeRethrown()) + if (LogManager.ThrowExceptions) { throw; } @@ -123,8 +123,8 @@ protected override void BeginRequest(NetworkRequestArgs eventArgs) // Schedule async network operation to avoid blocking socket-operation (Allow adding more request) if (_asyncBeginRequest is null) - _asyncBeginRequest = new AsyncHelpersTask(BeginRequestAsync); - AsyncHelpers.StartAsyncTask(_asyncBeginRequest.Value, socketEventArgs); + _asyncBeginRequest = BeginRequestAsync; + System.Threading.ThreadPool.QueueUserWorkItem(_asyncBeginRequest, socketEventArgs); } private void BeginRequestAsync(object state) @@ -140,7 +140,7 @@ private void BeginSocketRequest(SocketAsyncEventArgs args) { try { - asyncOperation = _socket.SendToAsync(args); + asyncOperation = _socket?.SendToAsync(args) ?? false; } catch (SocketException ex) { @@ -156,7 +156,11 @@ private void BeginSocketRequest(SocketAsyncEventArgs args) args.SocketError = SocketError.OperationAborted; } - args = asyncOperation ? null : SocketOperationCompleted(args); + var nextArgs = asyncOperation ? null : SocketOperationCompleted(args); + if (nextArgs is null) + break; + + args = nextArgs; } while (args != null); } @@ -177,9 +181,9 @@ private void SocketOperationCompletedAsync(object sender, SocketAsyncEventArgs a } } - private SocketAsyncEventArgs SocketOperationCompleted(SocketAsyncEventArgs args) + private SocketAsyncEventArgs? SocketOperationCompleted(SocketAsyncEventArgs args) { - Exception socketException = null; + Exception? socketException = null; if (args.SocketError != SocketError.Success) { socketException = new IOException($"Error: {args.SocketError.ToString()}, Address: {Address}"); @@ -207,7 +211,7 @@ private SocketAsyncEventArgs SocketOperationCompleted(SocketAsyncEventArgs args) } } - public override ISocket CheckSocket() + public override ISocket? CheckSocket() { if (_socket is null) { diff --git a/src/NLog.Targets.Network/Properties/AssemblyInfo.cs b/src/NLog.Targets.Network/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..797aaa2bce --- /dev/null +++ b/src/NLog.Targets.Network/Properties/AssemblyInfo.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] +[assembly: InternalsVisibleTo("NLog.Targets.Network.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ef8eab4fbdeb511eeb475e1659fe53f00ec1c1340700f1aa347bf3438455d71993b28b1efbed44c8d97a989e0cb6f01bcb5e78f0b055d311546f63de0a969e04cf04450f43834db9f909e566545a67e42822036860075a1576e90e1c43d43e023a24c22a427f85592ae56cac26f13b7ec2625cbc01f9490d60f16cfbb1bc34d9")] +// NSubstitute +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +[assembly: AllowPartiallyTrustedCallers] +#if !NET35 +[assembly: SecurityRules(SecurityRuleSet.Level1)] +#endif diff --git a/src/NLog.Targets.Network/README.md b/src/NLog.Targets.Network/README.md new file mode 100644 index 0000000000..c12519d033 --- /dev/null +++ b/src/NLog.Targets.Network/README.md @@ -0,0 +1,50 @@ +# NLog Network Target + +NLog Network Target for sending mesages using TCP / UDP sockets with support for SSL / TSL. + +If having trouble with output, then check [NLog InternalLogger](https://github.com/NLog/NLog/wiki/Internal-Logging) for clues. See also [Troubleshooting NLog](https://github.com/NLog/NLog/wiki/Logging-Troubleshooting) + +See the [NLog Wiki - Network Target](https://github.com/NLog/NLog/wiki/Network-target) for available options and examples. + +## NLog SysLog Target + +NLog Syslog Target combines the NLog NetworkTarget with NLog SyslogLayout + +See the [NLog Wiki - Syslog Target](https://github.com/NLog/NLog/wiki/Syslog-target) for available options and examples. + +## NLog GELF Target + +NLog Gelf Target combines the NLog NetworkTarget with NLog GelfLayout for Graylog Extended Logging Format (GELF) + +See the [NLog Wiki - Gelf Target](https://github.com/NLog/NLog/wiki/Gelf-target) for available options and examples. + +## NLog Log4JXml Target + +NLog Log4JXml Target combines the NLog NetworkTarget with NLog Log4JXmlEventLayout for NLogViewer / Chainsaw. + +See the [NLog Wiki - Log4JXml Target](https://github.com/NLog/NLog/wiki/Log4JXml-target) for available options and examples. + +## Register Extension + +NLog will only recognize type-alias `Network` when loading from `NLog.config`-file, if having added extension to `NLog.config`-file: + +```xml + + + +``` + +Alternative register from code using [fluent configuration API](https://github.com/NLog/NLog/wiki/Fluent-Configuration-API): + +```csharp +LogManager.Setup().SetupExtensions(ext => { + ext.RegisterTarget(); + ext.RegisterTarget(); + ext.RegisterTarget(); + ext.RegisterTarget(); + ext.RegisterTarget(); + ext.RegisterLayout(); + ext.RegisterLayout(); + ext.RegisterLayout(); +}); +``` diff --git a/src/NLog.Targets.Network/Targets/GelfTarget.cs b/src/NLog.Targets.Network/Targets/GelfTarget.cs new file mode 100644 index 0000000000..27d71279a5 --- /dev/null +++ b/src/NLog.Targets.Network/Targets/GelfTarget.cs @@ -0,0 +1,108 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets +{ + using System.Collections.Generic; + using NLog.Config; + using NLog.Layouts; + + /// + /// Sends log messages to GrayLog server using either TCP or UDP with GELF-format + /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + [Target("Gelf")] + public class GelfTarget : NetworkTarget + { + private readonly GelfLayout _gelfLayout = new GelfLayout(); + + /// + public Layout GelfHostName { get => _gelfLayout.GelfHostName; set => _gelfLayout.GelfHostName = value; } + + /// + public Layout GelfShortMessage { get => _gelfLayout.GelfShortMessage; set => _gelfLayout.GelfShortMessage = value; } + + /// + public Layout GelfFullMessage { get => _gelfLayout.GelfFullMessage; set => _gelfLayout.GelfFullMessage = value; } + + /// + public Layout GelfFacility { get => _gelfLayout.GelfFacility; set => _gelfLayout.GelfFacility = value; } + + /// + [ArrayParameter(typeof(TargetPropertyWithContext), "GelfField")] + public List GelfFields { get => _gelfLayout.GelfFields; } + + /// + public bool IncludeEventProperties { get => _gelfLayout.IncludeEventProperties; set => _gelfLayout.IncludeEventProperties = value; } + + /// + public bool IncludeScopeProperties { get => _gelfLayout.IncludeScopeProperties; set => _gelfLayout.IncludeScopeProperties = value; } + + /// + public bool ExcludeEmptyProperties { get => _gelfLayout.ExcludeEmptyProperties; set => _gelfLayout.ExcludeEmptyProperties = value; } + + /// +#if NET35 + public HashSet ExcludeProperties { get => _gelfLayout.ExcludeProperties; } +#else + public ISet ExcludeProperties { get => _gelfLayout.ExcludeProperties; } +#endif + + /// +#if NET35 + public HashSet IncludeProperties { get => _gelfLayout.IncludeProperties; } +#else + public ISet IncludeProperties { get => _gelfLayout.IncludeProperties; } +#endif + + /// + public override Layout Layout + { + get => _gelfLayout; + set { /* Fixed GelfLayout */ } // NOSONAR + } + + /// + /// Initializes a new instance of the class. + /// + public GelfTarget() + { + LineEnding = LineEndingMode.Null; // Graylog Server oftens uses NUL-byte as message-delimiter for TCP (but prevents using compression) + NewLine = false; // LineEnding must be explicit enabled when using TCP + Layout = _gelfLayout; + } + } +} diff --git a/src/NLog/Targets/NLogViewerTarget.cs b/src/NLog.Targets.Network/Targets/Log4JXmlTarget.cs similarity index 74% rename from src/NLog/Targets/NLogViewerTarget.cs rename to src/NLog.Targets.Network/Targets/Log4JXmlTarget.cs index 217d72f726..b1b166f321 100644 --- a/src/NLog/Targets/NLogViewerTarget.cs +++ b/src/NLog.Targets.Network/Targets/Log4JXmlTarget.cs @@ -37,52 +37,50 @@ namespace NLog.Targets using System.Collections.Generic; using System.ComponentModel; using NLog.Config; - using NLog.LayoutRenderers; using NLog.Layouts; /// - /// Sends log messages to the remote instance of NLog Viewer. + /// Sends log messages to the remote instance of Chainsaw / NLogViewer application using Log4J Xml /// /// - /// See NLog Wiki + /// See NLog Wiki /// - /// Documentation on NLog Wiki + /// Documentation on NLog Wiki /// ///

/// To set up the target in the configuration file, /// use the following syntax: ///

- /// + /// ///

/// To set up the log target programmatically use code like this: ///

- /// + /// ///
- [Target("NLogViewer")] - public class NLogViewerTarget : NetworkTarget, IIncludeContext + [Target("Log4JXml")] + [Target("Chainsaw")] + public class Log4JXmlTarget : NetworkTarget { private readonly Log4JXmlEventLayout _log4JLayout = new Log4JXmlEventLayout(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} /// - public NLogViewerTarget() + public Log4JXmlTarget() { - IncludeNLogData = true; - IncludeEventProperties = false; // Already included when IncludeNLogData = true } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} /// /// Name of the target. - public NLogViewerTarget(string name) : this() + public Log4JXmlTarget(string name) : this() { Name = name; } @@ -91,10 +89,12 @@ public NLogViewerTarget(string name) : this() /// Gets or sets a value indicating whether to include NLog-specific extensions to log4j schema. ///
/// + [Obsolete("Non standard extension to the Log4j-XML format. Marked obsolete with NLog v5.4")] + [EditorBrowsable(EditorBrowsableState.Never)] public bool IncludeNLogData { - get => Renderer.IncludeNLogData; - set => Renderer.IncludeNLogData = value; + get => _log4JLayout.IncludeNLogData; + set => _log4JLayout.IncludeNLogData = value; } /// @@ -103,8 +103,8 @@ public bool IncludeNLogData /// public Layout LoggerName { - get => Renderer.LoggerName; - set => Renderer.LoggerName = value; + get => _log4JLayout.LoggerName; + set => _log4JLayout.LoggerName = value; } /// @@ -113,8 +113,8 @@ public Layout LoggerName /// public Layout FormattedMessage { - get => Renderer.FormattedMessage; - set => Renderer.FormattedMessage = value; + get => _log4JLayout.FormattedMessage; + set => _log4JLayout.FormattedMessage = value; } /// @@ -123,8 +123,8 @@ public Layout FormattedMessage /// public Layout AppInfo { - get => Renderer.AppInfo; - set => Renderer.AppInfo = value; + get => _log4JLayout.AppInfo; + set => _log4JLayout.AppInfo = value; } /// @@ -133,8 +133,8 @@ public Layout AppInfo /// public bool IncludeCallSite { - get => Renderer.IncludeCallSite; - set => Renderer.IncludeCallSite = value; + get => _log4JLayout.IncludeCallSite; + set => _log4JLayout.IncludeCallSite = value; } /// @@ -143,8 +143,8 @@ public bool IncludeCallSite /// public bool IncludeSourceInfo { - get => Renderer.IncludeSourceInfo; - set => Renderer.IncludeSourceInfo = value; + get => _log4JLayout.IncludeSourceInfo; + set => _log4JLayout.IncludeSourceInfo = value; } /// @@ -156,8 +156,8 @@ public bool IncludeSourceInfo [EditorBrowsable(EditorBrowsableState.Never)] public bool IncludeMdc { - get => Renderer.IncludeMdc; - set => Renderer.IncludeMdc = value; + get => _log4JLayout.IncludeMdc; + set => _log4JLayout.IncludeMdc = value; } /// @@ -166,33 +166,33 @@ public bool IncludeMdc /// public bool IncludeNdc { - get => Renderer.IncludeNdc; - set => Renderer.IncludeNdc = value; + get => _log4JLayout.IncludeNdc; + set => _log4JLayout.IncludeNdc = value; } /// /// Gets or sets the option to include all properties from the log events /// /// - public bool IncludeEventProperties { get => Renderer.IncludeEventProperties; set => Renderer.IncludeEventProperties = value; } + public bool IncludeEventProperties { get => _log4JLayout.IncludeEventProperties; set => _log4JLayout.IncludeEventProperties = value; } /// /// Gets or sets whether to include the contents of the properties-dictionary. /// /// - public bool IncludeScopeProperties { get => Renderer.IncludeScopeProperties; set => Renderer.IncludeScopeProperties = value; } + public bool IncludeScopeProperties { get => _log4JLayout.IncludeScopeProperties; set => _log4JLayout.IncludeScopeProperties = value; } /// /// Gets or sets whether to include log4j:NDC in output from nested context. /// /// - public bool IncludeScopeNested { get => Renderer.IncludeScopeNested; set => Renderer.IncludeScopeNested = value; } + public bool IncludeScopeNested { get => _log4JLayout.IncludeScopeNested; set => _log4JLayout.IncludeScopeNested = value; } /// /// Gets or sets the separator for operation-states-stack. /// /// - public string ScopeNestedSeparator { get => Renderer.ScopeNestedSeparator; set => Renderer.ScopeNestedSeparator = value; } + public string ScopeNestedSeparator { get => _log4JLayout.ScopeNestedSeparator; set => _log4JLayout.ScopeNestedSeparator = value; } /// /// Obsolete and replaced by with NLog v5. @@ -210,7 +210,7 @@ public bool IncludeNdc /// [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] - public bool IncludeMdlc { get => Renderer.IncludeMdlc; set => Renderer.IncludeMdlc = value; } + public bool IncludeMdlc { get => _log4JLayout.IncludeMdlc; set => _log4JLayout.IncludeMdlc = value; } /// /// Obsolete and replaced by with NLog v5. @@ -219,7 +219,7 @@ public bool IncludeNdc /// [Obsolete("Replaced by IncludeNdc. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] - public bool IncludeNdlc { get => Renderer.IncludeNdlc; set => Renderer.IncludeNdlc = value; } + public bool IncludeNdlc { get => _log4JLayout.IncludeNdlc; set => _log4JLayout.IncludeNdlc = value; } /// /// Obsolete and replaced by with NLog v5. @@ -228,7 +228,7 @@ public bool IncludeNdc /// [Obsolete("Replaced by NdcItemSeparator. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] - public string NdlcItemSeparator { get => Renderer.NdlcItemSeparator; set => Renderer.NdlcItemSeparator = value; } + public string NdlcItemSeparator { get => _log4JLayout.NdlcItemSeparator; set => _log4JLayout.NdlcItemSeparator = value; } /// /// Gets or sets the stack separator for log4j:NDC in output from nested context. @@ -236,8 +236,8 @@ public bool IncludeNdc /// public string NdcItemSeparator { - get => Renderer.NdcItemSeparator; - set => Renderer.NdcItemSeparator = value; + get => _log4JLayout.NdcItemSeparator; + set => _log4JLayout.NdcItemSeparator = value; } /// @@ -245,13 +245,8 @@ public string NdcItemSeparator /// between NLog layout and a named parameter. /// /// - [ArrayParameter(typeof(NLogViewerParameterInfo), "parameter")] - public IList Parameters => _log4JLayout.Parameters; - - /// - /// Gets the layout renderer which produces Log4j-compatible XML events. - /// - public Log4JXmlEventLayoutRenderer Renderer => _log4JLayout.Renderer; + [ArrayParameter(typeof(Log4JXmlEventParameter), "parameter")] + public IList Parameters => _log4JLayout.Parameters; /// /// Gets or sets the instance of that is used to format log messages. @@ -259,14 +254,8 @@ public string NdcItemSeparator /// public override Layout Layout { - get - { - return _log4JLayout; - } - set - { - // Fixed Log4JXmlEventLayout - } + get => _log4JLayout; + set { /* Fixed Log4JXmlEventLayout */ } // NOSONAR } } } diff --git a/src/NLog/Targets/NetworkLogEventDroppedEventArgs.cs b/src/NLog.Targets.Network/Targets/NetworkLogEventDroppedEventArgs.cs similarity index 99% rename from src/NLog/Targets/NetworkLogEventDroppedEventArgs.cs rename to src/NLog.Targets.Network/Targets/NetworkLogEventDroppedEventArgs.cs index a639a0634c..2ffb747028 100644 --- a/src/NLog/Targets/NetworkLogEventDroppedEventArgs.cs +++ b/src/NLog.Targets.Network/Targets/NetworkLogEventDroppedEventArgs.cs @@ -31,10 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; - namespace NLog.Targets { + using System; + /// /// Arguments for events. /// diff --git a/src/NLog/Targets/NetworkLogEventDroppedReason.cs b/src/NLog.Targets.Network/Targets/NetworkLogEventDroppedReason.cs similarity index 100% rename from src/NLog/Targets/NetworkLogEventDroppedReason.cs rename to src/NLog.Targets.Network/Targets/NetworkLogEventDroppedReason.cs diff --git a/src/NLog/Targets/NetworkTarget.cs b/src/NLog.Targets.Network/Targets/NetworkTarget.cs similarity index 73% rename from src/NLog/Targets/NetworkTarget.cs rename to src/NLog.Targets.Network/Targets/NetworkTarget.cs index 6fad19123a..e7c5f873a0 100644 --- a/src/NLog/Targets/NetworkTarget.cs +++ b/src/NLog.Targets.Network/Targets/NetworkTarget.cs @@ -38,12 +38,12 @@ namespace NLog.Targets using System.Text; using System.Threading; using NLog.Common; - using NLog.Internal; - using NLog.Internal.NetworkSenders; using NLog.Layouts; + using NLog.Internal.NetworkSenders; + using System.Security.Cryptography.X509Certificates; /// - /// Sends log messages over the network. + /// NetworkTarget for sending messages over the network using TCP / UDP sockets /// /// /// See NLog Wiki @@ -66,11 +66,6 @@ namespace NLog.Targets /// demonstrates the NetCat tool receiving log messages from Network target. ///

/// - ///

- /// There are two specialized versions of the Network target: Chainsaw - /// and NLogViewer which write to instances of Chainsaw log4j viewer - /// or NLogViewer application respectively. - ///

/// [Target("Network")] public class NetworkTarget : TargetWithLayout @@ -78,7 +73,11 @@ public class NetworkTarget : TargetWithLayout private readonly Dictionary> _currentSenderCache = new Dictionary>(StringComparer.Ordinal); private readonly LinkedList _openNetworkSenders = new LinkedList(); - private readonly ReusableBufferCreator _reusableEncodingBuffer = new ReusableBufferCreator(32 * 1024); + private readonly char[] _reusableEncodingBuffer = new char[32 * 1024]; + private readonly StringBuilder _reusableStringBuilder = new StringBuilder(); + + private readonly object _certificateCacheLock = new object(); + private Dictionary? _certificateCache; /// /// Initializes a new instance of the class. @@ -104,10 +103,10 @@ public NetworkTarget(string name) : this() } /// - /// Gets or sets the network address. + /// Gets or sets the network destination address. /// /// - /// The network address can be: + /// The network destination address can be: ///
    ///
  • tcp://host:port - TCP (auto select IPv4/IPv6)
  • ///
  • tcp4://host:port - force TCP/IPv4
  • @@ -121,7 +120,7 @@ public NetworkTarget(string name) : this() /// For SOAP-based webservice support over HTTP use WebService target. /// /// - public Layout Address { get; set; } + public Layout Address { get; set; } = Layout.Empty; /// /// Gets or sets a value indicating whether to keep connection open whenever possible. @@ -145,7 +144,7 @@ public LineEndingMode LineEnding set { _lineEnding = value; - NewLine = value != null; + NewLine = value?.NewLineCharacters?.Length > 0; } } private LineEndingMode _lineEnding = LineEndingMode.CRLF; @@ -172,7 +171,7 @@ public LineEndingMode LineEnding public NetworkTargetConnectionsOverflowAction OnConnectionOverflow { get; set; } = NetworkTargetConnectionsOverflowAction.Discard; /// - /// Gets or sets the maximum queue size for a single connection. Requires = true + /// Gets or sets the maximum queue size for a single connection. Requires = /// /// /// When having reached the maximum limit, then action will apply. @@ -194,10 +193,10 @@ public LineEndingMode LineEnding /// - When connection-list is full and set to
    /// - When message is too big and set to
    ///
    - public event EventHandler LogEventDropped; + public event EventHandler? LogEventDropped; /// - /// Gets or sets the size of the connection cache (number of connections which are kept alive). Requires = true + /// Gets or sets the size of the connection cache (number of connections which are kept alive). Requires = /// /// public int ConnectionCacheSize { get; set; } = 5; @@ -228,6 +227,18 @@ public LineEndingMode LineEnding /// public System.Security.Authentication.SslProtocols SslProtocols { get; set; } = System.Security.Authentication.SslProtocols.None; + /// + /// Gets or sets the file path to custom SSL certificate for TCP Socket SSL connections + /// + /// + public Layout? SslCertificateFile { get; set; } + + /// + /// Gets or sets the password for the custom SSL certificate specified by + /// + /// + public Layout? SslCertificatePassword { get; set; } + /// /// The number of seconds a connection will remain idle before the first keep-alive probe is sent /// @@ -235,10 +246,16 @@ public LineEndingMode LineEnding public int KeepAliveTimeSeconds { get; set; } /// - /// The number of seconds a TCP socket send-operation will block before timeout error. Default wait forever when network cable unplugged and tcp-buffer becomes full. + /// The number of seconds a TCP socket send-operation will block before timeout error. Default = 100 secs (0 = wait forever when network cable unplugged and tcp-buffer becomes full). /// /// - public int SendTimeoutSeconds { get; set; } + public int SendTimeoutSeconds { get; set; } = 100; + + /// + /// Gets or sets whether to disable the delayed ACK timer, and avoid delay of 200 ms. + /// + /// Default: + public bool NoDelay { get; set; } = true; /// /// Type of compression for protocol payload. Useful for UDP where datagram max-size is 8192 bytes. @@ -252,6 +269,15 @@ public LineEndingMode LineEnding internal INetworkSenderFactory SenderFactory { get; set; } + /// + protected override void InitializeTarget() + { + base.InitializeTarget(); + + if (Address is null || ReferenceEquals(Address, Layout.Empty)) + throw new NLogConfigurationException($"{GetType()} Address-property must be assigned. Address is needed for network destination."); + } + /// /// Flush any pending log messages asynchronously (in case of asynchronous targets). /// @@ -260,7 +286,7 @@ protected override void FlushAsync(AsyncContinuation asyncContinuation) { int remainingCount; - void Continuation(Exception ex) + void Continuation(Exception? ex) { // ignore exception if (Interlocked.Decrement(ref remainingCount) == 0) @@ -308,6 +334,12 @@ protected override void CloseTarget() _currentSenderCache.Clear(); } + + if (_certificateCache?.Count > 0) + { + // Safe to reset without lock, since immutable collection + _certificateCache = null; + } } /// @@ -319,6 +351,7 @@ protected override void Write(AsyncLogEventInfo logEvent) { string address = RenderLogEvent(Address, logEvent.LogEvent); byte[] payload = GetBytesToWrite(logEvent.LogEvent); + byte[]? header = GetHeaderToWrite(logEvent.LogEvent, address, payload); int messageSize = payload.Length; InternalLogger.Trace("{0}: Sending {1} bytes to address: '{2}'", this, messageSize, address); @@ -350,20 +383,20 @@ protected override void Write(AsyncLogEventInfo logEvent) if (KeepConnection) { - WriteBytesToCachedNetworkSender(address, payload, logEvent); + WriteBytesToCachedNetworkSender(address, header, payload, logEvent); } else { - WriteBytesToNewNetworkSender(address, payload, logEvent); + WriteBytesToNewNetworkSender(address, header, payload, logEvent); } } - private void WriteBytesToCachedNetworkSender(string address, byte[] payload, AsyncLogEventInfo logEvent) + private void WriteBytesToCachedNetworkSender(string address, byte[]? header, byte[] payload, AsyncLogEventInfo logEvent) { LinkedListNode senderNode; try { - senderNode = GetCachedNetworkSender(address); + senderNode = GetCachedNetworkSender(address, logEvent.LogEvent); } catch (Exception ex) { @@ -375,6 +408,7 @@ private void WriteBytesToCachedNetworkSender(string address, byte[] payload, Asy WriteBytesToNetworkSender( senderNode.Value, payload, + header, ex => { if (ex != null) @@ -388,7 +422,7 @@ private void WriteBytesToCachedNetworkSender(string address, byte[] payload, Asy }); } - private void WriteBytesToNewNetworkSender(string address, byte[] payload, AsyncLogEventInfo logEvent) + private void WriteBytesToNewNetworkSender(string address, byte[]? header, byte[] payload, AsyncLogEventInfo logEvent) { NetworkSender sender; LinkedListNode linkedListNode; @@ -428,7 +462,7 @@ private void WriteBytesToNewNetworkSender(string address, byte[] payload, AsyncL try { - sender = CreateNetworkSender(address); + sender = CreateNetworkSender(address, logEvent.LogEvent); } catch (Exception ex) { @@ -443,6 +477,7 @@ private void WriteBytesToNewNetworkSender(string address, byte[] payload, AsyncL WriteBytesToNetworkSender( sender, payload, + header, ex => { lock (_openNetworkSenders) @@ -488,7 +523,7 @@ private static bool TryRemove(LinkedList list, LinkedListNode node) } /// - /// Gets the bytes to be written. + /// Gets the payload bytes to be written. /// /// Log event. /// Byte array. @@ -504,26 +539,36 @@ protected virtual byte[] GetBytesToWrite(LogEventInfo logEvent) return payload; } + /// + /// Gets the header bytes to be written. + /// + /// Log event. + /// network address. + /// Payload buffer. + /// Byte array. + protected virtual byte[]? GetHeaderToWrite(LogEventInfo logEvent, string address, byte[] payload) + { + return null; + } + private byte[] RenderBytesToWrite(LogEventInfo logEvent) { - using (var localBuffer = _reusableEncodingBuffer.Allocate()) + lock (_reusableEncodingBuffer) { - if (!NewLine && logEvent.TryGetCachedLayoutValue(Layout, out var text)) - { - return GetBytesFromString(localBuffer.Result, text?.ToString() ?? string.Empty); - } - else + try { - using (var localBuilder = ReusableLayoutBuilder.Allocate()) + _reusableStringBuilder.Length = 0; + Layout.Render(logEvent, _reusableStringBuilder); + if (NewLine) { - Layout.Render(logEvent, localBuilder.Result); - if (NewLine) - { - localBuilder.Result.Append(LineEnding.NewLineCharacters); - } - - return GetBytesFromStringBuilder(localBuffer.Result, localBuilder.Result); + _reusableStringBuilder.Append(LineEnding.NewLineCharacters); } + + return GetBytesFromStringBuilder(_reusableEncodingBuffer, _reusableStringBuilder); + } + finally + { + _reusableStringBuilder.Length = 0; } } } @@ -538,17 +583,6 @@ private byte[] GetBytesFromStringBuilder(char[] charBuffer, StringBuilder string return Encoding.GetBytes(stringBuilder.ToString()); } - private byte[] GetBytesFromString(char[] charBuffer, string layoutMessage) - { - InternalLogger.Trace("{0}: Sending {1}", this, layoutMessage); - if (layoutMessage.Length <= charBuffer.Length) - { - layoutMessage.CopyTo(0, charBuffer, 0, layoutMessage.Length); - return Encoding.GetBytes(charBuffer, 0, layoutMessage.Length); - } - return Encoding.GetBytes(layoutMessage); - } - private byte[] CompressBytesToWrite(byte[] payload) { if (payload.Length > CompressMinBytes) @@ -570,7 +604,7 @@ private byte[] CompressBytesToWrite(byte[] payload) return payload; } - private LinkedListNode GetCachedNetworkSender(string address) + private LinkedListNode GetCachedNetworkSender(string address, LogEventInfo logEventInfo) { lock (_currentSenderCache) { @@ -585,7 +619,7 @@ private LinkedListNode GetCachedNetworkSender(string address) { // make room in the cache by closing the least recently used connection int minAccessTime = int.MaxValue; - LinkedListNode leastRecentlyUsed = null; + LinkedListNode? leastRecentlyUsed = null; foreach (var pair in _currentSenderCache) { @@ -603,7 +637,8 @@ private LinkedListNode GetCachedNetworkSender(string address) } } - NetworkSender sender = CreateNetworkSender(address); + InternalLogger.Debug("{0}: Creating network sender to address: {1}", this, address); + NetworkSender sender = CreateNetworkSender(address, logEventInfo); lock (_openNetworkSenders) { senderNode = _openNetworkSenders.AddLast(sender); @@ -614,9 +649,13 @@ private LinkedListNode GetCachedNetworkSender(string address) } } - private NetworkSender CreateNetworkSender(string address) + private NetworkSender CreateNetworkSender(string address, LogEventInfo logEventInfo) { - var sender = SenderFactory.Create(address, MaxQueueSize, OnQueueOverflow, MaxMessageSize, SslProtocols, TimeSpan.FromSeconds(KeepAliveTimeSeconds), TimeSpan.FromSeconds(SendTimeoutSeconds)); + var sslCertificateFile = SslCertificateFile?.Render(logEventInfo) ?? string.Empty; + var sslCertificatePassword = SslCertificatePassword?.Render(logEventInfo) ?? string.Empty; + var sslCertificateOverride = LoadSslCertificateFromFile(sslCertificateFile, sslCertificatePassword); + + var sender = SenderFactory.Create(address, sslCertificateOverride, this); sender.Initialize(); if (KeepConnection || LogEventDropped != null) { @@ -647,9 +686,92 @@ private void ReleaseCachedConnection(LinkedListNode senderNode) } } - private static void WriteBytesToNetworkSender(NetworkSender sender, byte[] payload, AsyncContinuation continuation) + private static void WriteBytesToNetworkSender(NetworkSender sender, byte[] payload, byte[]? header, AsyncContinuation continuation) { + if (header?.Length > 0) + sender.Send(header, 0, header.Length, continuation); sender.Send(payload, 0, payload.Length, continuation); } + + internal X509Certificate2Collection? LoadSslCertificateFromFile(string sslCertificateFile, string sslCertificatePassword) + { + if (string.IsNullOrEmpty(sslCertificateFile)) + return null; // NOSONAR + + if (_certificateCache != null && _certificateCache.TryGetValue(sslCertificateFile, out var clientCertificates)) + return clientCertificates; // Safe to lookup without lock, since immutable collection + + try + { + lock (_certificateCacheLock) + { + if (_certificateCache?.TryGetValue(sslCertificateFile, out clientCertificates) == true) + return clientCertificates; + + if (string.IsNullOrEmpty(sslCertificateFile)) + { + clientCertificates = new X509Certificate2Collection(); + } + else if (sslCertificateFile.EndsWith(".pem", StringComparison.OrdinalIgnoreCase)) + { + InternalLogger.Debug("{0}: Loading SSL certificate from PEM-file: {1}", this, sslCertificateFile); + var clientCertificate = LoadCertificateFromPem(sslCertificateFile); + clientCertificates = new X509Certificate2Collection(clientCertificate); + } + else + { + InternalLogger.Debug("{0}: Loading SSL certificate from file: {1}", this, sslCertificateFile); + clientCertificates = new X509Certificate2Collection(new X509Certificate2(sslCertificateFile, string.IsNullOrEmpty(sslCertificatePassword) ? null : sslCertificatePassword)); + } + + var certificateCache = new Dictionary((_certificateCache?.Count ?? 0) + 1); + if (_certificateCache != null) + { + foreach (var existingCertificate in _certificateCache) + certificateCache.Add(existingCertificate.Key, existingCertificate.Value); + } + certificateCache[sslCertificateFile] = clientCertificates; + _certificateCache = certificateCache; + return clientCertificates; + } + } + catch (Exception ex) + { + InternalLogger.Error(ex, "{0}: Failed loading SSL certificate from file: {1}", this, sslCertificateFile); + throw new NLogRuntimeException($"NetworkTarget: Failed loading SSL certificate from file: {sslCertificateFile}", ex); + } + } + + private static X509Certificate2 LoadCertificateFromPem(string fileName) + { + using (var reader = new System.IO.StreamReader(new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read), Encoding.UTF8)) + { + var pem = reader.ReadToEnd(); + string base64 = GetBase64FromPem(pem); + byte[] certBytes = Convert.FromBase64String(base64); + return new X509Certificate2(certBytes); + } + } + + private static string GetBase64FromPem(string pem) + { + const string header = "-----BEGIN CERTIFICATE-----"; + const string footer = "-----END CERTIFICATE-----"; + + int start = pem.IndexOf(header, StringComparison.Ordinal) + header.Length; + if (start <= header.Length) + { + throw new NLogRuntimeException("Invalid PEM format: Missing BEGIN CERTIFICATE header"); + } + + int end = pem.IndexOf(footer, start, StringComparison.Ordinal); + if (end <= start) + { + throw new NLogRuntimeException("Invalid PEM format: Missing END CERTIFICATE footer"); + } + string base64 = pem.Substring(start, end - start); + base64 = base64.Replace("\r", "").Replace("\n", "").Trim(); + return base64; + } } } diff --git a/src/NLog/Targets/NetworkTargetCompressionType.cs b/src/NLog.Targets.Network/Targets/NetworkTargetCompressionType.cs similarity index 100% rename from src/NLog/Targets/NetworkTargetCompressionType.cs rename to src/NLog.Targets.Network/Targets/NetworkTargetCompressionType.cs diff --git a/src/NLog/Targets/NetworkTargetConnectionsOverflowAction.cs b/src/NLog.Targets.Network/Targets/NetworkTargetConnectionsOverflowAction.cs similarity index 100% rename from src/NLog/Targets/NetworkTargetConnectionsOverflowAction.cs rename to src/NLog.Targets.Network/Targets/NetworkTargetConnectionsOverflowAction.cs diff --git a/src/NLog/Targets/NetworkTargetOverflowAction.cs b/src/NLog.Targets.Network/Targets/NetworkTargetOverflowAction.cs similarity index 100% rename from src/NLog/Targets/NetworkTargetOverflowAction.cs rename to src/NLog.Targets.Network/Targets/NetworkTargetOverflowAction.cs diff --git a/src/NLog/Targets/NetworkTargetQueueOverflowAction.cs b/src/NLog.Targets.Network/Targets/NetworkTargetQueueOverflowAction.cs similarity index 100% rename from src/NLog/Targets/NetworkTargetQueueOverflowAction.cs rename to src/NLog.Targets.Network/Targets/NetworkTargetQueueOverflowAction.cs diff --git a/src/NLog.Targets.Network/Targets/SplunkTarget.cs b/src/NLog.Targets.Network/Targets/SplunkTarget.cs new file mode 100644 index 0000000000..717ce7e53b --- /dev/null +++ b/src/NLog.Targets.Network/Targets/SplunkTarget.cs @@ -0,0 +1,95 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets +{ + using System.Collections.Generic; + using NLog.Layouts; + + /// + /// Sends log messages to Splunk server using either TCP or UDP with Splunk-JSON-format + /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + [Target("Splunk")] + public class SplunkTarget : NetworkTarget + { + private readonly SplunkLayout _splunkLayout = new SplunkLayout(); + + /// + public Layout SplunkHostName { get => _splunkLayout.SplunkHostName; set => _splunkLayout.SplunkHostName = value; } + + /// + public Layout SplunkSourceName { get => _splunkLayout.SplunkSourceName; set => _splunkLayout.SplunkSourceName = value; } + + /// + public Layout SplunkSourceType { get => _splunkLayout.SplunkSourceType; set => _splunkLayout.SplunkSourceType = value; } + + /// + public Layout SplunkIndex { get => _splunkLayout.SplunkIndex; set => _splunkLayout.SplunkIndex = value; } + + /// + public bool IncludeEventProperties { get => _splunkLayout.IncludeEventProperties; set => _splunkLayout.IncludeEventProperties = value; } + + /// + public bool IncludeScopeProperties { get => _splunkLayout.IncludeScopeProperties; set => _splunkLayout.IncludeScopeProperties = value; } + + /// + public bool ExcludeEmptyProperties { get => _splunkLayout.ExcludeEmptyProperties; set => _splunkLayout.ExcludeEmptyProperties = value; } + + /// +#if NET35 + public HashSet? ExcludeProperties { get => _splunkLayout.ExcludeProperties; set => _splunkLayout.ExcludeProperties = value; } +#else + public ISet? ExcludeProperties { get => _splunkLayout.ExcludeProperties; set => _splunkLayout.ExcludeProperties = value; } +#endif + + /// + public override Layout Layout + { + get => _splunkLayout; + set { /* Fixed SplunkLayout */ } // NOSONAR + } + + /// + /// Initializes a new instance of the class. + /// + public SplunkTarget() + { + LineEnding = LineEndingMode.LF; + Layout = _splunkLayout; + } + } +} diff --git a/src/NLog.Targets.Network/Targets/SyslogTarget.cs b/src/NLog.Targets.Network/Targets/SyslogTarget.cs new file mode 100644 index 0000000000..e654064bfb --- /dev/null +++ b/src/NLog.Targets.Network/Targets/SyslogTarget.cs @@ -0,0 +1,142 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets +{ + using System.Collections.Generic; + using System.Text; + using NLog.Config; + using NLog.Layouts; + + /// + /// Sends log messages to Syslog server using either TCP or UDP with format Rfc3164 or Rfc5424 + /// + /// + /// When using TCP then the default message-delimeter is octet-byte-count prefix, but it can be changed + /// by setting to or + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + [Target("Syslog")] + public class SyslogTarget : NetworkTarget + { + private readonly SyslogLayout _syslogLayout = new SyslogLayout(); + + /// + public bool Rfc3164 { get => _syslogLayout.Rfc3164; set => _syslogLayout.Rfc3164 = value; } + + /// + public bool Rfc5424 { get => _syslogLayout.Rfc5424; set => _syslogLayout.Rfc5424 = value; } + + /// + public Layout SyslogTimestamp { get => _syslogLayout.SyslogTimestamp; set => _syslogLayout.SyslogTimestamp = value; } + + /// + public Layout SyslogHostName { get => _syslogLayout.SyslogHostName; set => _syslogLayout.SyslogHostName = value; } + + /// + public Layout SyslogAppName { get => _syslogLayout.SyslogAppName; set => _syslogLayout.SyslogAppName = value; } + + /// + public Layout SyslogProcessId { get => _syslogLayout.SyslogProcessId; set => _syslogLayout.SyslogProcessId = value; } + + /// + public Layout? SyslogMessageId { get => _syslogLayout.SyslogMessageId; set => _syslogLayout.SyslogMessageId = value; } + + /// + public Layout SyslogMessage { get => _syslogLayout.SyslogMessage; set => _syslogLayout.SyslogMessage = value; } + + /// + public Layout SyslogLevel { get => _syslogLayout.SyslogLevel; set => _syslogLayout.SyslogLevel = value; } + + /// + public SyslogFacility SyslogFacility { get => _syslogLayout.SyslogFacility; set => _syslogLayout.SyslogFacility = value; } + + /// + public Layout StructuredDataId { get => _syslogLayout.StructuredDataId; set => _syslogLayout.StructuredDataId = value; } + + /// + public bool IncludeEventProperties { get => _syslogLayout.IncludeEventProperties; set => _syslogLayout.IncludeEventProperties = value; } + + /// + [ArrayParameter(typeof(TargetPropertyWithContext), "StructuredDataParam")] + public List StructuredDataParams => _syslogLayout.StructuredDataParams; + + /// + /// Initializes a new instance of the class. + /// + public SyslogTarget() + { + LineEnding = LineEndingMode.None; + Layout = _syslogLayout; + } + + /// + protected override byte[]? GetHeaderToWrite(LogEventInfo logEvent, string address, byte[] payload) + { + if (LineEnding?.NewLineCharacters?.Length > 0) + return null; + + if (address?.StartsWith("tcp", System.StringComparison.OrdinalIgnoreCase) == true) + { + var octetCount = payload.Length; + return GenerateOctetHeader(octetCount); + } + + // Skip octet framing for UDP protocols by returning null + return null; + } + + private static byte[] GenerateOctetHeader(int octetCount) + { + if (octetCount < OctetHeaders.Length) + { + var headerBytes = OctetHeaders[octetCount]; + if (headerBytes is null) + OctetHeaders[octetCount] = headerBytes = Encoding.ASCII.GetBytes($"{octetCount} "); + return headerBytes; + } + + return Encoding.ASCII.GetBytes($"{octetCount} "); + } + private static readonly byte[][] OctetHeaders = new byte[4046][]; + + /// + public override Layout Layout + { + get => _syslogLayout; + set { /* Fixed SyslogLayout */ } // NOSONAR + } + } +} diff --git a/src/NLog.Targets.Trace/N.png b/src/NLog.Targets.Trace/N.png new file mode 100644 index 0000000000..4b8869e2e6 Binary files /dev/null and b/src/NLog.Targets.Trace/N.png differ diff --git a/src/NLog.Targets.Trace/NLog.Targets.Trace.csproj b/src/NLog.Targets.Trace/NLog.Targets.Trace.csproj new file mode 100644 index 0000000000..fcd52359b8 --- /dev/null +++ b/src/NLog.Targets.Trace/NLog.Targets.Trace.csproj @@ -0,0 +1,89 @@ + + + + 17.0 + net35;net46;netstandard2.0 + net35;net46;netstandard2.0;netstandard2.1 + + NLog.Targets.Trace + NLog + Trace Target for System.Diagnostics.Trace + NLog.Targets.Trace v$(ProductVersion) + $(ProductVersion) + Jarek Kowalski,Kim Christensen,Julian Verdurmen + $([System.DateTime]::Now.ToString(yyyy)) + Copyright (c) 2004-$(CurrentYear) NLog Project - https://nlog-project.org/ + + +Trace-Target Docs: +https://github.com/NLog/NLog/wiki/Trace-target + +Trace-ActivityId Docs: +https://github.com/NLog/NLog/wiki/Trace-Activity-Id-Layout-Renderer + +NLogTraceListener Docs: +https://github.com/NLog/NLog/wiki/NLog-Trace-Listener-for-System-Diagnostics-Trace + + README.md + NLog;Trace;Diagnostics;logging;log + N.png + https://nlog-project.org/ + BSD-3-Clause + git + https://github.com/NLog/NLog.git + + true + 6.0.0.0 + ..\NLog.snk + true + + true + true + true + enable + 9 + true + true + true + + + + NLog.Targets.Trace for .NET Standard 2.1 + + + + NLog.Targets.Trace for .NET Standard 2.0 + + + + NLog.Targets.Trace for .NET Framework 4.6 + true + + + + NLog.Targets.Trace for .NET Framework 4.5 + true + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NLog/NLogTraceListener.cs b/src/NLog.Targets.Trace/NLogTraceListener.cs similarity index 92% rename from src/NLog/NLogTraceListener.cs rename to src/NLog.Targets.Trace/NLogTraceListener.cs index 3b1fc06eca..68dd942af5 100644 --- a/src/NLog/NLogTraceListener.cs +++ b/src/NLog.Targets.Trace/NLogTraceListener.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog { using System; @@ -42,7 +40,6 @@ namespace NLog using System.Diagnostics; using System.Linq; using System.Text; - using System.Xml; using NLog.Internal; /// @@ -58,7 +55,7 @@ public class NLogTraceListener : TraceListener private LogLevel _defaultLogLevel = LogLevel.Debug; private bool _attributesLoaded; private bool _autoLoggerName; - private LogLevel _forceLogLevel; + private LogLevel? _forceLogLevel; private bool _disableFlush; /// @@ -66,6 +63,7 @@ public class NLogTraceListener : TraceListener /// public NLogTraceListener() { + _logFactory = LogManager.LogFactory; } /// @@ -107,7 +105,7 @@ public LogLevel DefaultLogLevel /// /// Gets or sets the log which should be always used regardless of source level. /// - public LogLevel ForceLogLevel + public LogLevel? ForceLogLevel { get { @@ -144,7 +142,7 @@ public bool DisableFlush /// Gets a value indicating whether the trace listener is thread safe. /// /// - /// true if the trace listener is thread safe; otherwise, false. The default is false. + /// if the trace listener is thread safe; otherwise, false. The default is . public override bool IsThreadSafe => true; /// @@ -207,7 +205,7 @@ private void WriteInternal(string message) if (Filter != null && !Filter.ShouldTrace(null, String.Empty, TraceEventType.Verbose, 0, message, null, null, null)) return; - ProcessLogEventInfo(DefaultLogLevel, null, message, null, null, TraceEventType.Verbose, null); + ProcessLogEventInfo(DefaultLogLevel, string.Empty, message, null, null, TraceEventType.Verbose, null); } private void WriteInternal(object o) @@ -217,11 +215,11 @@ private void WriteInternal(object o) if (o is null || o is IConvertible) { - ProcessLogEventInfo(DefaultLogLevel, null, o?.ToString() ?? string.Empty, null, null, TraceEventType.Verbose, null); + ProcessLogEventInfo(DefaultLogLevel, string.Empty, o?.ToString() ?? string.Empty, null, null, TraceEventType.Verbose, null); } else { - ProcessLogEventInfo(DefaultLogLevel, null, "{0}", new[] { o }, null, TraceEventType.Verbose, null); + ProcessLogEventInfo(DefaultLogLevel, string.Empty, "{0}", new[] { o }, null, TraceEventType.Verbose, null); } } @@ -239,7 +237,7 @@ public override void Close() /// A message to emit. public override void Fail(string message) { - ProcessLogEventInfo(LogLevel.Error, null, message, null, null, TraceEventType.Error, null); + ProcessLogEventInfo(LogLevel.Error, string.Empty, message, null, null, TraceEventType.Error, null); } /// @@ -249,11 +247,11 @@ public override void Fail(string message) /// A detailed message to emit. public override void Fail(string message, string detailMessage) { - ProcessLogEventInfo(LogLevel.Error, null, string.Concat(message, " ", detailMessage), null, null, TraceEventType.Error, null); + ProcessLogEventInfo(LogLevel.Error, string.Empty, string.Concat(message, " ", detailMessage), null, null, TraceEventType.Error, null); } /// - /// Flushes the output (if is not true) buffer with the default timeout of 15 seconds. + /// Flushes the output (if is not ) buffer with the default timeout of 15 seconds. /// public override void Flush() { @@ -314,7 +312,7 @@ public override void TraceData(TraceEventCache eventCache, string source, TraceE } sb.Append('{'); - sb.AppendInvariant(i); + sb.Append(i); sb.Append('}'); } message = sb.ToString(); @@ -438,7 +436,7 @@ private static LogLevel TranslateLogLevel(TraceEventType eventType) /// The event type. /// The related activity id. /// - protected virtual void ProcessLogEventInfo(LogLevel logLevel, string loggerName, [Localizable(false)] string message, object[] arguments, int? eventId, TraceEventType? eventType, Guid? relatedActivityId) + protected virtual void ProcessLogEventInfo(LogLevel logLevel, string loggerName, [Localizable(false)] string message, object?[]? arguments, int? eventId, TraceEventType? eventType, Guid? relatedActivityId) { var globalLogLevel = (LogFactory ?? LogManager.LogFactory).GlobalThreshold; if (logLevel < globalLogLevel) @@ -446,7 +444,7 @@ protected virtual void ProcessLogEventInfo(LogLevel logLevel, string loggerName, return; // We are done } - StackTrace stackTrace = AutoLoggerName ? new StackTrace() : null; + var stackTrace = AutoLoggerName ? new StackTrace() : null; var logger = GetLogger(loggerName, stackTrace, out int userFrameIndex); logLevel = _forceLogLevel ?? logLevel; @@ -473,10 +471,6 @@ protected virtual void ProcessLogEventInfo(LogLevel logLevel, string loggerName, if (arguments?.Length == 1 && arguments[0] is Exception exception) { ev.Exception = exception; - if (message == "{0}") - { - ev.FormatProvider = ExceptionMessageFormatProvider.Instance; - } } ev.Level = _forceLogLevel ?? logLevel; @@ -486,9 +480,11 @@ protected virtual void ProcessLogEventInfo(LogLevel logLevel, string loggerName, ev.Properties.Add("EventID", ResolvedBoxedEventId(eventId.Value)); } - if (stackTrace != null && userFrameIndex >= 0) + if (stackTrace != null) { +#pragma warning disable CS0618 // Type or member is obsolete ev.SetStackTrace(stackTrace, userFrameIndex); +#pragma warning restore CS0618 // Type or member is obsolete } logger.Log(typeof(System.Diagnostics.Trace), ev); @@ -513,7 +509,7 @@ private static object ResolvedBoxedEventId(int eventId) return eventId; } - private Logger GetLogger(string loggerName, StackTrace stackTrace, out int userFrameIndex) + private Logger GetLogger(string loggerName, StackTrace? stackTrace, out int userFrameIndex) { if (string.IsNullOrEmpty(loggerName)) { @@ -563,8 +559,8 @@ private void InitAttributes() foreach (DictionaryEntry de in Attributes) { - var key = (string)de.Key; - var value = (string)de.Value; + var key = de.Key?.ToString()?.Trim() ?? string.Empty; + var value = de.Value?.ToString()?.Trim() ?? string.Empty; switch (key.ToUpperInvariant()) { @@ -577,11 +573,11 @@ private void InitAttributes() break; case "AUTOLOGGERNAME": - AutoLoggerName = XmlConvert.ToBoolean(value); + AutoLoggerName = value.Length == 1 ? value[0] == '1' : bool.Parse(value); break; case "DISABLEFLUSH": - DisableFlush = bool.Parse(value); + DisableFlush = value.Length == 1 ? value[0] == '1' : bool.Parse(value); break; } } @@ -589,5 +585,3 @@ private void InitAttributes() } } } - -#endif diff --git a/src/NLog.Targets.Trace/Properties/AssemblyInfo.cs b/src/NLog.Targets.Trace/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..5abf4e6c0f --- /dev/null +++ b/src/NLog.Targets.Trace/Properties/AssemblyInfo.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] +[assembly: AllowPartiallyTrustedCallers] +#if !NET35 +[assembly: SecurityRules(SecurityRuleSet.Level1)] +#endif diff --git a/src/NLog.Targets.Trace/README.md b/src/NLog.Targets.Trace/README.md new file mode 100644 index 0000000000..533e3eb835 --- /dev/null +++ b/src/NLog.Targets.Trace/README.md @@ -0,0 +1,37 @@ +# NLog Trace Target + +NLog Trace Target for writing to System.Diagnostics.Trace for each logevent. + +If having trouble with output, then check [NLog InternalLogger](https://github.com/NLog/NLog/wiki/Internal-Logging) for clues. See also [Troubleshooting NLog](https://github.com/NLog/NLog/wiki/Logging-Troubleshooting) + +See the [NLog Wiki - Trace Target](https://github.com/NLog/NLog/wiki/Trace-target) for available options and examples. + +# NLog TraceListener + +NLog TraceListener for redirecting System.Diagnostics.Trace into NLog Logger output. + +See the [NLog Wiki - NLogTraceListener](https://github.com/NLog/NLog/wiki/NLog-Trace-Listener-for-System-Diagnostics-Trace) for available options and examples. + +# NLog ActivityId LayoutRenderer + +NLog LayoutRenderer `${activityid}` renders Guid from System.Diagnostics.Trace.CorrelationManager.ActivityId + +See the [NLog Wiki - ActivityId](https://github.com/NLog/NLog/wiki/Trace-Activity-Id-Layout-Renderer) for available options and examples. + +## Register Extension + +NLog will only recognize type-alias `Trace` when loading from `NLog.config`-file, if having added extension to `NLog.config`-file: + +```xml + + + +``` + +Alternative register from code using [fluent configuration API](https://github.com/NLog/NLog/wiki/Fluent-Configuration-API): + +```csharp +LogManager.Setup().SetupExtensions(ext => { + ext.RegisterTarget(); +}); +``` diff --git a/src/NLog.Targets.Trace/StackTraceUsageUtils.cs b/src/NLog.Targets.Trace/StackTraceUsageUtils.cs new file mode 100644 index 0000000000..8482dd7c01 --- /dev/null +++ b/src/NLog.Targets.Trace/StackTraceUsageUtils.cs @@ -0,0 +1,165 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Internal +{ + using System; + using System.Diagnostics; + using System.Reflection; + using NLog.Config; + + /// + /// Utilities for dealing with values. + /// + internal static class StackTraceUsageUtils + { + private static readonly Assembly nlogAssembly = typeof(StackTraceUsageUtils).Assembly; + private static readonly Assembly mscorlibAssembly = typeof(string).Assembly; + private static readonly Assembly systemAssembly = typeof(Debug).Assembly; + + /// + /// Returns the classname from the provided StackFrame (If not from internal assembly) + /// + /// + /// Valid class name, or empty string if assembly was internal + public static string LookupClassNameFromStackFrame(StackFrame stackFrame) + { + var method = stackFrame?.GetMethod(); + if (method != null && LookupAssemblyFromMethod(method) != null) + { + string className = GetStackFrameMethodClassName(method, true, true, true); + if (!string.IsNullOrEmpty(className)) + { + if (!className.StartsWith("System.", StringComparison.Ordinal)) + return className; + } + else + { + className = method.Name ?? string.Empty; + if (className != "lambda_method" && className != "MoveNext") + return className; + } + } + + return string.Empty; + } + + private static string GetStackFrameMethodClassName(MethodBase method, bool includeNameSpace, bool cleanAsyncMoveNext, bool cleanAnonymousDelegates) + { + if (method is null) + return string.Empty; + + var callerClassType = method.DeclaringType; + if (cleanAsyncMoveNext + && method.Name == "MoveNext" + && callerClassType?.DeclaringType != null + && callerClassType.Name?.IndexOf('<') == 0 + && callerClassType.Name.IndexOf('>', 1) > 1) + { + // NLog.UnitTests.LayoutRenderers.CallSiteTests+d_3'1 + callerClassType = callerClassType.DeclaringType; + } + + if (callerClassType is null) + return string.Empty; + + var className = includeNameSpace ? callerClassType.FullName : callerClassType.Name; + if (cleanAnonymousDelegates && className?.IndexOf("<>", StringComparison.Ordinal) >= 0) + { + if (!includeNameSpace && callerClassType.DeclaringType != null && callerClassType.IsNested) + { + className = callerClassType.DeclaringType.Name; + } + else + { + // NLog.UnitTests.LayoutRenderers.CallSiteTests+<>c__DisplayClassa + int index = className.IndexOf("+<>", StringComparison.Ordinal); + if (index >= 0) + { + className = className.Substring(0, index); + } + } + } + + if (includeNameSpace && className?.IndexOf('.') == -1) + { + var typeNamespace = GetNamespaceFromTypeAssembly(callerClassType); + className = string.IsNullOrEmpty(typeNamespace) ? className : string.Concat(typeNamespace, ".", className); + } + + return className ?? string.Empty; + } + + private static string GetNamespaceFromTypeAssembly(Type callerClassType) + { + var classAssembly = callerClassType.Assembly; + if (classAssembly != null && classAssembly != mscorlibAssembly && classAssembly != systemAssembly) + { + var assemblyFullName = classAssembly.FullName; + if (assemblyFullName?.IndexOf(',') >= 0 && !assemblyFullName.StartsWith("System.", StringComparison.Ordinal) && !assemblyFullName.StartsWith("Microsoft.", StringComparison.Ordinal)) + { + return assemblyFullName.Substring(0, assemblyFullName.IndexOf(',')); + } + } + + return string.Empty; + } + + /// + /// Returns the assembly from the provided StackFrame (If not internal assembly) + /// + /// Valid assembly, or null if assembly was internal + private static Assembly? LookupAssemblyFromMethod(MethodBase method) + { + var assembly = method?.DeclaringType?.Assembly ?? method?.Module?.Assembly; + + // skip stack frame if the method declaring type assembly is from hidden assemblies list + if (assembly == nlogAssembly) + { + return null; + } + + if (assembly == mscorlibAssembly) + { + return null; + } + + if (assembly == systemAssembly) + { + return null; + } + + return assembly; + } + } +} diff --git a/src/NLog/LayoutRenderers/TraceActivityIdLayoutRenderer.cs b/src/NLog.Targets.Trace/TraceActivityIdLayoutRenderer.cs similarity index 91% rename from src/NLog/LayoutRenderers/TraceActivityIdLayoutRenderer.cs rename to src/NLog.Targets.Trace/TraceActivityIdLayoutRenderer.cs index cd7546b29a..b3725407de 100644 --- a/src/NLog/LayoutRenderers/TraceActivityIdLayoutRenderer.cs +++ b/src/NLog.Targets.Trace/TraceActivityIdLayoutRenderer.cs @@ -31,15 +31,12 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.LayoutRenderers { using System; using System.Diagnostics; using System.Globalization; using System.Text; - using NLog.Internal; /// /// A renderer that puts into log a System.Diagnostics trace correlation id. @@ -49,7 +46,7 @@ namespace NLog.LayoutRenderers /// /// Documentation on NLog Wiki [LayoutRenderer("activityid")] - public class TraceActivityIdLayoutRenderer : LayoutRenderer, IStringValueRenderer + public class TraceActivityIdLayoutRenderer : LayoutRenderer { /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) @@ -57,8 +54,6 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) builder.Append(GetStringValue()); } - string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue(); - private static string GetStringValue() { var activityId = GetValue(); @@ -75,5 +70,3 @@ private static Guid GetValue() } } } - -#endif diff --git a/src/NLog/Targets/TraceTarget.cs b/src/NLog.Targets.Trace/TraceTarget.cs similarity index 88% rename from src/NLog/Targets/TraceTarget.cs rename to src/NLog.Targets.Trace/TraceTarget.cs index 5eb0a00d5e..4d12ab45ee 100644 --- a/src/NLog/Targets/TraceTarget.cs +++ b/src/NLog.Targets.Trace/TraceTarget.cs @@ -33,8 +33,6 @@ #define TRACE -#if !NETSTANDARD1_3 - namespace NLog.Targets { using System.Diagnostics; @@ -82,7 +80,7 @@ public sealed class TraceTarget : TargetWithLayoutHeaderAndFooter /// /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} /// - public TraceTarget() : base() + public TraceTarget() { } @@ -105,7 +103,8 @@ protected override void InitializeTarget() if (Header != null) { - Trace.WriteLine(RenderLogEvent(Footer, LogEventInfo.CreateNullEvent())); + string logMessage = RenderLogEvent(Header, LogEventInfo.CreateNullEvent()); + TraceWrite(LogLevel.Debug, logMessage); } } @@ -114,7 +113,8 @@ protected override void CloseTarget() { if (Footer != null) { - Trace.WriteLine(RenderLogEvent(Footer, LogEventInfo.CreateNullEvent())); + string logMessage = RenderLogEvent(Footer, LogEventInfo.CreateNullEvent()); + TraceWrite(LogLevel.Debug, logMessage); } base.CloseTarget(); @@ -136,23 +136,28 @@ protected override void CloseTarget() protected override void Write(LogEventInfo logEvent) { string logMessage = RenderLogEvent(Layout, logEvent); - if (RawWrite || logEvent.Level <= LogLevel.Debug) + TraceWrite(logEvent.Level, logMessage); + } + + private void TraceWrite(LogLevel logLevel, string logMessage) + { + if (RawWrite || logLevel <= LogLevel.Debug) { - Trace.WriteLine(logMessage); + Trace.WriteLine(logMessage); // NOSONAR } - else if (logEvent.Level == LogLevel.Info) + else if (logLevel == LogLevel.Info) { Trace.TraceInformation(logMessage); } - else if (logEvent.Level == LogLevel.Warn) + else if (logLevel == LogLevel.Warn) { Trace.TraceWarning(logMessage); } - else if (logEvent.Level == LogLevel.Error) + else if (logLevel == LogLevel.Error) { Trace.TraceError(logMessage); } - else if (logEvent.Level >= LogLevel.Fatal) + else if (logLevel >= LogLevel.Fatal) { if (EnableTraceFail) Trace.Fail(logMessage); // Can throw exceptions, show message dialog or perform Environment.FailFast @@ -161,10 +166,8 @@ protected override void Write(LogEventInfo logEvent) } else { - Trace.WriteLine(logMessage); + Trace.WriteLine(logMessage); // NOSONAR } } } } - -#endif diff --git a/src/NLog.Targets.WebService/N.png b/src/NLog.Targets.WebService/N.png new file mode 100644 index 0000000000..4b8869e2e6 Binary files /dev/null and b/src/NLog.Targets.WebService/N.png differ diff --git a/src/NLog.Targets.WebService/NLog.Targets.WebService.csproj b/src/NLog.Targets.WebService/NLog.Targets.WebService.csproj new file mode 100644 index 0000000000..3b43b1363d --- /dev/null +++ b/src/NLog.Targets.WebService/NLog.Targets.WebService.csproj @@ -0,0 +1,84 @@ + + + + 17.0 + net46;netstandard2.0 + net46;netstandard2.0;netstandard2.1 + + NLog.Targets.WebService + NLog + WebServiceTarget for calling HTTP / SOAP / REST web-service methods + NLog.Targets.WebService v$(ProductVersion) + $(ProductVersion) + Jarek Kowalski,Kim Christensen,Julian Verdurmen + $([System.DateTime]::Now.ToString(yyyy)) + Copyright (c) 2004-$(CurrentYear) NLog Project - https://nlog-project.org/ + + + WebServiceTarget Docs: + https://github.com/NLog/NLog/wiki/WebService-target + + README.md + NLog;WebService;Soap;Rest;HttpClient;Network;logging;log + N.png + https://nlog-project.org/ + BSD-3-Clause + git + https://github.com/NLog/NLog.git + + true + 6.0.0.0 + ..\NLog.snk + true + + true + true + true + enable + 9 + true + true + true + + + + NLog.Targets.WebService for .NET Standard 2.1 + + + + NLog.Targets.WebService for .NET Standard 2.0 + + + + NLog.Targets.WebService for .NET Framework 4.6 + true + + + + NLog.Targets.WebService for .NET Framework 4.5 + true + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NLog.Targets.WebService/Properties/AssemblyInfo.cs b/src/NLog.Targets.WebService/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3e030e53e5 --- /dev/null +++ b/src/NLog.Targets.WebService/Properties/AssemblyInfo.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] +[assembly: InternalsVisibleTo("NLog.Targets.WebService.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ef8eab4fbdeb511eeb475e1659fe53f00ec1c1340700f1aa347bf3438455d71993b28b1efbed44c8d97a989e0cb6f01bcb5e78f0b055d311546f63de0a969e04cf04450f43834db9f909e566545a67e42822036860075a1576e90e1c43d43e023a24c22a427f85592ae56cac26f13b7ec2625cbc01f9490d60f16cfbb1bc34d9")] +[assembly: AllowPartiallyTrustedCallers] +#if !NET35 +[assembly: SecurityRules(SecurityRuleSet.Level1)] +#endif diff --git a/src/NLog.Targets.WebService/README.md b/src/NLog.Targets.WebService/README.md new file mode 100644 index 0000000000..d7d8450f51 --- /dev/null +++ b/src/NLog.Targets.WebService/README.md @@ -0,0 +1,25 @@ +# NLog WebService Target + +NLog WebService Target for calling web-service for each logevent, with support for different protocols: JsonPost, XmlPost, HttpGet, HttpPost, Soap11, Soap12 + +If having trouble with output, then check [NLog InternalLogger](https://github.com/NLog/NLog/wiki/Internal-Logging) for clues. See also [Troubleshooting NLog](https://github.com/NLog/NLog/wiki/Logging-Troubleshooting) + +See the [NLog Wiki](https://github.com/NLog/NLog/wiki/WebService-target) for available options and examples. + +## Register Extension + +NLog will only recognize type-alias `WebService` when loading from `NLog.config`-file, if having added extension to `NLog.config`-file: + +```xml + + + +``` + +Alternative register from code using [fluent configuration API](https://github.com/NLog/NLog/wiki/Fluent-Configuration-API): + +```csharp +LogManager.Setup().SetupExtensions(ext => { + ext.RegisterTarget(); +}); +``` diff --git a/src/NLog/Targets/WebServiceProtocol.cs b/src/NLog.Targets.WebService/WebServiceProtocol.cs similarity index 100% rename from src/NLog/Targets/WebServiceProtocol.cs rename to src/NLog.Targets.WebService/WebServiceProtocol.cs diff --git a/src/NLog/Targets/WebServiceProxyType.cs b/src/NLog.Targets.WebService/WebServiceProxyType.cs similarity index 100% rename from src/NLog/Targets/WebServiceProxyType.cs rename to src/NLog.Targets.WebService/WebServiceProxyType.cs diff --git a/src/NLog/Targets/WebServiceTarget.cs b/src/NLog.Targets.WebService/WebServiceTarget.cs similarity index 78% rename from src/NLog/Targets/WebServiceTarget.cs rename to src/NLog.Targets.WebService/WebServiceTarget.cs index 4580ecce28..f367bc1139 100644 --- a/src/NLog/Targets/WebServiceTarget.cs +++ b/src/NLog.Targets.WebService/WebServiceTarget.cs @@ -42,7 +42,6 @@ namespace NLog.Targets using System.Xml; using NLog.Common; using NLog.Config; - using NLog.Internal; using NLog.Layouts; /// @@ -100,11 +99,9 @@ public WebServiceTarget() Encoding = new UTF8Encoding(writeBOM); IncludeBOM = writeBOM; -#if NETSTANDARD1_3 || NETSTANDARD1_5 // NetCore1 throws PlatformNotSupportedException on WebRequest.GetSystemWebProxy, when using DefaultWebProxy // Net5 (or newer) will turn off Http-connection-pooling if not using DefaultWebProxy ProxyType = WebServiceProxyType.NoProxy; -#endif } /// @@ -120,26 +117,25 @@ public WebServiceTarget(string name) : this() /// Gets or sets the web service URL. /// /// - [RequiredParameter] - public Layout Url { get; set; } + public Layout Url { get; set; } = new Layout(default(Uri)); /// /// Gets or sets the value of the User-agent HTTP header. /// /// - public Layout UserAgent { get; set; } + public Layout? UserAgent { get; set; } /// /// Gets or sets the Web service method name. Only used with Soap. /// /// - public string MethodName { get; set; } + public string MethodName { get; set; } = string.Empty; /// /// Gets or sets the Web service namespace. Only used with Soap. /// /// - public string Namespace { get; set; } + public string Namespace { get; set; } = string.Empty; /// /// Gets or sets the protocol to be used when calling web service. @@ -148,9 +144,9 @@ public WebServiceTarget(string name) : this() public WebServiceProtocol Protocol { get => _activeProtocol.Key; - set => _activeProtocol = new KeyValuePair(value, null); + set => _activeProtocol = new KeyValuePair(value, null); } - private KeyValuePair _activeProtocol = new KeyValuePair(WebServiceProtocol.Soap11, null); + private KeyValuePair _activeProtocol = new KeyValuePair(WebServiceProtocol.Soap11, null); /// /// Gets or sets the proxy configuration when calling web service @@ -162,15 +158,15 @@ public WebServiceProtocol Protocol public WebServiceProxyType ProxyType { get => _activeProxy.Key; - set => _activeProxy = new KeyValuePair(value, null); + set => _activeProxy = new KeyValuePair(value, null); } - private KeyValuePair _activeProxy = new KeyValuePair(WebServiceProxyType.DefaultWebProxy, null); + private KeyValuePair _activeProxy = new KeyValuePair(WebServiceProxyType.DefaultWebProxy, null); /// /// Gets or sets the custom proxy address, include port separated by a colon /// /// - public Layout ProxyAddress { get; set; } + public Layout? ProxyAddress { get; set; } /// /// Should we include the BOM (Byte-order-mark) for UTF? Influences the property. @@ -189,16 +185,17 @@ public WebServiceProxyType ProxyType /// /// Gets or sets a value whether escaping be done according to Rfc3986 (Supports Internationalized Resource Identifiers - IRIs) /// - /// A value of true if Rfc3986; otherwise, false for legacy Rfc2396. + /// A value of if Rfc3986; otherwise, for legacy Rfc2396. /// + [Obsolete("Replaced by WebUtility.UrlEncode. Marked obsolete with NLog 6.0")] public bool EscapeDataRfc3986 { get; set; } /// /// Gets or sets a value whether escaping be done according to the old NLog style (Very non-standard) /// - /// A value of true if legacy encoding; otherwise, false for standard UTF8 encoding. + /// A value of if legacy encoding; otherwise, for standard UTF8 encoding. /// - [Obsolete("Instead use default Rfc2396 or EscapeDataRfc3986. Marked obsolete with NLog v5.3")] + [Obsolete("Replaced by WebUtility.UrlEncode. Marked obsolete with NLog 6.0")] [EditorBrowsable(EditorBrowsableState.Never)] public bool EscapeDataNLogLegacy { get; set; } @@ -209,7 +206,7 @@ public WebServiceProxyType ProxyType /// (see and ). /// /// - public string XmlRoot { get; set; } + public string XmlRoot { get; set; } = string.Empty; /// /// Gets or sets the (optional) root namespace of the XML document, @@ -217,7 +214,7 @@ public WebServiceProxyType ProxyType /// (see and ). /// /// - public string XmlRootNamespace { get; set; } + public string XmlRootNamespace { get; set; } = string.Empty; /// /// Gets the array of parameters to be passed. @@ -232,28 +229,20 @@ public WebServiceProxyType ProxyType /// public bool PreAuthenticate { get; set; } - private readonly AsyncOperationCounter _pendingManualFlushList = new AsyncOperationCounter(); + private IJsonConverter JsonConverter => _jsonConverter ?? (_jsonConverter = ResolveService()); + private IJsonConverter? _jsonConverter; + + private long _pendingWriteOperations; + private Action? _pendingFlushOperation; /// /// Calls the target method. Must be implemented in concrete classes. /// /// Method call parameters. - protected override void DoInvoke(object[] parameters) + protected override void DoInvoke(object?[] parameters) { // method is not used, instead asynchronous overload will be used - throw new NotImplementedException(); - } - - /// - /// Calls the target DoInvoke method, and handles AsyncContinuation callback - /// - /// Method call parameters. - /// The continuation. - protected override void DoInvoke(object[] parameters, AsyncContinuation continuation) - { - var url = BuildWebServiceUrl(LogEventInfo.CreateNullEvent(), parameters); - var webRequest = CreateHttpWebRequest(url); - DoInvoke(parameters, webRequest, continuation); + throw new NotSupportedException(); } /// @@ -261,10 +250,10 @@ protected override void DoInvoke(object[] parameters, AsyncContinuation continua /// /// Parameters to be passed. /// The logging event. - protected override void DoInvoke(object[] parameters, AsyncLogEventInfo logEvent) + protected override void DoInvoke(object?[] parameters, AsyncLogEventInfo logEvent) { - Uri url = null; - HttpWebRequest webRequest = null; + Uri? url = null; + HttpWebRequest? webRequest = null; try { @@ -290,13 +279,11 @@ protected override void DoInvoke(object[] parameters, AsyncLogEventInfo logEvent } } -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 var userAgent = RenderLogEvent(UserAgent, logEvent.LogEvent); if (!string.IsNullOrEmpty(userAgent)) { webRequest.UserAgent = userAgent; } -#endif } catch (Exception ex) { @@ -315,13 +302,12 @@ private HttpWebRequest CreateHttpWebRequest(Uri url) { case WebServiceProxyType.DefaultWebProxy: break; -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 case WebServiceProxyType.AutoProxy: if (_activeProxy.Value is null) { IWebProxy proxy = WebRequest.GetSystemWebProxy(); proxy.Credentials = CredentialCache.DefaultCredentials; - _activeProxy = new KeyValuePair(ProxyType, proxy); + _activeProxy = new KeyValuePair(ProxyType, proxy); } webRequest.Proxy = _activeProxy.Value; break; @@ -331,28 +317,25 @@ private HttpWebRequest CreateHttpWebRequest(Uri url) if (_activeProxy.Value is null) { IWebProxy proxy = new WebProxy(RenderLogEvent(ProxyAddress, LogEventInfo.CreateNullEvent()), true); - _activeProxy = new KeyValuePair(ProxyType, proxy); + _activeProxy = new KeyValuePair(ProxyType, proxy); } webRequest.Proxy = _activeProxy.Value; } break; -#endif default: webRequest.Proxy = null; break; } -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 if (PreAuthenticate || ProxyType == WebServiceProxyType.AutoProxy) { webRequest.PreAuthenticate = true; } -#endif return webRequest; } - private void DoInvoke(object[] parameters, HttpWebRequest webRequest, AsyncContinuation continuation) + private void DoInvoke(object?[] parameters, HttpWebRequest webRequest, AsyncContinuation continuation) { Func beginGetRequest = (request, result) => request.BeginGetRequestStream(result, null); Func getRequestStream = (request, result) => request.EndGetRequestStream(result); @@ -360,10 +343,10 @@ private void DoInvoke(object[] parameters, HttpWebRequest webRequest, AsyncConti DoInvoke(parameters, continuation, webRequest, beginGetRequest, getRequestStream); } - internal void DoInvoke(object[] parameters, AsyncContinuation continuation, HttpWebRequest webRequest, Func beginGetRequest, + internal void DoInvoke(object?[] parameters, AsyncContinuation continuation, HttpWebRequest webRequest, Func beginGetRequest, Func getRequestStream) { - MemoryStream postPayload = null; + MemoryStream? postPayload = null; if (Protocol == WebServiceProtocol.HttpGet) { @@ -371,12 +354,16 @@ internal void DoInvoke(object[] parameters, AsyncContinuation continuation, Http } else { - if (_activeProtocol.Value is null) - _activeProtocol = new KeyValuePair(Protocol, _postFormatterFactories[Protocol](this)); - postPayload = _activeProtocol.Value.PrepareRequest(webRequest, parameters); + var activeProtocol = _activeProtocol.Value; + if (activeProtocol is null) + { + activeProtocol = _postFormatterFactories[Protocol](this); + _activeProtocol = new KeyValuePair(Protocol, activeProtocol); + } + postPayload = activeProtocol.PrepareRequest(webRequest, parameters); } - _pendingManualFlushList.BeginOperation(); + System.Threading.Interlocked.Increment(ref _pendingWriteOperations); try { @@ -392,7 +379,7 @@ internal void DoInvoke(object[] parameters, AsyncContinuation continuation, Http catch (Exception ex) { InternalLogger.Error(ex, "{0}: Error starting request for url={1}", this, webRequest.RequestUri); - if (ExceptionMustBeRethrown(ex)) + if (LogManager.ThrowExceptions) { throw; } @@ -418,7 +405,7 @@ private void WaitForReponse(AsyncContinuation continuation, HttpWebRequest webRe catch (Exception ex) { #if DEBUG - if (ex.MustBeRethrownImmediately()) + if (LogManager.ThrowExceptions) { throw; // Throwing exceptions here will crash the entire application (.NET 2.0 behavior) } @@ -449,7 +436,7 @@ private void PostPayload(AsyncContinuation continuation, HttpWebRequest webReque catch (Exception ex) { #if DEBUG - if (ex.MustBeRethrownImmediately()) + if (LogManager.ThrowExceptions) { throw; // Throwing exceptions here will crash the entire application (.NET 2.0 behavior) } @@ -461,29 +448,60 @@ private void PostPayload(AsyncContinuation continuation, HttpWebRequest webReque }); } - private void DoInvokeCompleted(AsyncContinuation continuation, Exception ex) + private void DoInvokeCompleted(AsyncContinuation continuation, Exception? ex) { - _pendingManualFlushList.CompleteOperation(ex); + System.Threading.Interlocked.Decrement(ref _pendingWriteOperations); + _pendingFlushOperation?.Invoke(); continuation(ex); } + /// + protected override void InitializeTarget() + { + base.InitializeTarget(); + + if (Url is null || (Url.IsFixed && Url.FixedValue is null)) + throw new NLogConfigurationException("WebServiceTarget Url-property must be assigned. WebRequest requires Url-address."); + } + /// protected override void FlushAsync(AsyncContinuation asyncContinuation) { - _pendingManualFlushList.RegisterCompletionNotification(asyncContinuation).Invoke(null); + var pendingWriteOperations = System.Threading.Interlocked.Read(ref _pendingWriteOperations); + if (pendingWriteOperations <= 0) + { + asyncContinuation?.Invoke(null); + } + else + { + var pendingFlushOperation = _pendingFlushOperation; + Action? newPendingFlushOperation = null; + newPendingFlushOperation = () => + { + pendingFlushOperation?.Invoke(); + if (System.Threading.Interlocked.Decrement(ref pendingWriteOperations) == 0) + { + System.Threading.Interlocked.CompareExchange(ref _pendingFlushOperation, null, newPendingFlushOperation); + asyncContinuation.Invoke(null); + } + }; + + _pendingFlushOperation = newPendingFlushOperation; + } } /// protected override void CloseTarget() { - _pendingManualFlushList.Clear(); // Maybe consider to wait a short while if pending requests? + _pendingFlushOperation = null; // Maybe consider to wait a short while if pending requests? + _pendingWriteOperations = 0; base.CloseTarget(); } /// /// Builds the URL to use when calling the web service for a message, depending on the WebServiceProtocol. /// - private Uri BuildWebServiceUrl(LogEventInfo logEvent, object[] parameterValues) + private Uri? BuildWebServiceUrl(LogEventInfo logEvent, object?[] parameterValues) { var uri = RenderLogEvent(Url, logEvent); if (Protocol != WebServiceProtocol.HttpGet) @@ -491,19 +509,10 @@ private Uri BuildWebServiceUrl(LogEventInfo logEvent, object[] parameterValues) return uri; } -#pragma warning disable CS0618 // Type or member is obsolete - bool escapeDataNLogLegacy = EscapeDataNLogLegacy; -#pragma warning restore CS0618 // Type or member is obsolete - //if the protocol is HttpGet, we need to add the parameters to the query string of the url - string queryParameters; - using (var targetBuilder = ReusableLayoutBuilder.Allocate()) - { - StringBuilder sb = targetBuilder.Result ?? new StringBuilder(); - UrlHelper.EscapeEncodingOptions encodingOptions = UrlHelper.GetUriStringEncodingFlags(escapeDataNLogLegacy, false, EscapeDataRfc3986); - BuildWebServiceQueryParameters(parameterValues, sb, encodingOptions); - queryParameters = sb.ToString(); - } + StringBuilder sb = new StringBuilder(); + BuildWebServiceQueryParameters(parameterValues, sb); + var queryParameters = sb.ToString(); var builder = new UriBuilder(uri); //append our query string to the URL following @@ -520,21 +529,43 @@ private Uri BuildWebServiceUrl(LogEventInfo logEvent, object[] parameterValues) return builder.Uri; } - private void BuildWebServiceQueryParameters(object[] parameterValues, StringBuilder sb, UrlHelper.EscapeEncodingOptions encodingOptions) + private void BuildWebServiceQueryParameters(object?[] parameterValues, StringBuilder sb) { string separator = string.Empty; for (int i = 0; i < Parameters.Count; i++) { sb.Append(separator); sb.Append(Parameters[i].Name); - sb.Append('='); - string parameterValue = XmlHelper.XmlConvertToString(parameterValues[i]); - if (!string.IsNullOrEmpty(parameterValue)) + sb.Append("="); + AppendParameterAsString(parameterValues[i], sb); + separator = "&"; + } + } + + private void AppendParameterAsString(object? parameterValue, StringBuilder sb) + { + var parameterObject = parameterValue as IConvertible; + if (parameterObject != null) + { + switch (parameterObject.GetTypeCode()) { - UrlHelper.EscapeDataEncode(parameterValue, sb, encodingOptions); + case TypeCode.DateTime: + sb.Append(XmlConvert.ToString(parameterObject.ToDateTime(System.Globalization.CultureInfo.InvariantCulture), XmlDateTimeSerializationMode.RoundtripKind)); + return; + case TypeCode.Object: + break; + case TypeCode.String: + break; + case TypeCode.Char: + break; + default: + JsonConverter.SerializeObject(parameterValue, sb); + return; } - separator = "&"; } + + var parameterString = Convert.ToString(parameterValue, System.Globalization.CultureInfo.InvariantCulture); + sb.Append(WebUtility.UrlEncode(parameterString)); } /// @@ -552,10 +583,10 @@ private static void WriteStreamAndFixPreamble(MemoryStream postPayload, Stream o var hasBomInEncoding = encoding.GetPreamble().Length == preambleSize; //BOM already in Encoding. - nothingToDo = writeUtf8BOM.Value && hasBomInEncoding; + nothingToDo = writeUtf8BOM == true && hasBomInEncoding; //Bom already not in Encoding - nothingToDo = nothingToDo || !writeUtf8BOM.Value && !hasBomInEncoding; + nothingToDo = nothingToDo || (writeUtf8BOM != true && !hasBomInEncoding); } var byteArray = postPayload.GetBuffer(); @@ -577,7 +608,7 @@ protected HttpPostFormatterBase(WebServiceTarget target) } protected string ContentType => _contentType ?? (_contentType = GetContentType(Target)); - private string _contentType; + private string? _contentType; protected WebServiceTarget Target { get; } @@ -586,7 +617,7 @@ protected virtual string GetContentType(WebServiceTarget target) return string.Concat("charset=", target.Encoding.WebName); } - public MemoryStream PrepareRequest(HttpWebRequest request, object[] parameterValues) + public MemoryStream PrepareRequest(HttpWebRequest request, object?[] parameterValues) { InitRequest(request); @@ -601,19 +632,13 @@ protected virtual void InitRequest(HttpWebRequest request) request.ContentType = ContentType; } - protected abstract void WriteContent(MemoryStream ms, object[] parameterValues); + protected abstract void WriteContent(MemoryStream ms, object?[] parameterValues); } private sealed class HttpPostFormEncodedFormatter : HttpPostTextFormatterBase { - readonly UrlHelper.EscapeEncodingOptions _encodingOptions; - public HttpPostFormEncodedFormatter(WebServiceTarget target) : base(target) { -#pragma warning disable CS0618 // Type or member is obsolete - bool escapeDataNLogLegacy = target.EscapeDataNLogLegacy; -#pragma warning restore CS0618 // Type or member is obsolete - _encodingOptions = UrlHelper.GetUriStringEncodingFlags(escapeDataNLogLegacy, true, target.EscapeDataRfc3986); } protected override string GetContentType(WebServiceTarget target) @@ -621,9 +646,9 @@ protected override string GetContentType(WebServiceTarget target) return string.Concat("application/x-www-form-urlencoded", "; ", base.GetContentType(target)); } - protected override void WriteStringContent(StringBuilder builder, object[] parameterValues) + protected override void WriteStringContent(StringBuilder builder, object?[] parameterValues) { - Target.BuildWebServiceQueryParameters(parameterValues, builder, _encodingOptions); + Target.BuildWebServiceQueryParameters(parameterValues, builder); } } @@ -642,7 +667,7 @@ protected override string GetContentType(WebServiceTarget target) return string.Concat("application/json", "; ", base.GetContentType(target)); } - protected override void WriteStringContent(StringBuilder builder, object[] parameterValues) + protected override void WriteStringContent(StringBuilder builder, object?[] parameterValues) { if (Target.Parameters.Count == 1 && string.IsNullOrEmpty(Target.Parameters[0].Name) && parameterValues[0] is string s) { @@ -735,7 +760,7 @@ protected HttpPostSoapFormatterBase(WebServiceTarget target) : base(target) protected abstract string SoapEnvelopeNamespace { get; } protected abstract string SoapName { get; } - protected override void WriteContent(MemoryStream ms, object[] parameterValues) + protected override void WriteContent(MemoryStream ms, object?[] parameterValues) { using (var xtw = XmlWriter.Create(ms, _xmlWriterSettings)) { @@ -762,8 +787,8 @@ protected static string GetDefaultSoapAction(WebServiceTarget target) private abstract class HttpPostTextFormatterBase : HttpPostFormatterBase { - readonly ReusableBuilderCreator _reusableStringBuilder = new ReusableBuilderCreator(); - readonly ReusableBufferCreator _reusableEncodingBuffer = new ReusableBufferCreator(1024); + readonly StringBuilder _reusableStringBuilder = new StringBuilder(); + readonly char[] _reusableEncodingBuffer = new char[1024]; readonly byte[] _encodingPreamble; protected HttpPostTextFormatterBase(WebServiceTarget target) : base(target) @@ -771,25 +796,42 @@ protected HttpPostTextFormatterBase(WebServiceTarget target) : base(target) _encodingPreamble = target.Encoding.GetPreamble(); } - protected override void WriteContent(MemoryStream ms, object[] parameterValues) + protected override void WriteContent(MemoryStream ms, object?[] parameterValues) { lock (_reusableStringBuilder) { - using (var targetBuilder = _reusableStringBuilder.Allocate()) + try { - WriteStringContent(targetBuilder.Result, parameterValues); + _reusableStringBuilder.Length = 0; + + WriteStringContent(_reusableStringBuilder, parameterValues); - using (var transformBuffer = _reusableEncodingBuffer.Allocate()) + if (_encodingPreamble.Length > 0) + ms.Write(_encodingPreamble, 0, _encodingPreamble.Length); + + int charCount; + int byteCount = Target.Encoding.GetMaxByteCount(_reusableStringBuilder.Length); + ms.SetLength(ms.Position + byteCount); + for (int i = 0; i < _reusableStringBuilder.Length; i += _reusableEncodingBuffer.Length) + { + charCount = Math.Min(_reusableStringBuilder.Length - i, _reusableEncodingBuffer.Length); + _reusableStringBuilder.CopyTo(i, _reusableEncodingBuffer, 0, charCount); + byteCount = Target.Encoding.GetBytes(_reusableEncodingBuffer, 0, charCount, ms.GetBuffer(), (int)ms.Position); + ms.Position += byteCount; + } + if (ms.Position != ms.Length) { - if (_encodingPreamble.Length > 0) - ms.Write(_encodingPreamble, 0, _encodingPreamble.Length); - targetBuilder.Result.CopyToStream(ms, Target.Encoding, transformBuffer.Result); + ms.SetLength(ms.Position); } } + finally + { + _reusableStringBuilder.Length = 0; + } } } - protected abstract void WriteStringContent(StringBuilder builder, object[] parameterValues); + protected abstract void WriteStringContent(StringBuilder builder, object?[] parameterValues); } private sealed class HttpPostXmlDocumentFormatter : HttpPostXmlFormatterBase @@ -809,7 +851,7 @@ protected override string GetContentType(WebServiceTarget target) return string.Concat("application/xml", "; ", base.GetContentType(target)); } - protected override void WriteContent(MemoryStream ms, object[] parameterValues) + protected override void WriteContent(MemoryStream ms, object?[] parameterValues) { using (var xtw = XmlWriter.Create(ms, _xmlWriterSettings)) { @@ -829,7 +871,7 @@ protected HttpPostXmlFormatterBase(WebServiceTarget target) : base(target) { } - protected void WriteAllParametersToCurrenElement(XmlWriter currentXmlWriter, object[] parameterValues) + protected void WriteAllParametersToCurrenElement(XmlWriter currentXmlWriter, object?[] parameterValues) { for (int i = 0; i < Target.Parameters.Count; i++) { diff --git a/src/NLog.WindowsEventLog/NLog.WindowsEventLog.csproj b/src/NLog.WindowsEventLog/NLog.WindowsEventLog.csproj index 43c1d6ef9c..e0add104f6 100644 --- a/src/NLog.WindowsEventLog/NLog.WindowsEventLog.csproj +++ b/src/NLog.WindowsEventLog/NLog.WindowsEventLog.csproj @@ -1,9 +1,9 @@  - PackageReference - - netstandard2.0 + 17.0 + netstandard2.0 + netstandard2.0;netstandard2.1 NLog.WindowsEventLog for .NET Standard NLog @@ -27,25 +27,36 @@ https://github.com/NLog/NLog.git true - 5.0.0.0 + 6.0.0.0 ..\NLog.snk true true true true - true - true - copyused + enable + 9 + true + true + true + + + + NLog.WindowsEventLog for NetStandard 2.1 + $(DefineConstants);WindowsEventLogPackage NLog.WindowsEventLog for NetStandard 2.0 - $(DefineConstants);NETSTANDARD2_0;WindowsEventLogPackage + $(DefineConstants);WindowsEventLogPackage - + - + + + + + diff --git a/src/NLog.WindowsEventLog/Properties/AssemblyInfo.cs b/src/NLog.WindowsEventLog/Properties/AssemblyInfo.cs index 0e114863aa..3c9d9e1f4a 100644 --- a/src/NLog.WindowsEventLog/Properties/AssemblyInfo.cs +++ b/src/NLog.WindowsEventLog/Properties/AssemblyInfo.cs @@ -33,7 +33,6 @@ using System; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; @@ -41,6 +40,6 @@ [assembly: CLSCompliant(true)] [assembly: ComVisible(false)] [assembly: AllowPartiallyTrustedCallers] -#if !NET35 && !NETSTANDARD1_3 && !NETSTANDARD1_5 +#if !NET35 [assembly: SecurityRules(SecurityRuleSet.Level1)] #endif diff --git a/src/NLog.WindowsRegistry/NLog.WindowsRegistry.csproj b/src/NLog.WindowsRegistry/NLog.WindowsRegistry.csproj index f1091c465a..7cb120b40e 100644 --- a/src/NLog.WindowsRegistry/NLog.WindowsRegistry.csproj +++ b/src/NLog.WindowsRegistry/NLog.WindowsRegistry.csproj @@ -1,7 +1,9 @@  - net35;net46;netstandard2.0 + 17.0 + net35;net46;netstandard2.0 + net35;net46;netstandard2.0;netstandard2.1 NLog Windows Registry NLog @@ -25,16 +27,18 @@ https://github.com/NLog/NLog.git true - 5.0.0.0 + 6.0.0.0 ..\NLog.snk true true true true - true - true - copyused + enable + 9 + true + true + true @@ -54,16 +58,23 @@ NLog.WindowsRegistry for NetStandard 2.0 - $(DefineConstants);NETSTANDARD - + + NLog.WindowsRegistry for NetStandard 2.1 + + + - + + + + + diff --git a/src/NLog.WindowsRegistry/Properties/AssemblyInfo.cs b/src/NLog.WindowsRegistry/Properties/AssemblyInfo.cs index 5abf4e6c0f..3c9d9e1f4a 100644 --- a/src/NLog.WindowsRegistry/Properties/AssemblyInfo.cs +++ b/src/NLog.WindowsRegistry/Properties/AssemblyInfo.cs @@ -33,7 +33,6 @@ using System; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; diff --git a/src/NLog.WindowsRegistry/RegistryLayoutRenderer.cs b/src/NLog.WindowsRegistry/RegistryLayoutRenderer.cs index 06259d6469..4415763903 100644 --- a/src/NLog.WindowsRegistry/RegistryLayoutRenderer.cs +++ b/src/NLog.WindowsRegistry/RegistryLayoutRenderer.cs @@ -38,7 +38,6 @@ namespace NLog.LayoutRenderers using System.Text; using Microsoft.Win32; using NLog.Common; - using NLog.Config; using NLog.Layouts; /// @@ -51,18 +50,18 @@ public class RegistryLayoutRenderer : LayoutRenderer /// Gets or sets the registry value name. /// /// - public Layout Value { get; set; } + public Layout Value { get; set; } = Layout.Empty; /// /// Gets or sets the value to be output when the specified registry key or value is not found. /// /// - public Layout DefaultValue { get; set; } + public Layout? DefaultValue { get; set; } /// /// Require escaping backward slashes in . Need to be backwards-compatible. /// - /// When true: + /// When : /// /// `\` in value should be configured as `\\` /// `\\` in value should be configured as `\\\\`. @@ -100,15 +99,31 @@ public class RegistryLayoutRenderer : LayoutRenderer ///
///
/// - [RequiredParameter] - public Layout Key { get; set; } + public Layout Key { get; set; } = Layout.Empty; + + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + + if (Key is null || ReferenceEquals(Key, Layout.Empty)) + throw new NLogConfigurationException("Registry-LayoutRenderer Key-property must be assigned. Lookup blank value not supported."); + } /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { - object registryValue = null; + var value = LookupRegistryValue(logEvent); + builder.Append(value); + } + + private string LookupRegistryValue(LogEventInfo logEvent) + { + object? registryValue = null; // Value = null is necessary for querying "unnamed values" - string renderedValue = Value?.Render(logEvent); + var registryName = Value?.Render(logEvent); + if (string.IsNullOrEmpty(registryName)) + registryName = null; var parseResult = ParseKey(Key.Render(logEvent)); try @@ -123,12 +138,12 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) { using (RegistryKey registryKey = rootKey.OpenSubKey(parseResult.SubKey)) { - if (registryKey != null) registryValue = registryKey.GetValue(renderedValue); + if (registryKey != null) registryValue = registryKey.GetValue(registryName); } } else { - registryValue = rootKey.GetValue(renderedValue); + registryValue = rootKey.GetValue(registryName); } } } @@ -140,27 +155,24 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) InternalLogger.Error(ex, "Error when reading from registry"); } - string value = null; if (registryValue != null) // valid value returned from registry will never be null { - value = Convert.ToString(registryValue, System.Globalization.CultureInfo.InvariantCulture); + return Convert.ToString(registryValue, System.Globalization.CultureInfo.InvariantCulture); } - else if (DefaultValue != null) - { - value = DefaultValue.Render(logEvent); - if (RequireEscapingSlashesInDefaultValue) - { - //remove escape slash - value = value.Replace("\\\\", "\\"); - } + var defaultValue = DefaultValue?.Render(logEvent); + if (defaultValue != null && RequireEscapingSlashesInDefaultValue) + { + //remove escape slash + defaultValue = defaultValue.Replace("\\\\", "\\"); } - builder.Append(value); + + return defaultValue ?? string.Empty; } private sealed class ParseResult { - public string SubKey { get; set; } + public string SubKey { get; set; } = string.Empty; public RegistryHive Hive { get; set; } @@ -180,7 +192,7 @@ private static ParseResult ParseKey(string key) string hiveName; int pos = key.IndexOfAny(new char[] { '\\', '/' }); - string subkey = null; + string subkey = string.Empty; if (pos >= 0) { hiveName = key.Substring(0, pos); @@ -220,7 +232,7 @@ private static ParseResult ParseKey(string key) {"HKEY_CLASSES_ROOT", RegistryHive.ClassesRoot}, {"HKEY_USERS", RegistryHive.Users}, {"HKEY_CURRENT_CONFIG", RegistryHive.CurrentConfig}, -#if !NETSTANDARD +#if NETFRAMEWORK {"HKEY_DYN_DATA", RegistryHive.DynData}, #endif {"HKEY_PERFORMANCE_DATA", RegistryHive.PerformanceData}, diff --git a/src/NLog.proj b/src/NLog.proj index c5fe5c71b1..327172ab34 100644 --- a/src/NLog.proj +++ b/src/NLog.proj @@ -103,6 +103,7 @@ + @@ -117,7 +118,7 @@ Inputs="@(TargetFramework -> '$(BaseOutputDirectory)\bin\$(Configuration)\%(Identity)\NLog.dll');$(DumpApiXml);$(MergeApiXml)" Outputs="$(BaseOutputDirectory)\bin\$(Configuration)\NLogMerged.api.xml" > - diff --git a/src/NLog.sln b/src/NLog.sln index a83f8ba219..429b5d4f52 100644 --- a/src/NLog.sln +++ b/src/NLog.sln @@ -13,8 +13,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLogAutoLoadExtension", ".. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.WindowsEventLog", "NLog.WindowsEventLog\NLog.WindowsEventLog.csproj", "{99BCE294-CA56-41BD-AEBF-61795F5650AB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PackageLoaderTestAssembly", "..\tests\PackageLoaderTestAssembly\PackageLoaderTestAssembly.csproj", "{1A86A7FA-1C2E-4745-87FC-FD3F583DBAD2}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestAssets", "TestAssets", "{8CCD7CF7-F30B-414A-B7FE-1B49411E4EFD}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Packages", "Packages", "{194AB4BD-C817-4C59-A86C-49D89B8C3A64}" @@ -23,17 +21,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManuallyLoadedExtension", " EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0C18FFD9-FD83-4F04-B4B5-91745D2D125E}" ProjectSection(SolutionItems) = preProject + ..\.coderabbit.yaml = ..\.coderabbit.yaml ..\.editorconfig = ..\.editorconfig ..\.gitattributes = ..\.gitattributes ..\.gitignore = ..\.gitignore ..\appveyor.yml = ..\appveyor.yml ..\build.ps1 = ..\build.ps1 ..\CHANGELOG.md = ..\CHANGELOG.md - ..\.github\CONTRIBUTING.md = ..\.github\CONTRIBUTING.md + ..\CONTRIBUTING.md = ..\CONTRIBUTING.md ..\.github\dependabot.yml = ..\.github\dependabot.yml + NLog.proj = NLog.proj ..\README.md = ..\README.md ..\run-tests.ps1 = ..\run-tests.ps1 ..\SECURITY.md = ..\SECURITY.md + ..\Test-XmlFile.ps1 = ..\Test-XmlFile.ps1 EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.WindowsRegistry", "NLog.WindowsRegistry\NLog.WindowsRegistry.csproj", "{BFE365AA-A01F-4E8B-B540-3F8DF0466CE6}" @@ -48,6 +49,38 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Database.Tests", "..\t EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestTrimPublish", "..\tests\TestTrimPublish\TestTrimPublish.csproj", "{058FBDB2-4C9C-45F6-BE9E-088AD019C77A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Targets.Mail", "NLog.Targets.Mail\NLog.Targets.Mail.csproj", "{B47B6417-6987-4D42-9C8C-4659C8F56806}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Targets.Mail.Tests", "..\tests\NLog.Targets.Mail.Tests\NLog.Targets.Mail.Tests.csproj", "{F26A0E10-7DED-4AD9-BB59-147E4EB70924}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Targets.WebService", "NLog.Targets.WebService\NLog.Targets.WebService.csproj", "{F5F88648-5D6C-4A18-B98E-FCEEA4D704CE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Targets.WebService.Tests", "..\tests\NLog.Targets.WebService.Tests\NLog.Targets.WebService.Tests.csproj", "{D6FB13E7-92A7-4F4F-A09A-37EB04767A82}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Targets.Network", "NLog.Targets.Network\NLog.Targets.Network.csproj", "{B048DD1F-4A24-453B-9C3D-F08280AE3FF3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Targets.Network.Tests", "..\tests\NLog.Targets.Network.Tests\NLog.Targets.Network.Tests.csproj", "{38828090-4953-4EF5-929C-8063FD4BCCC9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.RegEx", "NLog.RegEx\NLog.RegEx.csproj", "{A87BFA04-DBFD-44F4-8223-A02AC0E85C7F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.RegEx.Tests", "..\tests\NLog.RegEx.Tests\NLog.RegEx.Tests.csproj", "{7C77DA2A-DF9A-4F4C-91BF-A593B31D7619}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Targets.ConcurrentFile", "NLog.Targets.ConcurrentFile\NLog.Targets.ConcurrentFile.csproj", "{C54C900A-2FAC-4482-BFFB-D4A9F6AB3619}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Targets.ConcurrentFile.Tests", "..\tests\NLog.Targets.ConcurrentFile.Tests\NLog.Targets.ConcurrentFile.Tests.csproj", "{A869B720-AB81-4AA9-94B4-12C5CF490FFE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLog.Targets.Trace", "NLog.Targets.Trace\NLog.Targets.Trace.csproj", "{68CE5BB3-DF9D-4774-9B66-6F7378BC04F5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLog.Targets.Trace.Tests", "..\tests\NLog.Targets.Trace.Tests\NLog.Targets.Trace.Tests.csproj", "{877F835E-4D1E-45D8-95AD-AC2F0E3764C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLog.Targets.AtomicFile", "NLog.Targets.AtomicFile\NLog.Targets.AtomicFile.csproj", "{C767E563-82AB-4545-8904-B31E4CF86C2E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLog.Targets.AtomicFile.Tests", "..\tests\NLog.Targets.AtomicFile.Tests\NLog.Targets.AtomicFile.Tests.csproj", "{2365D168-476A-4239-9086-71DC8F5A2B75}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLog.Targets.GZipFile", "NLog.Targets.GZipFile\NLog.Targets.GZipFile.csproj", "{D21F3DA2-B45C-4CBC-A392-03ACA2021CC1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLog.Targets.GZipFile.Tests", "..\tests\NLog.Targets.GZipFile.Tests\NLog.Targets.GZipFile.Tests.csproj", "{DED8D8EC-B0E6-4F2C-9BB8-744FA34230B5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -74,10 +107,6 @@ Global {99BCE294-CA56-41BD-AEBF-61795F5650AB}.Debug|Any CPU.Build.0 = Debug|Any CPU {99BCE294-CA56-41BD-AEBF-61795F5650AB}.Release|Any CPU.ActiveCfg = Release|Any CPU {99BCE294-CA56-41BD-AEBF-61795F5650AB}.Release|Any CPU.Build.0 = Release|Any CPU - {1A86A7FA-1C2E-4745-87FC-FD3F583DBAD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1A86A7FA-1C2E-4745-87FC-FD3F583DBAD2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A86A7FA-1C2E-4745-87FC-FD3F583DBAD2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1A86A7FA-1C2E-4745-87FC-FD3F583DBAD2}.Release|Any CPU.Build.0 = Release|Any CPU {B9714BBB-A9F6-429B-ACC1-961C16643057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B9714BBB-A9F6-429B-ACC1-961C16643057}.Debug|Any CPU.Build.0 = Debug|Any CPU {B9714BBB-A9F6-429B-ACC1-961C16643057}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -106,6 +135,70 @@ Global {058FBDB2-4C9C-45F6-BE9E-088AD019C77A}.Debug|Any CPU.Build.0 = Debug|Any CPU {058FBDB2-4C9C-45F6-BE9E-088AD019C77A}.Release|Any CPU.ActiveCfg = Release|Any CPU {058FBDB2-4C9C-45F6-BE9E-088AD019C77A}.Release|Any CPU.Build.0 = Release|Any CPU + {B47B6417-6987-4D42-9C8C-4659C8F56806}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B47B6417-6987-4D42-9C8C-4659C8F56806}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B47B6417-6987-4D42-9C8C-4659C8F56806}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B47B6417-6987-4D42-9C8C-4659C8F56806}.Release|Any CPU.Build.0 = Release|Any CPU + {F26A0E10-7DED-4AD9-BB59-147E4EB70924}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F26A0E10-7DED-4AD9-BB59-147E4EB70924}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F26A0E10-7DED-4AD9-BB59-147E4EB70924}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F26A0E10-7DED-4AD9-BB59-147E4EB70924}.Release|Any CPU.Build.0 = Release|Any CPU + {F5F88648-5D6C-4A18-B98E-FCEEA4D704CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5F88648-5D6C-4A18-B98E-FCEEA4D704CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5F88648-5D6C-4A18-B98E-FCEEA4D704CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5F88648-5D6C-4A18-B98E-FCEEA4D704CE}.Release|Any CPU.Build.0 = Release|Any CPU + {D6FB13E7-92A7-4F4F-A09A-37EB04767A82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6FB13E7-92A7-4F4F-A09A-37EB04767A82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6FB13E7-92A7-4F4F-A09A-37EB04767A82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6FB13E7-92A7-4F4F-A09A-37EB04767A82}.Release|Any CPU.Build.0 = Release|Any CPU + {B048DD1F-4A24-453B-9C3D-F08280AE3FF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B048DD1F-4A24-453B-9C3D-F08280AE3FF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B048DD1F-4A24-453B-9C3D-F08280AE3FF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B048DD1F-4A24-453B-9C3D-F08280AE3FF3}.Release|Any CPU.Build.0 = Release|Any CPU + {38828090-4953-4EF5-929C-8063FD4BCCC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38828090-4953-4EF5-929C-8063FD4BCCC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38828090-4953-4EF5-929C-8063FD4BCCC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38828090-4953-4EF5-929C-8063FD4BCCC9}.Release|Any CPU.Build.0 = Release|Any CPU + {A87BFA04-DBFD-44F4-8223-A02AC0E85C7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A87BFA04-DBFD-44F4-8223-A02AC0E85C7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A87BFA04-DBFD-44F4-8223-A02AC0E85C7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A87BFA04-DBFD-44F4-8223-A02AC0E85C7F}.Release|Any CPU.Build.0 = Release|Any CPU + {7C77DA2A-DF9A-4F4C-91BF-A593B31D7619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C77DA2A-DF9A-4F4C-91BF-A593B31D7619}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C77DA2A-DF9A-4F4C-91BF-A593B31D7619}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C77DA2A-DF9A-4F4C-91BF-A593B31D7619}.Release|Any CPU.Build.0 = Release|Any CPU + {C54C900A-2FAC-4482-BFFB-D4A9F6AB3619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C54C900A-2FAC-4482-BFFB-D4A9F6AB3619}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C54C900A-2FAC-4482-BFFB-D4A9F6AB3619}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C54C900A-2FAC-4482-BFFB-D4A9F6AB3619}.Release|Any CPU.Build.0 = Release|Any CPU + {A869B720-AB81-4AA9-94B4-12C5CF490FFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A869B720-AB81-4AA9-94B4-12C5CF490FFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A869B720-AB81-4AA9-94B4-12C5CF490FFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A869B720-AB81-4AA9-94B4-12C5CF490FFE}.Release|Any CPU.Build.0 = Release|Any CPU + {68CE5BB3-DF9D-4774-9B66-6F7378BC04F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68CE5BB3-DF9D-4774-9B66-6F7378BC04F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68CE5BB3-DF9D-4774-9B66-6F7378BC04F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68CE5BB3-DF9D-4774-9B66-6F7378BC04F5}.Release|Any CPU.Build.0 = Release|Any CPU + {877F835E-4D1E-45D8-95AD-AC2F0E3764C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {877F835E-4D1E-45D8-95AD-AC2F0E3764C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {877F835E-4D1E-45D8-95AD-AC2F0E3764C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {877F835E-4D1E-45D8-95AD-AC2F0E3764C7}.Release|Any CPU.Build.0 = Release|Any CPU + {C767E563-82AB-4545-8904-B31E4CF86C2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C767E563-82AB-4545-8904-B31E4CF86C2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C767E563-82AB-4545-8904-B31E4CF86C2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C767E563-82AB-4545-8904-B31E4CF86C2E}.Release|Any CPU.Build.0 = Release|Any CPU + {2365D168-476A-4239-9086-71DC8F5A2B75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2365D168-476A-4239-9086-71DC8F5A2B75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2365D168-476A-4239-9086-71DC8F5A2B75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2365D168-476A-4239-9086-71DC8F5A2B75}.Release|Any CPU.Build.0 = Release|Any CPU + {D21F3DA2-B45C-4CBC-A392-03ACA2021CC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D21F3DA2-B45C-4CBC-A392-03ACA2021CC1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D21F3DA2-B45C-4CBC-A392-03ACA2021CC1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D21F3DA2-B45C-4CBC-A392-03ACA2021CC1}.Release|Any CPU.Build.0 = Release|Any CPU + {DED8D8EC-B0E6-4F2C-9BB8-744FA34230B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DED8D8EC-B0E6-4F2C-9BB8-744FA34230B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DED8D8EC-B0E6-4F2C-9BB8-744FA34230B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DED8D8EC-B0E6-4F2C-9BB8-744FA34230B5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -114,7 +207,6 @@ Global {94F21E64-A259-4D79-AFCA-08974DF51701} = {8CCD7CF7-F30B-414A-B7FE-1B49411E4EFD} {B1DA63B3-1EB0-41B3-BFDA-9D7409481E27} = {8CCD7CF7-F30B-414A-B7FE-1B49411E4EFD} {99BCE294-CA56-41BD-AEBF-61795F5650AB} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} - {1A86A7FA-1C2E-4745-87FC-FD3F583DBAD2} = {8CCD7CF7-F30B-414A-B7FE-1B49411E4EFD} {B9714BBB-A9F6-429B-ACC1-961C16643057} = {8CCD7CF7-F30B-414A-B7FE-1B49411E4EFD} {BFE365AA-A01F-4E8B-B540-3F8DF0466CE6} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} {1648106F-CD67-4009-878B-96B5D2369B6C} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} @@ -122,6 +214,22 @@ Global {7D3A19CB-9BE0-4611-8464-6C49D4FE0106} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} {35EB0745-C37C-4E42-86C5-481A7D773E2E} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} {058FBDB2-4C9C-45F6-BE9E-088AD019C77A} = {8CCD7CF7-F30B-414A-B7FE-1B49411E4EFD} + {B47B6417-6987-4D42-9C8C-4659C8F56806} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {F26A0E10-7DED-4AD9-BB59-147E4EB70924} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {F5F88648-5D6C-4A18-B98E-FCEEA4D704CE} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {D6FB13E7-92A7-4F4F-A09A-37EB04767A82} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {B048DD1F-4A24-453B-9C3D-F08280AE3FF3} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {38828090-4953-4EF5-929C-8063FD4BCCC9} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {A87BFA04-DBFD-44F4-8223-A02AC0E85C7F} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {7C77DA2A-DF9A-4F4C-91BF-A593B31D7619} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {C54C900A-2FAC-4482-BFFB-D4A9F6AB3619} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {A869B720-AB81-4AA9-94B4-12C5CF490FFE} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {68CE5BB3-DF9D-4774-9B66-6F7378BC04F5} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {877F835E-4D1E-45D8-95AD-AC2F0E3764C7} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {C767E563-82AB-4545-8904-B31E4CF86C2E} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {2365D168-476A-4239-9086-71DC8F5A2B75} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {D21F3DA2-B45C-4CBC-A392-03ACA2021CC1} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} + {DED8D8EC-B0E6-4F2C-9BB8-744FA34230B5} = {194AB4BD-C817-4C59-A86C-49D89B8C3A64} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6B4C8E9F-1E2F-4AB3-993A-8776CFA08DC0} diff --git a/src/NLog/Annotations.cs b/src/NLog/Annotations.cs index 724a56d405..2284e2f4f1 100644 --- a/src/NLog/Annotations.cs +++ b/src/NLog/Annotations.cs @@ -64,45 +64,6 @@ internal sealed class CanBeNullAttribute : Attribute { } AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] internal sealed class NotNullAttribute : Attribute { } - /// - /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task - /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property - /// or of the Lazy.Value property can never be null. - /// - /// - /// public void Foo([ItemNotNull]List<string> books) - /// { - /// foreach (var book in books) { - /// if (book != null) // Warning: Expression is always true - /// Console.WriteLine(book.ToUpper()); - /// } - /// } - /// - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field)] - internal sealed class ItemNotNullAttribute : Attribute { } - - /// - /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task - /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property - /// or of the Lazy.Value property can be null. - /// - /// - /// public void Foo([ItemCanBeNull]List<string> books) - /// { - /// foreach (var book in books) - /// { - /// // Warning: Possible 'System.NullReferenceException' - /// Console.WriteLine(book.ToUpper()); - /// } - /// } - /// - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field)] - internal sealed class ItemCanBeNullAttribute : Attribute { } - /// /// Indicates that the marked method builds string by the format pattern and (optional) arguments. /// The parameter, which contains the format string, should be given in the constructor. The format string @@ -146,169 +107,6 @@ public StringFormatMethodAttribute([NotNull] string formatParameterName) [AttributeUsage(AttributeTargets.Parameter)] internal sealed class StructuredMessageTemplateAttribute : Attribute { } - /// - /// Use this annotation to specify a type that contains static or const fields - /// with values for the annotated property/field/parameter. - /// The specified type will be used to improve completion suggestions. - /// - /// - /// namespace TestNamespace - /// { - /// public class Constants - /// { - /// public static int INT_CONST = 1; - /// public const string STRING_CONST = "1"; - /// } - /// - /// public class Class1 - /// { - /// [ValueProvider("TestNamespace.Constants")] public int myField; - /// public void Foo([ValueProvider("TestNamespace.Constants")] string str) { } - /// - /// public void Test() - /// { - /// Foo(/*try completion here*/);// - /// myField = /*try completion here*/ - /// } - /// } - /// } - /// - [AttributeUsage( - AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, - AllowMultiple = true)] - internal sealed class ValueProviderAttribute : Attribute - { - public ValueProviderAttribute([NotNull] string name) - { - Name = name; - } - - [NotNull] public string Name { get; } - } - - /// - /// Indicates that the integral value falls into the specified interval. - /// It's allowed to specify multiple non-intersecting intervals. - /// Values of interval boundaries are inclusive. - /// - /// - /// void Foo([ValueRange(0, 100)] int value) { - /// if (value == -1) { // Warning: Expression is always 'false' - /// ... - /// } - /// } - /// - [AttributeUsage( - AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | - AttributeTargets.Method | AttributeTargets.Delegate, - AllowMultiple = true)] - internal sealed class ValueRangeAttribute : Attribute - { - public object From { get; } - public object To { get; } - - public ValueRangeAttribute(long from, long to) - { - From = from; - To = to; - } - - public ValueRangeAttribute(ulong from, ulong to) - { - From = from; - To = to; - } - - public ValueRangeAttribute(long value) - { - From = To = value; - } - - public ValueRangeAttribute(ulong value) - { - From = To = value; - } - } - - /// - /// Indicates that the integral value never falls below zero. - /// - /// - /// void Foo([NonNegativeValue] int value) { - /// if (value == -1) { // Warning: Expression is always 'false' - /// ... - /// } - /// } - /// - [AttributeUsage( - AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | - AttributeTargets.Method | AttributeTargets.Delegate)] - internal sealed class NonNegativeValueAttribute : Attribute { } - - /// - /// Indicates that the function argument should be a string literal and match - /// one of the parameters of the caller function. This annotation is used for parameters - /// like 'string paramName' parameter of the constructor. - /// - /// - /// void Foo(string param) { - /// if (param == null) - /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol - /// } - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class InvokerParameterNameAttribute : Attribute { } - - /// - /// Indicates that the method is contained in a type that implements - /// System.ComponentModel.INotifyPropertyChanged interface and this method - /// is used to notify that some property value changed. - /// - /// - /// The method should be non-static and conform to one of the supported signatures: - /// - /// NotifyChanged(string) - /// NotifyChanged(params string[]) - /// NotifyChanged{T}(Expression{Func{T}}) - /// NotifyChanged{T,U}(Expression{Func{T,U}}) - /// SetProperty{T}(ref T, T, string) - /// - /// - /// - /// public class Foo : INotifyPropertyChanged { - /// public event PropertyChangedEventHandler PropertyChanged; - /// - /// [NotifyPropertyChangedInvocator] - /// protected virtual void NotifyChanged(string propertyName) { ... } - /// - /// string _name; - /// - /// public string Name { - /// get { return _name; } - /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } - /// } - /// } - /// - /// Examples of generated notifications: - /// - /// NotifyChanged("Property") - /// NotifyChanged(() => Property) - /// NotifyChanged((VM x) => x.Property) - /// SetProperty(ref myField, value, "Property") - /// - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class NotifyPropertyChangedInvocatorAttribute : Attribute - { - public NotifyPropertyChangedInvocatorAttribute() { } - public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) - { - ParameterName = parameterName; - } - - [CanBeNull] public string ParameterName { get; } - } - /// /// Describes dependency between method input and output. /// @@ -369,75 +167,6 @@ public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStat public bool ForceFullStates { get; } } - - /// - /// Indicates whether the marked element should be localized. - /// - /// - /// [LocalizationRequiredAttribute(true)] - /// class Foo { - /// string str = "my string"; // Warning: Localizable string - /// } - /// - [AttributeUsage(AttributeTargets.All)] - internal sealed class LocalizationRequiredAttribute : Attribute - { - public LocalizationRequiredAttribute() : this(true) { } - - public LocalizationRequiredAttribute(bool required) - { - Required = required; - } - - public bool Required { get; } - } - - /// - /// Indicates that the value of the marked type (or its derivatives) - /// cannot be compared using '==' or '!=' operators and Equals() - /// should be used instead. However, using '==' or '!=' for comparison - /// with null is always permitted. - /// - /// - /// [CannotApplyEqualityOperator] - /// class NoEquality { } - /// - /// class UsesNoEquality { - /// void Test() { - /// var ca1 = new NoEquality(); - /// var ca2 = new NoEquality(); - /// if (ca1 != null) { // OK - /// bool condition = ca1 == ca2; // Warning - /// } - /// } - /// } - /// - [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] - internal sealed class CannotApplyEqualityOperatorAttribute : Attribute { } - - /// - /// When applied to a target attribute, specifies a requirement for any type marked - /// with the target attribute to implement or inherit specific type or types. - /// - /// - /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement - /// class ComponentAttribute : Attribute { } - /// - /// [Component] // ComponentAttribute requires implementing IComponent interface - /// class MyComponent : IComponent { } - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - [BaseTypeRequired(typeof(Attribute))] - internal sealed class BaseTypeRequiredAttribute : Attribute - { - public BaseTypeRequiredAttribute([NotNull] Type baseType) - { - BaseType = baseType; - } - - [NotNull] public Type BaseType { get; } - } - /// /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), /// so this symbol will be ignored by usage-checking inspections.
@@ -479,7 +208,6 @@ public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTar public ImplicitUseTargetFlags TargetFlags { get; } } - /// /// Can be applied to attributes, type parameters, and parameters of a type assignable from . /// When applied to an attribute, the decorated attribute behaves the same as . @@ -546,403 +274,4 @@ internal enum ImplicitUseTargetFlags /// Entity marked with the attribute and all its members considered used. WithMembers = Itself | Members } - - /// - /// This attribute is intended to mark publicly available API, - /// which should not be removed and so is treated as used. - /// - [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] - [AttributeUsage(AttributeTargets.All, Inherited = false)] - internal sealed class PublicAPIAttribute : Attribute - { - public PublicAPIAttribute() { } - - public PublicAPIAttribute([NotNull] string comment) - { - Comment = comment; - } - - [CanBeNull] public string Comment { get; } - } - - /// - /// Tells the code analysis engine if the parameter is completely handled when the invoked method is on stack. - /// If the parameter is a delegate, indicates that delegate can only be invoked during method execution - /// (the delegate can be invoked zero or multiple times, but not stored to some field and invoked later, - /// when the containing method is no longer on the execution stack). - /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. - /// If is true, the attribute will only takes effect if the method invocation is located under the 'await' expression. - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class InstantHandleAttribute : Attribute - { - /// - /// Require the method invocation to be used under the 'await' expression for this attribute to take effect on code analysis engine. - /// Can be used for delegate/enumerable parameters of 'async' methods. - /// - public bool RequireAwait { get; set; } - } - - /// - /// Indicates that a method does not make any observable state changes. - /// The same as System.Diagnostics.Contracts.PureAttribute. - /// - /// - /// [Pure] int Multiply(int x, int y) => x * y; - /// - /// void M() { - /// Multiply(123, 42); // Warning: Return value of pure method is not used - /// } - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class PureAttribute : Attribute { } - - /// - /// Indicates that the return value of the method invocation must be used. - /// - /// - /// Methods decorated with this attribute (in contrast to pure methods) might change state, - /// but make no sense without using their return value.
- /// Similarly to , this attribute - /// will help to detect usages of the method when the return value is not used. - /// Optionally, you can specify a message to use when showing warnings, e.g. - /// [MustUseReturnValue("Use the return value to...")]. - ///
- [AttributeUsage(AttributeTargets.Method)] - internal sealed class MustUseReturnValueAttribute : Attribute - { - public MustUseReturnValueAttribute() { } - - public MustUseReturnValueAttribute([NotNull] string justification) - { - Justification = justification; - } - - [CanBeNull] public string Justification { get; } - } - - /// - /// This annotation allows to enforce allocation-less usage patterns of delegates for performance-critical APIs. - /// When this annotation is applied to the parameter of delegate type, IDE checks the input argument of this parameter: - /// * When lambda expression or anonymous method is passed as an argument, IDE verifies that the passed closure - /// has no captures of the containing local variables and the compiler is able to cache the delegate instance - /// to avoid heap allocations. Otherwise the warning is produced. - /// * IDE warns when method name or local function name is passed as an argument as this always results - /// in heap allocation of the delegate instance. - /// - /// - /// In C# 9.0 code IDE would also suggest to annotate the anonymous function with 'static' modifier - /// to make use of the similar analysis provided by the language/compiler. - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class RequireStaticDelegateAttribute : Attribute - { - public bool IsError { get; set; } - } - - /// - /// Indicates the type member or parameter of some type, that should be used instead of all other ways - /// to get the value of that type. This annotation is useful when you have some "context" value evaluated - /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. - /// - /// - /// class Foo { - /// [ProvidesContext] IBarService _barService = ...; - /// - /// void ProcessNode(INode node) { - /// DoSomething(node, node.GetGlobalServices().Bar); - /// // ^ Warning: use value of '_barService' field - /// } - /// } - /// - [AttributeUsage( - AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] - internal sealed class ProvidesContextAttribute : Attribute { } - - /// - /// Indicates that a parameter is a path to a file or a folder within a web project. - /// Path can be relative or absolute, starting from web root (~). - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class PathReferenceAttribute : Attribute - { - public PathReferenceAttribute() { } - - public PathReferenceAttribute([NotNull, PathReference] string basePath) - { - BasePath = basePath; - } - - [CanBeNull] public string BasePath { get; } - } - - /// - /// An extension method marked with this attribute is processed by code completion - /// as a 'Source Template'. When the extension method is completed over some expression, its source code - /// is automatically expanded like a template at call site. - /// - /// - /// Template method body can contain valid source code and/or special comments starting with '$'. - /// Text inside these comments is added as source code when the template is applied. Template parameters - /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. - /// Use the attribute to specify macros for parameters. - /// - /// - /// In this example, the 'forEach' method is a source template available over all values - /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: - /// - /// [SourceTemplate] - /// public static void forEach<T>(this IEnumerable<T> xs) { - /// foreach (var x in xs) { - /// //$ $END$ - /// } - /// } - /// - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class SourceTemplateAttribute : Attribute { } - - /// - /// Allows specifying a macro for a parameter of a source template. - /// - /// - /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression - /// is defined in the property. When applied on a method, the target - /// template parameter is defined in the property. To apply the macro silently - /// for the parameter, set the property value = -1. - /// - /// - /// Applying the attribute on a source template method: - /// - /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] - /// public static void forEach<T>(this IEnumerable<T> collection) { - /// foreach (var item in collection) { - /// //$ $END$ - /// } - /// } - /// - /// Applying the attribute on a template method parameter: - /// - /// [SourceTemplate] - /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { - /// /*$ var $x$Id = "$newguid$" + x.ToString(); - /// x.DoSomething($x$Id); */ - /// } - /// - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] - internal sealed class MacroAttribute : Attribute - { - /// - /// Allows specifying a macro that will be executed for a source template - /// parameter when the template is expanded. - /// - [CanBeNull] public string Expression { get; set; } - - /// - /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. - /// - /// - /// If the target parameter is used several times in the template, only one occurrence becomes editable; - /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, - /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. - /// - public int Editable { get; set; } - - /// - /// Identifies the target parameter of a source template if the - /// is applied on a template method. - /// - [CanBeNull] public string Target { get; set; } - } - - /// - /// Indicates how method, constructor invocation, or property access - /// over collection type affects the contents of the collection. - /// When applied to a return value of a method indicates if the returned collection - /// is created exclusively for the caller (CollectionAccessType.UpdatedContent) or - /// can be read/updated from outside (CollectionAccessType.Read | CollectionAccessType.UpdatedContent) - /// Use to specify the access type. - /// - /// - /// Using this attribute only makes sense if all collection methods are marked with this attribute. - /// - /// - /// public class MyStringCollection : List<string> - /// { - /// [CollectionAccess(CollectionAccessType.Read)] - /// public string GetFirstString() - /// { - /// return this.ElementAt(0); - /// } - /// } - /// class Test - /// { - /// public void Foo() - /// { - /// // Warning: Contents of the collection is never updated - /// var col = new MyStringCollection(); - /// string x = col.GetFirstString(); - /// } - /// } - /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property | AttributeTargets.ReturnValue)] - internal sealed class CollectionAccessAttribute : Attribute - { - public CollectionAccessAttribute(CollectionAccessType collectionAccessType) - { - CollectionAccessType = collectionAccessType; - } - - public CollectionAccessType CollectionAccessType { get; } - } - - /// - /// Provides a value for the to define - /// how the collection method invocation affects the contents of the collection. - /// - [Flags] - internal enum CollectionAccessType - { - /// Method does not use or modify content of the collection. - None = 0, - /// Method only reads content of the collection but does not modify it. - Read = 1, - /// Method can change content of the collection but does not add new elements. - ModifyExistingContent = 2, - /// Method can add new elements to the collection. - UpdatedContent = ModifyExistingContent | 4 - } - - /// - /// Indicates that the marked method is assertion method, i.e. it halts the control flow if - /// one of the conditions is satisfied. To set the condition, mark one of the parameters with - /// attribute. - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class AssertionMethodAttribute : Attribute { } - - /// - /// Indicates the condition parameter of the assertion method. The method itself should be - /// marked by attribute. The mandatory argument of - /// the attribute is the assertion type. - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class AssertionConditionAttribute : Attribute - { - public AssertionConditionAttribute(AssertionConditionType conditionType) - { - ConditionType = conditionType; - } - - public AssertionConditionType ConditionType { get; } - } - - /// - /// Specifies assertion type. If the assertion method argument satisfies the condition, - /// then the execution continues. Otherwise, execution is assumed to be halted. - /// - internal enum AssertionConditionType - { - /// Marked parameter should be evaluated to true. - IS_TRUE = 0, - /// Marked parameter should be evaluated to false. - IS_FALSE = 1, - /// Marked parameter should be evaluated to null value. - IS_NULL = 2, - /// Marked parameter should be evaluated to not null value. - IS_NOT_NULL = 3, - } - - /// - /// Indicates that the marked method unconditionally terminates control flow execution. - /// For example, it could unconditionally throw exception. - /// - [Obsolete("Use [ContractAnnotation('=> halt')] instead")] - [AttributeUsage(AttributeTargets.Method)] - internal sealed class TerminatesProgramAttribute : Attribute { } - - /// - /// Indicates that the method is a pure LINQ method, with postponed enumeration (like Enumerable.Select, - /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters - /// of delegate type by analyzing LINQ method chains. - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class LinqTunnelAttribute : Attribute { } - - /// - /// Indicates that IEnumerable passed as a parameter is not enumerated. - /// Use this annotation to suppress the 'Possible multiple enumeration of IEnumerable' inspection. - /// - /// - /// static void ThrowIfNull<T>([NoEnumeration] T v, string n) where T : class - /// { - /// // custom check for null but no enumeration - /// } - /// - /// void Foo(IEnumerable<string> values) - /// { - /// ThrowIfNull(values, nameof(values)); - /// var x = values.ToList(); // No warnings about multiple enumeration - /// } - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class NoEnumerationAttribute : Attribute { } - - /// - /// Indicates that the marked parameter, field, or property is a regular expression pattern. - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] - internal sealed class RegexPatternAttribute : Attribute { } - - /// - /// Language of injected code fragment inside marked by string literal. - /// - internal enum InjectedLanguage - { - CSS, - HTML, - JAVASCRIPT, - JSON, - XML - } - - /// - /// Indicates that the marked parameter, field, or property is accepting a string literal - /// containing code fragment in a language specified by the . - /// - /// - /// void Foo([LanguageInjection(InjectedLanguage.CSS, Prefix = "body{", Suffix = "}")] string cssProps) - /// { - /// // cssProps should only contains a list of CSS properties - /// } - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] - internal sealed class LanguageInjectionAttribute : Attribute - { - public LanguageInjectionAttribute(InjectedLanguage injectedLanguage) - { - InjectedLanguage = injectedLanguage; - } - - /// Specify a language of injected code fragment. - public InjectedLanguage InjectedLanguage { get; } - - /// Specify a string that "precedes" injected string literal. - [CanBeNull] public string Prefix { get; set; } - - /// Specify a string that "follows" injected string literal. - [CanBeNull] public string Suffix { get; set; } - } - - /// - /// Prevents the Member Reordering feature from tossing members of the marked class. - /// - /// - /// The attribute must be mentioned in your member reordering patterns. - /// - [AttributeUsage( - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] - internal sealed class NoReorderAttribute : Attribute { } } diff --git a/src/NLog/Common/AsyncContinuation.cs b/src/NLog/Common/AsyncContinuation.cs index 1f8f7c7d06..c546a92ad5 100644 --- a/src/NLog/Common/AsyncContinuation.cs +++ b/src/NLog/Common/AsyncContinuation.cs @@ -41,5 +41,5 @@ namespace NLog.Common ///
/// Exception during asynchronous processing or null if no exception /// was thrown. - public delegate void AsyncContinuation(Exception exception); + public delegate void AsyncContinuation(Exception? exception); } diff --git a/src/NLog/Common/AsyncHelpers.cs b/src/NLog/Common/AsyncHelpers.cs index f92ab20a40..1205a0476e 100644 --- a/src/NLog/Common/AsyncHelpers.cs +++ b/src/NLog/Common/AsyncHelpers.cs @@ -47,30 +47,17 @@ public static class AsyncHelpers { internal static int GetManagedThreadId() { -#if !NETSTANDARD1_3 return Thread.CurrentThread.ManagedThreadId; -#else - return System.Environment.CurrentManagedThreadId; -#endif } - internal static void StartAsyncTask(AsyncHelpersTask asyncTask, object state) + internal static void StartAsyncTask(WaitCallback asyncDelegate, object? state) { - var asyncDelegate = asyncTask.AsyncDelegate; -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 ThreadPool.QueueUserWorkItem(asyncDelegate, state); -#else - System.Threading.Tasks.Task.Factory.StartNew(asyncDelegate, state, CancellationToken.None, System.Threading.Tasks.TaskCreationOptions.None, System.Threading.Tasks.TaskScheduler.Default); -#endif } internal static void WaitForDelay(TimeSpan delay) { -#if !NETSTANDARD1_3 Thread.Sleep(delay); -#else - System.Threading.Tasks.Task.Delay(delay).ConfigureAwait(false).GetAwaiter().GetResult(); -#endif } /// @@ -90,7 +77,7 @@ public static void ForEachItemSequentially(IEnumerable items, AsyncContinu IEnumerator enumerator = items.GetEnumerator(); - void InvokeNext(Exception ex) + void InvokeNext(Exception? ex) { if (ex != null) { @@ -122,7 +109,7 @@ public static void Repeat(int repeatCount, AsyncContinuation asyncContinuation, action = ExceptionGuard(action); int remaining = repeatCount; - void InvokeNext(Exception ex) + void InvokeNext(Exception? ex) { if (ex != null) { @@ -178,6 +165,8 @@ public static AsyncContinuation PrecededBy(AsyncContinuation asyncContinuation, /// The asynchronous continuation. /// The timeout. /// Wrapped continuation. + [Obsolete("Marked obsolete on NLog 6.0")] + [EditorBrowsable(EditorBrowsableState.Never)] public static AsyncContinuation WithTimeout(AsyncContinuation asyncContinuation, TimeSpan timeout) { return new TimeoutContinuation(asyncContinuation, timeout).Function; @@ -232,7 +221,7 @@ public static void ForEachItemInParallel(IEnumerable values, AsyncContinua foreach (T item in items) { T itemCopy = item; - StartAsyncTask(new AsyncHelpersTask(s => + StartAsyncTask((s) => { var preventMultipleCalls = PreventMultipleCalls(continuation); try @@ -250,7 +239,7 @@ public static void ForEachItemInParallel(IEnumerable values, AsyncContinua InternalLogger.Error(ex, "ForEachItemInParallel - Unhandled Exception"); preventMultipleCalls.Invoke(ex); } - }), null); + }, null); } } @@ -267,7 +256,7 @@ public static void ForEachItemInParallel(IEnumerable values, AsyncContinua public static void RunSynchronously(AsynchronousAction action) { var ev = new ManualResetEvent(false); - Exception lastException = null; + Exception? lastException = null; action(PreventMultipleCalls(ex => { lastException = ex; ev.Set(); })); ev.WaitOne(); @@ -298,7 +287,7 @@ public static AsyncContinuation PreventMultipleCalls(AsyncContinuation asyncCont /// /// The exceptions. /// Combined exception or null if no exception was thrown. - public static Exception GetCombinedException(IList exceptions) + public static Exception? GetCombinedException(IList exceptions) { if (exceptions.Count == 0) { @@ -312,7 +301,7 @@ public static Exception GetCombinedException(IList exceptions) var sb = new StringBuilder(); string separator = string.Empty; - string newline = EnvironmentHelper.NewLine; + string newline = Environment.NewLine; foreach (var ex in exceptions) { sb.Append(separator); diff --git a/src/NLog/Common/AsyncLogEventInfo.cs b/src/NLog/Common/AsyncLogEventInfo.cs index 454b8c234a..17fa3da21b 100644 --- a/src/NLog/Common/AsyncLogEventInfo.cs +++ b/src/NLog/Common/AsyncLogEventInfo.cs @@ -36,7 +36,11 @@ namespace NLog.Common /// /// Represents the logging event with asynchronous continuation. /// - public struct AsyncLogEventInfo : System.IEquatable + public +#if !NETFRAMEWORK + readonly +#endif + struct AsyncLogEventInfo : System.IEquatable { /// /// Initializes a new instance of the struct. diff --git a/src/NLog/Common/ConversionHelpers.cs b/src/NLog/Common/ConversionHelpers.cs index 1bd27c2fb5..1aace1c56e 100644 --- a/src/NLog/Common/ConversionHelpers.cs +++ b/src/NLog/Common/ConversionHelpers.cs @@ -47,7 +47,7 @@ public static class ConversionHelpers /// Input value /// Output value /// Default value - /// Returns false if the input value could not be parsed + /// Returns if the input value could not be parsed public static bool TryParseEnum(string inputValue, out TEnum resultValue, TEnum defaultValue = default(TEnum)) where TEnum : struct { if (!TryParseEnum(inputValue, true, out resultValue)) @@ -64,7 +64,7 @@ public static class ConversionHelpers /// Input value /// The type of the enum /// Output value. Null if parse failed - internal static bool TryParseEnum(string inputValue, Type enumType, out object resultValue) + internal static bool TryParseEnum(string inputValue, Type enumType, out object? resultValue) { if (StringHelpers.IsNullOrWhiteSpace(inputValue)) { @@ -89,9 +89,9 @@ internal static bool TryParseEnum(string inputValue, Type enumType, out object r /// /// The enumeration type to which to convert value. /// The string representation of the enumeration name or underlying value to convert. - /// true to ignore case; false to consider case. + /// to ignore case; to consider case. /// When this method returns, result contains an object of type TEnum whose value is represented by value if the parse operation succeeds. If the parse operation fails, result contains the default value of the underlying type of TEnum. Note that this value need not be a member of the TEnum enumeration. This parameter is passed uninitialized. - /// true if the value parameter was converted successfully; otherwise, false. + /// if the value parameter was converted successfully; otherwise, . /// Wrapper because Enum.TryParse is not present in .net 3.5 internal static bool TryParseEnum(string inputValue, bool ignoreCase, out TEnum resultValue) where TEnum : struct { @@ -116,7 +116,7 @@ internal static bool TryParseEnum(string inputValue, bool ignoreCase, out private static bool TryParseEnum_net3(string value, bool ignoreCase, out TEnum result) where TEnum : struct { var enumType = typeof(TEnum); - if (!enumType.IsEnum()) + if (!enumType.IsEnum) throw new ArgumentException($"Type '{enumType.FullName}' is not an enum"); if (StringHelpers.IsNullOrWhiteSpace(value)) diff --git a/src/NLog/Common/IInternalLoggerContext.cs b/src/NLog/Common/IInternalLoggerContext.cs index fe053a0797..d485aab18e 100644 --- a/src/NLog/Common/IInternalLoggerContext.cs +++ b/src/NLog/Common/IInternalLoggerContext.cs @@ -31,12 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using JetBrains.Annotations; - namespace NLog.Common { /// - /// Enables to extract extra context details for + /// Enables to extract extra context details for /// internal interface IInternalLoggerContext { @@ -48,7 +46,6 @@ internal interface IInternalLoggerContext /// /// The current LogFactory next to LogManager /// - [CanBeNull] - LogFactory LogFactory { get; } + LogFactory? LogFactory { get; } } } diff --git a/src/NLog/Common/InternalEventOccurredHandler.cs b/src/NLog/Common/InternalEventOccurredHandler.cs index 47363f774e..94ed6c8116 100644 --- a/src/NLog/Common/InternalEventOccurredHandler.cs +++ b/src/NLog/Common/InternalEventOccurredHandler.cs @@ -39,5 +39,5 @@ namespace NLog.Common /// /// Never use/call NLog Logger-objects when handling these internal events, as it will lead to deadlock / stackoverflow. /// - public delegate void InternalEventOccurredHandler(object sender, InternalLogEventArgs e); + public delegate void InternalEventOccurredHandler(object? sender, InternalLogEventArgs e); } diff --git a/src/NLog/Common/InternalLogEventArgs.cs b/src/NLog/Common/InternalLogEventArgs.cs index 1372d36536..215862dcff 100644 --- a/src/NLog/Common/InternalLogEventArgs.cs +++ b/src/NLog/Common/InternalLogEventArgs.cs @@ -34,12 +34,15 @@ namespace NLog.Common { using System; - using JetBrains.Annotations; /// /// Internal LogEvent details from /// - public readonly struct InternalLogEventArgs + public +#if !NETFRAMEWORK + readonly +#endif + struct InternalLogEventArgs { /// /// The rendered message @@ -54,24 +57,21 @@ public readonly struct InternalLogEventArgs /// /// The exception. Could be null. /// - [CanBeNull] - public Exception Exception { get; } + public Exception? Exception { get; } /// /// The type that triggered this internal log event, for example the FileTarget. /// This property is not always populated. /// - [CanBeNull] - public Type SenderType { get; } + public Type? SenderType { get; } /// /// The context name that triggered this internal log event, for example the name of the Target. /// This property is not always populated. /// - [CanBeNull] - public string SenderName { get; } + public string? SenderName { get; } - internal InternalLogEventArgs(string message, LogLevel level, [CanBeNull] Exception exception, [CanBeNull] Type senderType, [CanBeNull] string senderName) + internal InternalLogEventArgs(string message, LogLevel level, Exception? exception, Type? senderType, string? senderName) { Message = message; Level = level; diff --git a/src/NLog/Common/InternalLogger-generated.cs b/src/NLog/Common/InternalLogger-generated.cs index e3ad568d8f..7e877c095e 100644 --- a/src/NLog/Common/InternalLogger-generated.cs +++ b/src/NLog/Common/InternalLogger-generated.cs @@ -70,13 +70,40 @@ public static partial class InternalLogger public static bool IsFatalEnabled => IsLogLevelEnabled(LogLevel.Fatal); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER /// /// Logs the specified message without an at the Trace level. /// /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Trace([Localizable(false)] string message, params object[] args) + public static void Trace([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (IsTraceEnabled) + Write(null, LogLevel.Trace, message, args.IsEmpty ? null : args.ToArray()); + } + + /// + /// Logs the specified message with an at the Trace level. + /// + /// Exception to be logged. + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Trace(Exception? ex, [Localizable(false)] string message, params ReadOnlySpan args) + { + if (IsTraceEnabled) + Write(ex, LogLevel.Trace, message, args.IsEmpty ? null : args.ToArray()); + } +#endif + + /// + /// Logs the specified message without an at the Trace level. + /// + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Trace([Localizable(false)] string message, params object?[] args) { Write(null, LogLevel.Trace, message, args); } @@ -110,7 +137,7 @@ public static void Trace(Func messageFunc) /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Trace(Exception ex, [Localizable(false)] string message, params object[] args) + public static void Trace(Exception? ex, [Localizable(false)] string message, params object?[] args) { Write(ex, LogLevel.Trace, message, args); } @@ -122,7 +149,7 @@ public static void Trace(Exception ex, [Localizable(false)] string message, para /// Message which may include positional parameters. /// Argument {0} to the message. [StringFormatMethod("message")] - public static void Trace([Localizable(false)] string message, TArgument1 arg0) + public static void Trace([Localizable(false)] string message, TArgument1? arg0) { if (IsTraceEnabled) Log(null, LogLevel.Trace, message, arg0); @@ -137,7 +164,7 @@ public static void Trace([Localizable(false)] string message, TArgum /// Argument {0} to the message. /// Argument {1} to the message. [StringFormatMethod("message")] - public static void Trace([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1) + public static void Trace([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1) { if (IsTraceEnabled) Log(null, LogLevel.Trace, message, arg0, arg1); @@ -154,7 +181,7 @@ public static void Trace([Localizable(false)] string mes /// Argument {1} to the message. /// Argument {2} to the message. [StringFormatMethod("message")] - public static void Trace([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1, TArgument3 arg2) + public static void Trace([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1, TArgument3? arg2) { if (IsTraceEnabled) Log(null, LogLevel.Trace, message, arg0, arg1, arg2); @@ -165,7 +192,7 @@ public static void Trace([Localizable(false) /// /// Exception to be logged. /// Log message. - public static void Trace(Exception ex, [Localizable(false)] string message) + public static void Trace(Exception? ex, [Localizable(false)] string message) { Write(ex, LogLevel.Trace, message, null); } @@ -178,19 +205,46 @@ public static void Trace(Exception ex, [Localizable(false)] string message) /// Function that returns the log message. [Obsolete("Avoid delegate capture allocations. Marked obsolete with v5.3")] [EditorBrowsable(EditorBrowsableState.Never)] - public static void Trace(Exception ex, Func messageFunc) + public static void Trace(Exception? ex, Func messageFunc) { if (IsTraceEnabled) Write(ex, LogLevel.Trace, messageFunc(), null); } +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Logs the specified message without an at the Debug level. + /// + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Debug([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (IsDebugEnabled) + Write(null, LogLevel.Debug, message, args.IsEmpty ? null : args.ToArray()); + } + + /// + /// Logs the specified message with an at the Debug level. + /// + /// Exception to be logged. + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Debug(Exception? ex, [Localizable(false)] string message, params ReadOnlySpan args) + { + if (IsDebugEnabled) + Write(ex, LogLevel.Debug, message, args.IsEmpty ? null : args.ToArray()); + } +#endif + /// /// Logs the specified message without an at the Debug level. /// /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Debug([Localizable(false)] string message, params object[] args) + public static void Debug([Localizable(false)] string message, params object?[] args) { Write(null, LogLevel.Debug, message, args); } @@ -224,7 +278,7 @@ public static void Debug(Func messageFunc) /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Debug(Exception ex, [Localizable(false)] string message, params object[] args) + public static void Debug(Exception? ex, [Localizable(false)] string message, params object?[] args) { Write(ex, LogLevel.Debug, message, args); } @@ -236,7 +290,7 @@ public static void Debug(Exception ex, [Localizable(false)] string message, para /// Message which may include positional parameters. /// Argument {0} to the message. [StringFormatMethod("message")] - public static void Debug([Localizable(false)] string message, TArgument1 arg0) + public static void Debug([Localizable(false)] string message, TArgument1? arg0) { if (IsDebugEnabled) Log(null, LogLevel.Debug, message, arg0); @@ -251,7 +305,7 @@ public static void Debug([Localizable(false)] string message, TArgum /// Argument {0} to the message. /// Argument {1} to the message. [StringFormatMethod("message")] - public static void Debug([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1) + public static void Debug([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1) { if (IsDebugEnabled) Log(null, LogLevel.Debug, message, arg0, arg1); @@ -268,7 +322,7 @@ public static void Debug([Localizable(false)] string mes /// Argument {1} to the message. /// Argument {2} to the message. [StringFormatMethod("message")] - public static void Debug([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1, TArgument3 arg2) + public static void Debug([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1, TArgument3? arg2) { if (IsDebugEnabled) Log(null, LogLevel.Debug, message, arg0, arg1, arg2); @@ -279,7 +333,7 @@ public static void Debug([Localizable(false) ///
/// Exception to be logged. /// Log message. - public static void Debug(Exception ex, [Localizable(false)] string message) + public static void Debug(Exception? ex, [Localizable(false)] string message) { Write(ex, LogLevel.Debug, message, null); } @@ -292,19 +346,46 @@ public static void Debug(Exception ex, [Localizable(false)] string message) /// Function that returns the log message. [Obsolete("Avoid delegate capture allocations. Marked obsolete with v5.3")] [EditorBrowsable(EditorBrowsableState.Never)] - public static void Debug(Exception ex, Func messageFunc) + public static void Debug(Exception? ex, Func messageFunc) { if (IsDebugEnabled) Write(ex, LogLevel.Debug, messageFunc(), null); } +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Logs the specified message without an at the Info level. + /// + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Info([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (IsInfoEnabled) + Write(null, LogLevel.Info, message, args.IsEmpty ? null : args.ToArray()); + } + + /// + /// Logs the specified message with an at the Info level. + /// + /// Exception to be logged. + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Info(Exception? ex, [Localizable(false)] string message, params ReadOnlySpan args) + { + if (IsInfoEnabled) + Write(ex, LogLevel.Info, message, args.IsEmpty ? null : args.ToArray()); + } +#endif + /// /// Logs the specified message without an at the Info level. /// /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Info([Localizable(false)] string message, params object[] args) + public static void Info([Localizable(false)] string message, params object?[] args) { Write(null, LogLevel.Info, message, args); } @@ -338,7 +419,7 @@ public static void Info(Func messageFunc) /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Info(Exception ex, [Localizable(false)] string message, params object[] args) + public static void Info(Exception? ex, [Localizable(false)] string message, params object?[] args) { Write(ex, LogLevel.Info, message, args); } @@ -350,7 +431,7 @@ public static void Info(Exception ex, [Localizable(false)] string message, param /// Message which may include positional parameters. /// Argument {0} to the message. [StringFormatMethod("message")] - public static void Info([Localizable(false)] string message, TArgument1 arg0) + public static void Info([Localizable(false)] string message, TArgument1? arg0) { if (IsInfoEnabled) Log(null, LogLevel.Info, message, arg0); @@ -365,7 +446,7 @@ public static void Info([Localizable(false)] string message, TArgume /// Argument {0} to the message. /// Argument {1} to the message. [StringFormatMethod("message")] - public static void Info([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1) + public static void Info([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1) { if (IsInfoEnabled) Log(null, LogLevel.Info, message, arg0, arg1); @@ -382,7 +463,7 @@ public static void Info([Localizable(false)] string mess /// Argument {1} to the message. /// Argument {2} to the message. [StringFormatMethod("message")] - public static void Info([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1, TArgument3 arg2) + public static void Info([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1, TArgument3? arg2) { if (IsInfoEnabled) Log(null, LogLevel.Info, message, arg0, arg1, arg2); @@ -393,7 +474,7 @@ public static void Info([Localizable(false)] ///
/// Exception to be logged. /// Log message. - public static void Info(Exception ex, [Localizable(false)] string message) + public static void Info(Exception? ex, [Localizable(false)] string message) { Write(ex, LogLevel.Info, message, null); } @@ -406,19 +487,46 @@ public static void Info(Exception ex, [Localizable(false)] string message) /// Function that returns the log message. [Obsolete("Avoid delegate capture allocations. Marked obsolete with v5.3")] [EditorBrowsable(EditorBrowsableState.Never)] - public static void Info(Exception ex, Func messageFunc) + public static void Info(Exception? ex, Func messageFunc) { if (IsInfoEnabled) Write(ex, LogLevel.Info, messageFunc(), null); } +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER /// /// Logs the specified message without an at the Warn level. /// /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Warn([Localizable(false)] string message, params object[] args) + public static void Warn([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (IsWarnEnabled) + Write(null, LogLevel.Warn, message, args.IsEmpty ? null : args.ToArray()); + } + + /// + /// Logs the specified message with an at the Warn level. + /// + /// Exception to be logged. + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Warn(Exception? ex, [Localizable(false)] string message, params ReadOnlySpan args) + { + if (IsWarnEnabled) + Write(ex, LogLevel.Warn, message, args.IsEmpty ? null : args.ToArray()); + } +#endif + + /// + /// Logs the specified message without an at the Warn level. + /// + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Warn([Localizable(false)] string message, params object?[] args) { Write(null, LogLevel.Warn, message, args); } @@ -452,7 +560,7 @@ public static void Warn(Func messageFunc) /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Warn(Exception ex, [Localizable(false)] string message, params object[] args) + public static void Warn(Exception? ex, [Localizable(false)] string message, params object?[] args) { Write(ex, LogLevel.Warn, message, args); } @@ -464,7 +572,7 @@ public static void Warn(Exception ex, [Localizable(false)] string message, param /// Message which may include positional parameters. /// Argument {0} to the message. [StringFormatMethod("message")] - public static void Warn([Localizable(false)] string message, TArgument1 arg0) + public static void Warn([Localizable(false)] string message, TArgument1? arg0) { if (IsWarnEnabled) Log(null, LogLevel.Warn, message, arg0); @@ -479,7 +587,7 @@ public static void Warn([Localizable(false)] string message, TArgume /// Argument {0} to the message. /// Argument {1} to the message. [StringFormatMethod("message")] - public static void Warn([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1) + public static void Warn([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1) { if (IsWarnEnabled) Log(null, LogLevel.Warn, message, arg0, arg1); @@ -496,7 +604,7 @@ public static void Warn([Localizable(false)] string mess /// Argument {1} to the message. /// Argument {2} to the message. [StringFormatMethod("message")] - public static void Warn([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1, TArgument3 arg2) + public static void Warn([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1, TArgument3? arg2) { if (IsWarnEnabled) Log(null, LogLevel.Warn, message, arg0, arg1, arg2); @@ -507,7 +615,7 @@ public static void Warn([Localizable(false)] ///
/// Exception to be logged. /// Log message. - public static void Warn(Exception ex, [Localizable(false)] string message) + public static void Warn(Exception? ex, [Localizable(false)] string message) { Write(ex, LogLevel.Warn, message, null); } @@ -520,19 +628,46 @@ public static void Warn(Exception ex, [Localizable(false)] string message) /// Function that returns the log message. [Obsolete("Avoid delegate capture allocations. Marked obsolete with v5.3")] [EditorBrowsable(EditorBrowsableState.Never)] - public static void Warn(Exception ex, Func messageFunc) + public static void Warn(Exception? ex, Func messageFunc) { if (IsWarnEnabled) Write(ex, LogLevel.Warn, messageFunc(), null); } +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER /// /// Logs the specified message without an at the Error level. /// /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Error([Localizable(false)] string message, params object[] args) + public static void Error([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (IsErrorEnabled) + Write(null, LogLevel.Error, message, args.IsEmpty ? null : args.ToArray()); + } + + /// + /// Logs the specified message with an at the Error level. + /// + /// Exception to be logged. + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Error(Exception? ex, [Localizable(false)] string message, params ReadOnlySpan args) + { + if (IsErrorEnabled) + Write(ex, LogLevel.Error, message, args.IsEmpty ? null : args.ToArray()); + } +#endif + + /// + /// Logs the specified message without an at the Error level. + /// + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Error([Localizable(false)] string message, params object?[] args) { Write(null, LogLevel.Error, message, args); } @@ -566,7 +701,7 @@ public static void Error(Func messageFunc) /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Error(Exception ex, [Localizable(false)] string message, params object[] args) + public static void Error(Exception? ex, [Localizable(false)] string message, params object?[] args) { Write(ex, LogLevel.Error, message, args); } @@ -578,7 +713,7 @@ public static void Error(Exception ex, [Localizable(false)] string message, para /// Message which may include positional parameters. /// Argument {0} to the message. [StringFormatMethod("message")] - public static void Error([Localizable(false)] string message, TArgument1 arg0) + public static void Error([Localizable(false)] string message, TArgument1? arg0) { if (IsErrorEnabled) Log(null, LogLevel.Error, message, arg0); @@ -593,7 +728,7 @@ public static void Error([Localizable(false)] string message, TArgum /// Argument {0} to the message. /// Argument {1} to the message. [StringFormatMethod("message")] - public static void Error([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1) + public static void Error([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1) { if (IsErrorEnabled) Log(null, LogLevel.Error, message, arg0, arg1); @@ -610,7 +745,7 @@ public static void Error([Localizable(false)] string mes /// Argument {1} to the message. /// Argument {2} to the message. [StringFormatMethod("message")] - public static void Error([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1, TArgument3 arg2) + public static void Error([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1, TArgument3? arg2) { if (IsErrorEnabled) Log(null, LogLevel.Error, message, arg0, arg1, arg2); @@ -621,7 +756,7 @@ public static void Error([Localizable(false) ///
/// Exception to be logged. /// Log message. - public static void Error(Exception ex, [Localizable(false)] string message) + public static void Error(Exception? ex, [Localizable(false)] string message) { Write(ex, LogLevel.Error, message, null); } @@ -634,19 +769,46 @@ public static void Error(Exception ex, [Localizable(false)] string message) /// Function that returns the log message. [Obsolete("Avoid delegate capture allocations. Marked obsolete with v5.3")] [EditorBrowsable(EditorBrowsableState.Never)] - public static void Error(Exception ex, Func messageFunc) + public static void Error(Exception? ex, Func messageFunc) { if (IsErrorEnabled) Write(ex, LogLevel.Error, messageFunc(), null); } +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER /// /// Logs the specified message without an at the Fatal level. /// /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Fatal([Localizable(false)] string message, params object[] args) + public static void Fatal([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (IsFatalEnabled) + Write(null, LogLevel.Fatal, message, args.IsEmpty ? null : args.ToArray()); + } + + /// + /// Logs the specified message with an at the Fatal level. + /// + /// Exception to be logged. + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Fatal(Exception? ex, [Localizable(false)] string message, params ReadOnlySpan args) + { + if (IsFatalEnabled) + Write(ex, LogLevel.Fatal, message, args.IsEmpty ? null : args.ToArray()); + } +#endif + + /// + /// Logs the specified message without an at the Fatal level. + /// + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Fatal([Localizable(false)] string message, params object?[] args) { Write(null, LogLevel.Fatal, message, args); } @@ -680,7 +842,7 @@ public static void Fatal(Func messageFunc) /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Fatal(Exception ex, [Localizable(false)] string message, params object[] args) + public static void Fatal(Exception? ex, [Localizable(false)] string message, params object?[] args) { Write(ex, LogLevel.Fatal, message, args); } @@ -692,7 +854,7 @@ public static void Fatal(Exception ex, [Localizable(false)] string message, para /// Message which may include positional parameters. /// Argument {0} to the message. [StringFormatMethod("message")] - public static void Fatal([Localizable(false)] string message, TArgument1 arg0) + public static void Fatal([Localizable(false)] string message, TArgument1? arg0) { if (IsFatalEnabled) Log(null, LogLevel.Fatal, message, arg0); @@ -707,7 +869,7 @@ public static void Fatal([Localizable(false)] string message, TArgum /// Argument {0} to the message. /// Argument {1} to the message. [StringFormatMethod("message")] - public static void Fatal([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1) + public static void Fatal([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1) { if (IsFatalEnabled) Log(null, LogLevel.Fatal, message, arg0, arg1); @@ -724,7 +886,7 @@ public static void Fatal([Localizable(false)] string mes /// Argument {1} to the message. /// Argument {2} to the message. [StringFormatMethod("message")] - public static void Fatal([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1, TArgument3 arg2) + public static void Fatal([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1, TArgument3? arg2) { if (IsFatalEnabled) Log(null, LogLevel.Fatal, message, arg0, arg1, arg2); @@ -735,7 +897,7 @@ public static void Fatal([Localizable(false) ///
/// Exception to be logged. /// Log message. - public static void Fatal(Exception ex, [Localizable(false)] string message) + public static void Fatal(Exception? ex, [Localizable(false)] string message) { Write(ex, LogLevel.Fatal, message, null); } @@ -748,11 +910,10 @@ public static void Fatal(Exception ex, [Localizable(false)] string message) /// Function that returns the log message. [Obsolete("Avoid delegate capture allocations. Marked obsolete with v5.3")] [EditorBrowsable(EditorBrowsableState.Never)] - public static void Fatal(Exception ex, Func messageFunc) + public static void Fatal(Exception? ex, Func messageFunc) { if (IsFatalEnabled) Write(ex, LogLevel.Fatal, messageFunc(), null); } - } } diff --git a/src/NLog/Common/InternalLogger-generated.tt b/src/NLog/Common/InternalLogger-generated.tt index e12cb907b8..4866b24b7c 100644 --- a/src/NLog/Common/InternalLogger-generated.tt +++ b/src/NLog/Common/InternalLogger-generated.tt @@ -33,9 +33,9 @@ namespace NLog.Common { - using JetBrains.Annotations; using System; using System.ComponentModel; + using JetBrains.Annotations; public static partial class InternalLogger { @@ -56,13 +56,40 @@ namespace NLog.Common { #> +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Logs the specified message without an at the <#=level#> level. + /// + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (Is<#=level#>Enabled) + Write(null, LogLevel.<#=level#>, message, args.IsEmpty ? null : args.ToArray()); + } + + /// + /// Logs the specified message with an at the <#=level#> level. + /// + /// Exception to be logged. + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void <#=level#>(Exception? ex, [Localizable(false)] string message, params ReadOnlySpan args) + { + if (Is<#=level#>Enabled) + Write(ex, LogLevel.<#=level#>, message, args.IsEmpty ? null : args.ToArray()); + } +#endif + /// /// Logs the specified message without an at the <#=level#> level. /// /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void <#=level#>([Localizable(false)] string message, params object[] args) + public static void <#=level#>([Localizable(false)] string message, params object?[] args) { Write(null, LogLevel.<#=level#>, message, args); } @@ -96,7 +123,7 @@ namespace NLog.Common /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void <#=level#>(Exception ex, [Localizable(false)] string message, params object[] args) + public static void <#=level#>(Exception? ex, [Localizable(false)] string message, params object?[] args) { Write(ex, LogLevel.<#=level#>, message, args); } @@ -108,7 +135,7 @@ namespace NLog.Common /// Message which may include positional parameters. /// Argument {0} to the message. [StringFormatMethod("message")] - public static void <#=level#>([Localizable(false)] string message, TArgument1 arg0) + public static void <#=level#>([Localizable(false)] string message, TArgument1? arg0) { if (Is<#=level#>Enabled) Log(null, LogLevel.<#=level#>, message, arg0); @@ -123,7 +150,7 @@ namespace NLog.Common /// Argument {0} to the message. /// Argument {1} to the message. [StringFormatMethod("message")] - public static void <#=level#>([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1) + public static void <#=level#>([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1) { if (Is<#=level#>Enabled) Log(null, LogLevel.<#=level#>, message, arg0, arg1); @@ -140,7 +167,7 @@ namespace NLog.Common /// Argument {1} to the message. /// Argument {2} to the message. [StringFormatMethod("message")] - public static void <#=level#>([Localizable(false)] string message, TArgument1 arg0, TArgument2 arg1, TArgument3 arg2) + public static void <#=level#>([Localizable(false)] string message, TArgument1? arg0, TArgument2? arg1, TArgument3? arg2) { if (Is<#=level#>Enabled) Log(null, LogLevel.<#=level#>, message, arg0, arg1, arg2); @@ -151,7 +178,7 @@ namespace NLog.Common ///
/// Exception to be logged. /// Log message. - public static void <#=level#>(Exception ex, [Localizable(false)] string message) + public static void <#=level#>(Exception? ex, [Localizable(false)] string message) { Write(ex, LogLevel.<#=level#>, message, null); } @@ -164,7 +191,7 @@ namespace NLog.Common /// Function that returns the log message. [Obsolete("Avoid delegate capture allocations. Marked obsolete with v5.3")] [EditorBrowsable(EditorBrowsableState.Never)] - public static void <#=level#>(Exception ex, Func messageFunc) + public static void <#=level#>(Exception? ex, Func messageFunc) { if (Is<#=level#>Enabled) Write(ex, LogLevel.<#=level#>, messageFunc(), null); @@ -173,4 +200,4 @@ namespace NLog.Common } #> } -} \ No newline at end of file +} diff --git a/src/NLog/Common/InternalLogger.cs b/src/NLog/Common/InternalLogger.cs index b93c934a37..7ae9c29f72 100644 --- a/src/NLog/Common/InternalLogger.cs +++ b/src/NLog/Common/InternalLogger.cs @@ -37,9 +37,9 @@ namespace NLog.Common using System.ComponentModel; using System.Globalization; using System.IO; - using System.Reflection; using JetBrains.Annotations; using NLog.Internal; + using NLog.Internal.Fakeables; using NLog.Time; /// @@ -47,9 +47,7 @@ namespace NLog.Common /// /// Writes to file, console or custom text writer (see ) /// - /// - /// Don't use as that can lead to recursive calls - stackoverflow - /// + /// Documentation on NLog Wiki public static partial class InternalLogger { private static readonly object LockObject = new object(); @@ -62,17 +60,11 @@ public static void Reset() ExceptionThrowWhenWriting = false; LogWriter = null; InternalEventOccurred = null; - -#pragma warning disable CS0618 // Type or member is obsolete - _logMessageReceived = null; - LogLevel = GetSetting("nlog.internalLogLevel", "NLOG_INTERNAL_LOG_LEVEL", LogLevel.Off); - IncludeTimestamp = GetSetting("nlog.internalLogIncludeTimestamp", "NLOG_INTERNAL_INCLUDE_TIMESTAMP", true); - LogToConsole = GetSetting("nlog.internalLogToConsole", "NLOG_INTERNAL_LOG_TO_CONSOLE", false); - LogToConsoleError = GetSetting("nlog.internalLogToConsoleError", "NLOG_INTERNAL_LOG_TO_CONSOLE_ERROR", false); - LogFile = GetSetting("nlog.internalLogFile", "NLOG_INTERNAL_LOG_FILE", string.Empty); - LogToTrace = GetSetting("nlog.internalLogToTrace", "NLOG_INTERNAL_LOG_TO_TRACE", false); -#pragma warning restore CS0618 // Type or member is obsolete - Info("NLog internal logger initialized."); + LogLevel = LogLevel.Off; + IncludeTimestamp = true; + LogToConsole = false; + LogToConsoleError = false; + LogFile = null; } /// @@ -122,32 +114,11 @@ public static bool LogToConsoleError } private static bool _logToConsoleError; - /// - /// Obsolete and replaced by with NLog v5.3. - /// Gets or sets a value indicating whether internal messages should be written to the .Trace - /// - [Obsolete("Instead use InternalLogger.LogWriter. Marked obsolete with NLog v5.3")] - public static bool LogToTrace - { - get => _logToTrace; - set - { - if (_logToTrace != value) - { - InternalEventOccurred -= LogToTraceSubscription; - if (value) - InternalEventOccurred += LogToTraceSubscription; - _logToTrace = value; - } - } - } - private static bool _logToTrace; - /// /// Gets or sets the file path of the internal log file. /// /// A value of value disables internal logging to a file. - public static string LogFile + public static string? LogFile { get { @@ -164,47 +135,20 @@ public static string LogFile _logFile = value; } - if (!string.IsNullOrEmpty(value)) + var logFile = (value != null && !string.IsNullOrEmpty(value)) ? ExpandFilePathVariables(value) : null; + _logFile = logFile; + if (logFile != null) { - _logFile = ExpandFilePathVariables(value); - CreateDirectoriesIfNeeded(_logFile); + CreateDirectoriesIfNeeded(logFile); } } } - private static string _logFile; + private static string? _logFile; /// /// Gets or sets the text writer that will receive internal logs. /// - public static TextWriter LogWriter { get; set; } - - /// - /// Obsolete and replaced by with NLog 5.3. - /// Event written to the internal log. - /// - /// - /// EventHandler will only be triggered for events, where severity matches the configured . - /// - /// Avoid using/calling NLog Logger-objects when handling these internal events, as it will lead to deadlock / stackoverflow. - /// - [Obsolete("Instead use InternalEventOccurred. Marked obsolete with NLog v5.3")] - public static event EventHandler LogMessageReceived - { - add - { - if (_logMessageReceived == null) - InternalEventOccurred += LogToMessageReceived; - _logMessageReceived += value; - } - remove - { - _logMessageReceived -= value; - if (_logMessageReceived == null) - InternalEventOccurred -= LogToMessageReceived; - } - } - [Obsolete("Instead use InternalEventOccurred. Marked obsolete with NLog v5.3")] - private static event EventHandler _logMessageReceived; + public static TextWriter? LogWriter { get; set; } /// /// Internal LogEvent written to the InternalLogger @@ -214,7 +158,7 @@ public static event EventHandler LogMessageRecei /// /// Never use/call NLog Logger-objects when handling these internal events, as it will lead to deadlock / stackoverflow. /// - public static event InternalEventOccurredHandler InternalEventOccurred; + public static event InternalEventOccurredHandler? InternalEventOccurred; /// /// Gets or sets a value indicating whether timestamp should be included in internal log output. @@ -233,11 +177,40 @@ public static event EventHandler LogMessageRecei /// Message which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Log(LogLevel level, [Localizable(false)] string message, params object[] args) + public static void Log(LogLevel level, [Localizable(false)] string message, params object?[] args) { Write(null, level, message, args); } +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Logs the specified message without an at the specified level. + /// + /// Log level. + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Log(LogLevel level, [Localizable(false)] string message, params ReadOnlySpan args) + { + if (IsLogLevelEnabled(level)) + Write(null, level, message, args.IsEmpty ? null : args.ToArray()); + } + + /// + /// Logs the specified message with an at the specified level. + /// + /// Exception to be logged. + /// Log level. + /// Message which may include positional parameters. + /// Arguments to the message. + [StringFormatMethod("message")] + public static void Log(Exception? ex, LogLevel level, [Localizable(false)] string message, params ReadOnlySpan args) + { + if (IsLogLevelEnabled(level)) + Write(ex, level, message, args.IsEmpty ? null : args.ToArray()); + } +#endif + /// /// Logs the specified message without an at the specified level. /// @@ -269,7 +242,7 @@ public static void Log(LogLevel level, [Localizable(false)] Func message /// Exception to be logged. /// Log level. /// Function that returns the log message. - public static void Log(Exception ex, LogLevel level, [Localizable(false)] Func messageFunc) + public static void Log(Exception? ex, LogLevel level, [Localizable(false)] Func messageFunc) { if (IsLogLevelEnabled(level)) { @@ -285,7 +258,7 @@ public static void Log(Exception ex, LogLevel level, [Localizable(false)] FuncMessage which may include positional parameters. /// Arguments to the message. [StringFormatMethod("message")] - public static void Log(Exception ex, LogLevel level, [Localizable(false)] string message, params object[] args) + public static void Log(Exception? ex, LogLevel level, [Localizable(false)] string message, params object?[] args) { Write(ex, level, message, args); } @@ -296,7 +269,7 @@ public static void Log(Exception ex, LogLevel level, [Localizable(false)] string /// Exception to be logged. /// Log level. /// Log message. - public static void Log(Exception ex, LogLevel level, [Localizable(false)] string message) + public static void Log(Exception? ex, LogLevel level, [Localizable(false)] string message) { Write(ex, level, message, null); } @@ -308,7 +281,7 @@ public static void Log(Exception ex, LogLevel level, [Localizable(false)] string /// level /// message /// optional args for - private static void Write([CanBeNull] Exception ex, LogLevel level, string message, [CanBeNull] object[] args) + private static void Write(Exception? ex, LogLevel level, string message, object?[]? args) { if (!IsLogLevelEnabled(level)) { @@ -360,7 +333,7 @@ private static void Write([CanBeNull] Exception ex, LogLevel level, string messa } } - private static void WriteToLog(LogLevel level, Exception ex, string fullMessage, IInternalLoggerContext loggerContext) + private static void WriteToLog(LogLevel level, Exception? ex, string fullMessage, IInternalLoggerContext? loggerContext) { if (LogWriter != null) { @@ -373,7 +346,7 @@ private static void WriteToLog(LogLevel level, Exception ex, string fullMessage, if (InternalEventOccurred != null) { - var loggerContextName = string.IsNullOrEmpty(loggerContext?.Name) ? loggerContext?.ToString() : loggerContext.Name; + var loggerContextName = (loggerContext is null || string.IsNullOrEmpty(loggerContext.Name)) ? loggerContext?.ToString() : loggerContext.Name; InternalEventOccurred?.Invoke(null, new InternalLogEventArgs(fullMessage, level, ex, loggerContext?.GetType(), loggerContextName)); } } @@ -381,7 +354,7 @@ private static void WriteToLog(LogLevel level, Exception ex, string fullMessage, /// /// Create log line with timestamp, exception message etc (if configured) /// - private static string CreateLogLine([CanBeNull] Exception ex, LogLevel level, string fullMessage) + private static string CreateLogLine(Exception? ex, LogLevel level, string fullMessage) { const string timeStampFormat = "yyyy-MM-dd HH:mm:ss.ffff"; const string fieldSeparator = " "; @@ -394,7 +367,7 @@ private static string CreateLogLine([CanBeNull] Exception ex, LogLevel level, st level.ToString(), fieldSeparator, fullMessage, - ex != null ? " Exception: " : "", + ex != null ? " Exception: " : string.Empty, ex?.ToString() ?? ""); } else @@ -403,8 +376,8 @@ private static string CreateLogLine([CanBeNull] Exception ex, LogLevel level, st level.ToString(), fieldSeparator, fullMessage, - ex != null ? " Exception: " : "", - ex?.ToString() ?? ""); + ex != null ? " Exception: " : string.Empty, + ex?.ToString() ?? string.Empty); } } @@ -412,8 +385,8 @@ private static string CreateLogLine([CanBeNull] Exception ex, LogLevel level, st /// Determine if logging should be avoided because of exception type. /// /// The exception to check. - /// true if logging should be avoided; otherwise, false. - private static bool IsSeriousException(Exception exception) + /// if logging should be avoided; otherwise, . + private static bool IsSeriousException(Exception? exception) { return exception != null && exception.MustBeRethrownImmediately(); } @@ -422,7 +395,7 @@ private static bool IsSeriousException(Exception exception) /// Determine if logging is enabled for given LogLevel /// /// The for the log event. - /// true if logging is enabled; otherwise, false. + /// if logging is enabled; otherwise, . private static bool IsLogLevelEnabled(LogLevel logLevel) { return !ReferenceEquals(_logLevel, LogLevel.Off) && _logLevel.CompareTo(logLevel) <= 0; @@ -431,7 +404,7 @@ private static bool IsLogLevelEnabled(LogLevel logLevel) /// /// Determine if logging is enabled. /// - /// true if logging is enabled; otherwise, false. + /// if logging is enabled; otherwise, . internal static bool HasActiveLoggers() { if (InternalEventOccurred is null && LogWriter is null) @@ -440,135 +413,6 @@ internal static bool HasActiveLoggers() return true; } - /// - /// Logs the assembly version and file version of the given Assembly. - /// - /// The assembly to log. - [Obsolete("InternalLogger should be minimal. Marked obsolete with NLog v5.3")] - public static void LogAssemblyVersion(Assembly assembly) - { - try - { - var fileVersion = assembly.GetFirstCustomAttribute()?.Version; - var productVersion = assembly.GetFirstCustomAttribute()?.InformationalVersion; - var globalAssemblyCache = false; -#if !NETSTANDARD - if (assembly.GlobalAssemblyCache) - globalAssemblyCache = true; -#endif - Info("{0}. File version: {1}. Product version: {2}. GlobalAssemblyCache: {3}", - assembly.FullName, - fileVersion, - productVersion, - globalAssemblyCache); - } - catch (Exception ex) - { - Error(ex, "Error logging version of assembly {0}.", assembly?.FullName); - } - } - - [Obsolete("InternalLogger should be minimal. Marked obsolete with NLog v5.3")] - private static string GetAppSettings(string configName) - { -#if NETFRAMEWORK - try - { - return System.Configuration.ConfigurationManager.AppSettings[configName]; - } - catch (Exception ex) - { - if (ex.MustBeRethrownImmediately()) - { - throw; - } - } -#endif - return null; - } - - [Obsolete("InternalLogger should be minimal. Marked obsolete with NLog v5.3")] - private static string GetSettingString(string configName, string envName) - { - try - { - string settingValue = GetAppSettings(configName); - if (settingValue != null) - return settingValue; - } - catch (Exception ex) - { - if (ex.MustBeRethrownImmediately()) - { - throw; - } - } - - try - { - string settingValue = EnvironmentHelper.GetSafeEnvironmentVariable(envName); - if (!string.IsNullOrEmpty(settingValue)) - return settingValue; - } - catch (Exception ex) - { - if (ex.MustBeRethrownImmediately()) - { - throw; - } - } - - return null; - } - - [Obsolete("InternalLogger should be minimal. Marked obsolete with NLog v5.3")] - private static LogLevel GetSetting(string configName, string envName, LogLevel defaultValue) - { - string value = GetSettingString(configName, envName); - if (value is null) - { - return defaultValue; - } - - try - { - return LogLevel.FromString(value); - } - catch (Exception exception) - { - if (exception.MustBeRethrownImmediately()) - { - throw; - } - - return defaultValue; - } - } - - [Obsolete("InternalLogger should be minimal. Marked obsolete with NLog v5.3")] - private static T GetSetting(string configName, string envName, T defaultValue) - { - string value = GetSettingString(configName, envName); - if (value is null) - { - return defaultValue; - } - - try - { - return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture); - } - catch (Exception exception) - { - if (exception.MustBeRethrownImmediately()) - { - throw; - } - - return defaultValue; - } - } - private static void CreateDirectoriesIfNeeded(string filename) { try @@ -599,26 +443,26 @@ private static string ExpandFilePathVariables(string internalLogFile) { try { - if (ContainsSubStringIgnoreCase(internalLogFile, "${currentdir}", out string currentDirToken)) + if (ContainsSubStringIgnoreCase(internalLogFile, "${currentdir}", out var currentDirToken)) internalLogFile = internalLogFile.Replace(currentDirToken, System.IO.Directory.GetCurrentDirectory() + System.IO.Path.DirectorySeparatorChar.ToString()); - if (ContainsSubStringIgnoreCase(internalLogFile, "${basedir}", out string baseDirToken)) + if (ContainsSubStringIgnoreCase(internalLogFile, "${basedir}", out var baseDirToken)) internalLogFile = internalLogFile.Replace(baseDirToken, LogManager.LogFactory.CurrentAppEnvironment.AppDomainBaseDirectory + System.IO.Path.DirectorySeparatorChar.ToString()); - if (ContainsSubStringIgnoreCase(internalLogFile, "${tempdir}", out string tempDirToken)) + if (ContainsSubStringIgnoreCase(internalLogFile, "${tempdir}", out var tempDirToken)) internalLogFile = internalLogFile.Replace(tempDirToken, LogManager.LogFactory.CurrentAppEnvironment.UserTempFilePath + System.IO.Path.DirectorySeparatorChar.ToString()); -#if !NETSTANDARD1_3 - if (ContainsSubStringIgnoreCase(internalLogFile, "${processdir}", out string processDirToken)) + if (ContainsSubStringIgnoreCase(internalLogFile, "${processdir}", out var processDirToken)) internalLogFile = internalLogFile.Replace(processDirToken, System.IO.Path.GetDirectoryName(LogManager.LogFactory.CurrentAppEnvironment.CurrentProcessFilePath) + System.IO.Path.DirectorySeparatorChar.ToString()); -#endif -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - if (ContainsSubStringIgnoreCase(internalLogFile, "${commonApplicationDataDir}", out string commonAppDataDirToken)) + if (ContainsSubStringIgnoreCase(internalLogFile, "${commonApplicationDataDir}", out var commonAppDataDirToken)) internalLogFile = internalLogFile.Replace(commonAppDataDirToken, NLog.LayoutRenderers.SpecialFolderLayoutRenderer.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + System.IO.Path.DirectorySeparatorChar.ToString()); - if (ContainsSubStringIgnoreCase(internalLogFile, "${userApplicationDataDir}", out string appDataDirToken)) + if (ContainsSubStringIgnoreCase(internalLogFile, "${userApplicationDataDir}", out var appDataDirToken)) internalLogFile = internalLogFile.Replace(appDataDirToken, NLog.LayoutRenderers.SpecialFolderLayoutRenderer.GetFolderPath(Environment.SpecialFolder.ApplicationData) + System.IO.Path.DirectorySeparatorChar.ToString()); - if (ContainsSubStringIgnoreCase(internalLogFile, "${userLocalApplicationDataDir}", out string localapplicationdatadir)) + if (ContainsSubStringIgnoreCase(internalLogFile, "${userLocalApplicationDataDir}", out var localapplicationdatadir)) internalLogFile = internalLogFile.Replace(localapplicationdatadir, NLog.LayoutRenderers.SpecialFolderLayoutRenderer.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + System.IO.Path.DirectorySeparatorChar.ToString()); -#endif if (internalLogFile.IndexOf('%') >= 0) internalLogFile = Environment.ExpandEnvironmentVariables(internalLogFile); + + if (!string.IsNullOrEmpty(internalLogFile) && internalLogFile.IndexOf('.') >= 0) + internalLogFile = AppEnvironmentWrapper.FixFilePathWithLongUNC(internalLogFile); + return internalLogFile; } catch @@ -627,39 +471,26 @@ private static string ExpandFilePathVariables(string internalLogFile) } } - private static bool ContainsSubStringIgnoreCase(string haystack, string needle, out string result) + private static bool ContainsSubStringIgnoreCase(string haystack, string needle, out string? result) { int needlePos = haystack.IndexOf(needle, StringComparison.OrdinalIgnoreCase); result = needlePos >= 0 ? haystack.Substring(needlePos, needle.Length) : null; return result != null; } - private static void LogToConsoleSubscription(object sender, InternalLogEventArgs eventArgs) + private static void LogToConsoleSubscription(object? sender, InternalLogEventArgs eventArgs) { -#if !NETSTANDARD1_3 var logLine = CreateLogLine(eventArgs.Exception, eventArgs.Level, eventArgs.Message); NLog.Targets.ConsoleTargetHelper.WriteLineThreadSafe(Console.Out, logLine); -#endif } - private static void LogToConsoleErrorSubscription(object sender, InternalLogEventArgs eventArgs) + private static void LogToConsoleErrorSubscription(object? sender, InternalLogEventArgs eventArgs) { -#if !NETSTANDARD1_3 var logLine = CreateLogLine(eventArgs.Exception, eventArgs.Level, eventArgs.Message); NLog.Targets.ConsoleTargetHelper.WriteLineThreadSafe(Console.Error, logLine); -#endif } - [Obsolete("Instead use InternalLogger.LogWriter. Marked obsolete with NLog v5.3")] - private static void LogToTraceSubscription(object sender, InternalLogEventArgs eventArgs) - { -#if !NETSTANDARD1_3 - var logLine = CreateLogLine(eventArgs.Exception, eventArgs.Level, eventArgs.Message); - System.Diagnostics.Trace.WriteLine(logLine, "NLog"); -#endif - } - - private static void LogToFileSubscription(object sender, InternalLogEventArgs eventArgs) + private static void LogToFileSubscription(object? sender, InternalLogEventArgs eventArgs) { var logLine = CreateLogLine(eventArgs.Exception, eventArgs.Level, eventArgs.Message); lock (LockObject) @@ -677,11 +508,5 @@ private static void LogToFileSubscription(object sender, InternalLogEventArgs ev } } } - - [Obsolete("Instead use InternalEventOccurred and InternalLogEventArgs. Marked obsolete with NLog v5.3")] - private static void LogToMessageReceived(object sender, InternalLogEventArgs eventArgs) - { - _logMessageReceived?.Invoke(null, new InternalLoggerMessageEventArgs(eventArgs.Message, eventArgs.Level, eventArgs.Exception, eventArgs.SenderType, eventArgs.SenderName)); - } } } diff --git a/src/NLog/Common/InternalLoggerMessageEventArgs.cs b/src/NLog/Common/InternalLoggerMessageEventArgs.cs deleted file mode 100644 index 7ad59db851..0000000000 --- a/src/NLog/Common/InternalLoggerMessageEventArgs.cs +++ /dev/null @@ -1,86 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.Common -{ - using System; - using System.ComponentModel; - using JetBrains.Annotations; - - /// - /// A message has been written to the internal logger - /// - [Obsolete("Instead use InternalEventOccurred and InternalLogEventArgs. Marked obsolete with NLog v5.3")] - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class InternalLoggerMessageEventArgs : EventArgs - { - /// - /// The rendered message - /// - public string Message { get; } - - /// - /// The log level - /// - public LogLevel Level { get; } - - /// - /// The exception. Could be null. - /// - [CanBeNull] - public Exception Exception { get; } - - /// - /// The type that triggered this internal log event, for example the FileTarget. - /// This property is not always populated. - /// - [CanBeNull] - public Type SenderType { get; } - - /// - /// The context name that triggered this internal log event, for example the name of the Target. - /// This property is not always populated. - /// - [CanBeNull] - public string SenderName { get; } - - internal InternalLoggerMessageEventArgs(string message, LogLevel level, [CanBeNull] Exception exception, [CanBeNull] Type senderType, [CanBeNull] string senderName) - { - Message = message; - Level = level; - Exception = exception; - SenderType = senderType; - SenderName = senderName; - } - } -} diff --git a/src/NLog/Conditions/ConditionAndExpression.cs b/src/NLog/Conditions/ConditionAndExpression.cs index e4730507ea..379364ab35 100644 --- a/src/NLog/Conditions/ConditionAndExpression.cs +++ b/src/NLog/Conditions/ConditionAndExpression.cs @@ -75,17 +75,13 @@ public override string ToString() /// The value of the conjunction operator. protected override object EvaluateNode(LogEventInfo context) { - var bval1 = (bool)Left.Evaluate(context); - if (!bval1) - { + var leftValue = Left.Evaluate(context) ?? BoxedFalse; + if (!(bool)leftValue) return BoxedFalse; - } - var bval2 = (bool)Right.Evaluate(context); - if (!bval2) - { + var rightValue = Right.Evaluate(context) ?? BoxedFalse; + if (!(bool)rightValue) return BoxedFalse; - } return BoxedTrue; } diff --git a/src/NLog/Conditions/ConditionEvaluationException.cs b/src/NLog/Conditions/ConditionEvaluationException.cs index aa387fb1c4..1ba94d8091 100644 --- a/src/NLog/Conditions/ConditionEvaluationException.cs +++ b/src/NLog/Conditions/ConditionEvaluationException.cs @@ -64,7 +64,7 @@ public ConditionEvaluationException(string message) /// /// The message. /// The inner exception. - public ConditionEvaluationException(string message, Exception innerException) + public ConditionEvaluationException(string message, Exception? innerException) : base(message, innerException) { } diff --git a/src/NLog/Conditions/ConditionExceptionExpression.cs b/src/NLog/Conditions/ConditionExceptionExpression.cs index bca71e85ea..a261065e05 100644 --- a/src/NLog/Conditions/ConditionExceptionExpression.cs +++ b/src/NLog/Conditions/ConditionExceptionExpression.cs @@ -49,7 +49,7 @@ public override string ToString() ///
/// Evaluation context. /// The object. - protected override object EvaluateNode(LogEventInfo context) + protected override object? EvaluateNode(LogEventInfo context) { return context.Exception; } diff --git a/src/NLog/Conditions/ConditionExpression.cs b/src/NLog/Conditions/ConditionExpression.cs index 2a2faebfb0..02ea237a63 100644 --- a/src/NLog/Conditions/ConditionExpression.cs +++ b/src/NLog/Conditions/ConditionExpression.cs @@ -53,8 +53,9 @@ public abstract class ConditionExpression ///
/// Condition text to be converted. /// Condition expression tree. - public static implicit operator ConditionExpression(string conditionExpressionText) + public static implicit operator ConditionExpression?(string? conditionExpressionText) { + if (conditionExpressionText is null) return null; return ConditionParser.ParseExpression(conditionExpressionText); } @@ -63,7 +64,7 @@ public static implicit operator ConditionExpression(string conditionExpressionTe ///
/// Evaluation context. /// Expression result. - public object Evaluate(LogEventInfo context) + public object? Evaluate(LogEventInfo context) { try { @@ -92,6 +93,6 @@ public object Evaluate(LogEventInfo context) ///
/// Evaluation context. /// Expression result. - protected abstract object EvaluateNode(LogEventInfo context); + protected abstract object? EvaluateNode(LogEventInfo context); } } diff --git a/src/NLog/Conditions/ConditionLayoutExpression.cs b/src/NLog/Conditions/ConditionLayoutExpression.cs index accbddcce6..d5fcd55bd1 100644 --- a/src/NLog/Conditions/ConditionLayoutExpression.cs +++ b/src/NLog/Conditions/ConditionLayoutExpression.cs @@ -34,8 +34,8 @@ namespace NLog.Conditions { using System.Text; - using Layouts; using NLog.Internal; + using NLog.Layouts; /// /// Condition layout expression (represented by a string literal @@ -44,7 +44,7 @@ namespace NLog.Conditions internal sealed class ConditionLayoutExpression : ConditionExpression { private readonly SimpleLayout _simpleLayout; - private StringBuilder _fastObjectPool; + private StringBuilder? _fastObjectPool; /// /// Initializes a new instance of the class. diff --git a/src/NLog/Conditions/ConditionLiteralExpression.cs b/src/NLog/Conditions/ConditionLiteralExpression.cs index b4c19c20bb..c93a6d1813 100644 --- a/src/NLog/Conditions/ConditionLiteralExpression.cs +++ b/src/NLog/Conditions/ConditionLiteralExpression.cs @@ -41,11 +41,15 @@ namespace NLog.Conditions /// internal sealed class ConditionLiteralExpression : ConditionExpression { + public static readonly ConditionLiteralExpression Null = new ConditionLiteralExpression(null); + public static readonly ConditionLiteralExpression True = new ConditionLiteralExpression(BoxedTrue); + public static readonly ConditionLiteralExpression False = new ConditionLiteralExpression(BoxedFalse); + /// /// Initializes a new instance of the class. /// /// Literal value. - public ConditionLiteralExpression(object literalValue) + public ConditionLiteralExpression(object? literalValue) { LiteralValue = literalValue; } @@ -54,7 +58,7 @@ public ConditionLiteralExpression(object literalValue) /// Gets the literal value. /// /// The literal value. - public object LiteralValue { get; } + public object? LiteralValue { get; } /// public override string ToString() @@ -68,6 +72,7 @@ public override string ToString() { return $"'{stringValue}'"; } + if (LiteralValue is char charValue) { return $"'{charValue}'"; @@ -81,7 +86,7 @@ public override string ToString() ///
/// Evaluation context. Ignored. /// The literal value as passed in the constructor. - protected override object EvaluateNode(LogEventInfo context) + protected override object? EvaluateNode(LogEventInfo context) { return LiteralValue; } diff --git a/src/NLog/Conditions/ConditionMethodAttribute.cs b/src/NLog/Conditions/ConditionMethodAttribute.cs index bc30c267b6..c95d96028c 100644 --- a/src/NLog/Conditions/ConditionMethodAttribute.cs +++ b/src/NLog/Conditions/ConditionMethodAttribute.cs @@ -34,7 +34,7 @@ namespace NLog.Conditions { using System; - using Config; + using NLog.Config; /// /// Marks class as a log event Condition and assigns a name to it. diff --git a/src/NLog/Conditions/ConditionMethodExpression.cs b/src/NLog/Conditions/ConditionMethodExpression.cs index 252fa26fb9..308103cf0e 100644 --- a/src/NLog/Conditions/ConditionMethodExpression.cs +++ b/src/NLog/Conditions/ConditionMethodExpression.cs @@ -57,7 +57,7 @@ private ConditionMethodExpression(string methodName, IList } /// - protected override object EvaluateNode(LogEventInfo context) + protected override object? EvaluateNode(LogEventInfo context) { return _method.EvaluateNode(context); } @@ -82,25 +82,25 @@ public override string ToString() return sb.ToString(); } - public static ConditionMethodExpression CreateMethodNoParameters(string conditionMethodName, Func method) + public static ConditionMethodExpression CreateMethodNoParameters(string conditionMethodName, Func method) { return new ConditionMethodExpression(conditionMethodName, ArrayHelper.Empty(), new EvaluateMethodNoParameters(method)); } - public static ConditionMethodExpression CreateMethodOneParameter(string conditionMethodName, Func method, IList methodParameters) + public static ConditionMethodExpression CreateMethodOneParameter(string conditionMethodName, Func method, IList methodParameters) { var methodParameter = methodParameters[0]; return new ConditionMethodExpression(conditionMethodName, methodParameters, new EvaluateMethodOneParameter(method, (logEvent) => methodParameter.Evaluate(logEvent))); } - public static ConditionMethodExpression CreateMethodTwoParameters(string conditionMethodName, Func method, IList methodParameters) + public static ConditionMethodExpression CreateMethodTwoParameters(string conditionMethodName, Func method, IList methodParameters) { var methodParameterArg1 = methodParameters[0]; var methodParameterArg2 = methodParameters[1]; return new ConditionMethodExpression(conditionMethodName, methodParameters, new EvaluateMethodTwoParameters(method, (logEvent) => methodParameterArg1.Evaluate(logEvent), (logEvent) => methodParameterArg2.Evaluate(logEvent))); } - public static ConditionMethodExpression CreateMethodThreeParameters(string conditionMethodName, Func method, IList methodParameters) + public static ConditionMethodExpression CreateMethodThreeParameters(string conditionMethodName, Func method, IList methodParameters) { var methodParameterArg1 = methodParameters[0]; var methodParameterArg2 = methodParameters[1]; @@ -108,26 +108,26 @@ public static ConditionMethodExpression CreateMethodThreeParameters(string condi return new ConditionMethodExpression(conditionMethodName, methodParameters, new EvaluateMethodThreeParameters(method, (logEvent) => methodParameterArg1.Evaluate(logEvent), (logEvent) => methodParameterArg2.Evaluate(logEvent), (logEvent) => methodParameterArg3.Evaluate(logEvent))); } - public static ConditionMethodExpression CreateMethodManyParameters(string conditionMethodName, Func method, IList methodParameters, bool includeLogEvent) + public static ConditionMethodExpression CreateMethodManyParameters(string conditionMethodName, Func method, IList methodParameters, bool includeLogEvent) { return new ConditionMethodExpression(conditionMethodName, methodParameters, new EvaluateMethodManyParameters(method, methodParameters, includeLogEvent)); } private interface IEvaluateMethod { - object EvaluateNode(LogEventInfo logEvent); + object? EvaluateNode(LogEventInfo logEvent); } private sealed class EvaluateMethodNoParameters : IEvaluateMethod { - private readonly Func _method; + private readonly Func _method; - public EvaluateMethodNoParameters(Func method) + public EvaluateMethodNoParameters(Func method) { _method = Guard.ThrowIfNull(method); } - public object EvaluateNode(LogEventInfo logEvent) + public object? EvaluateNode(LogEventInfo logEvent) { return _method(logEvent); } @@ -135,16 +135,16 @@ public object EvaluateNode(LogEventInfo logEvent) private sealed class EvaluateMethodOneParameter : IEvaluateMethod { - private readonly Func _method; - private readonly Func _methodParameter; + private readonly Func _method; + private readonly Func _methodParameter; - public EvaluateMethodOneParameter(Func method, Func methodParameter) + public EvaluateMethodOneParameter(Func method, Func methodParameter) { _method = Guard.ThrowIfNull(method); _methodParameter = Guard.ThrowIfNull(methodParameter); } - public object EvaluateNode(LogEventInfo logEvent) + public object? EvaluateNode(LogEventInfo logEvent) { var inputParameter = _methodParameter(logEvent); return _method(logEvent, inputParameter); @@ -153,18 +153,18 @@ public object EvaluateNode(LogEventInfo logEvent) private sealed class EvaluateMethodTwoParameters : IEvaluateMethod { - private readonly Func _method; - private readonly Func _methodParameterArg1; - private readonly Func _methodParameterArg2; + private readonly Func _method; + private readonly Func _methodParameterArg1; + private readonly Func _methodParameterArg2; - public EvaluateMethodTwoParameters(Func method, Func methodParameterArg1, Func methodParameterArg2) + public EvaluateMethodTwoParameters(Func method, Func methodParameterArg1, Func methodParameterArg2) { _method = Guard.ThrowIfNull(method); _methodParameterArg1 = Guard.ThrowIfNull(methodParameterArg1); _methodParameterArg2 = Guard.ThrowIfNull(methodParameterArg2); } - public object EvaluateNode(LogEventInfo logEvent) + public object? EvaluateNode(LogEventInfo logEvent) { var inputParameter1 = _methodParameterArg1(logEvent); var inputParameter2 = _methodParameterArg2(logEvent); @@ -174,12 +174,12 @@ public object EvaluateNode(LogEventInfo logEvent) private sealed class EvaluateMethodThreeParameters : IEvaluateMethod { - private readonly Func _method; - private readonly Func _methodParameterArg1; - private readonly Func _methodParameterArg2; - private readonly Func _methodParameterArg3; + private readonly Func _method; + private readonly Func _methodParameterArg1; + private readonly Func _methodParameterArg2; + private readonly Func _methodParameterArg3; - public EvaluateMethodThreeParameters(Func method, Func methodParameterArg1, Func methodParameterArg2, Func methodParameterArg3) + public EvaluateMethodThreeParameters(Func method, Func methodParameterArg1, Func methodParameterArg2, Func methodParameterArg3) { _method = Guard.ThrowIfNull(method); _methodParameterArg1 = Guard.ThrowIfNull(methodParameterArg1); @@ -187,7 +187,7 @@ public EvaluateMethodThreeParameters(Func _method; + private readonly Func _method; private readonly IList _methodParameters; private readonly bool _includeLogEvent; - public EvaluateMethodManyParameters(Func method, IList inputParameters, bool includeLogEvent) + public EvaluateMethodManyParameters(Func method, IList inputParameters, bool includeLogEvent) { _method = Guard.ThrowIfNull(method); _methodParameters = Guard.ThrowIfNull(inputParameters); _includeLogEvent = includeLogEvent; } - public object EvaluateNode(LogEventInfo logEvent) + public object? EvaluateNode(LogEventInfo logEvent) { var parameterIndex = _includeLogEvent ? 1 : 0; - var inputParameters = new object[_methodParameters.Count + parameterIndex]; + var inputParameters = new object?[_methodParameters.Count + parameterIndex]; if (_includeLogEvent) inputParameters[0] = logEvent; for (int i = 0; i < _methodParameters.Count; ++i) diff --git a/src/NLog/Conditions/ConditionMethods.cs b/src/NLog/Conditions/ConditionMethods.cs index fe9c12c310..846086739f 100644 --- a/src/NLog/Conditions/ConditionMethods.cs +++ b/src/NLog/Conditions/ConditionMethods.cs @@ -35,7 +35,6 @@ namespace NLog.Conditions { using System; using System.Runtime.InteropServices; - using System.Text.RegularExpressions; /// /// A bunch of utility methods (mostly predicates) which can be used in @@ -51,7 +50,7 @@ public static class ConditionMethods /// The second value. /// true when two objects are equal, false otherwise. [ConditionMethod("equals")] - public static bool Equals2(object firstValue, object secondValue) + public static bool Equals2(object? firstValue, object? secondValue) { return ReferenceEquals(firstValue, secondValue) || firstValue?.Equals(secondValue) == true; } @@ -64,7 +63,7 @@ public static bool Equals2(object firstValue, object secondValue) /// Optional. If true, case is ignored; if false (default), case is significant. /// true when two strings are equal, false otherwise. [ConditionMethod("strequals")] - public static bool Equals2(string firstValue, string secondValue, [Optional, DefaultParameterValue(false)] bool ignoreCase) + public static bool Equals2(string? firstValue, string? secondValue, [Optional, DefaultParameterValue(false)] bool ignoreCase) { return string.Equals(firstValue, secondValue, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); } @@ -77,7 +76,7 @@ public static bool Equals2(string firstValue, string secondValue, [Optional, Def /// Optional. If true (default), case is ignored; if false, case is significant. /// true when the second string is a substring of the first string, false otherwise. [ConditionMethod("contains")] - public static bool Contains(string haystack, string needle, [Optional, DefaultParameterValue(true)] bool ignoreCase) + public static bool Contains(string? haystack, string? needle, [Optional, DefaultParameterValue(true)] bool ignoreCase) { return haystack?.IndexOf(needle, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) >= 0; } @@ -90,7 +89,7 @@ public static bool Contains(string haystack, string needle, [Optional, DefaultPa /// Optional. If true (default), case is ignored; if false, case is significant. /// true when the second string is a prefix of the first string, false otherwise. [ConditionMethod("starts-with")] - public static bool StartsWith(string haystack, string needle, [Optional, DefaultParameterValue(true)] bool ignoreCase) + public static bool StartsWith(string? haystack, string? needle, [Optional, DefaultParameterValue(true)] bool ignoreCase) { return haystack?.StartsWith(needle, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == true; } @@ -103,7 +102,7 @@ public static bool StartsWith(string haystack, string needle, [Optional, Default /// Optional. If true (default), case is ignored; if false, case is significant. /// true when the second string is a prefix of the first string, false otherwise. [ConditionMethod("ends-with")] - public static bool EndsWith(string haystack, string needle, [Optional, DefaultParameterValue(true)] bool ignoreCase) + public static bool EndsWith(string? haystack, string? needle, [Optional, DefaultParameterValue(true)] bool ignoreCase) { return haystack?.EndsWith(needle, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == true; } @@ -114,38 +113,9 @@ public static bool EndsWith(string haystack, string needle, [Optional, DefaultPa /// A string whose lengths is to be evaluated. /// The length of the string. [ConditionMethod("length")] - public static int Length(string text) + public static int Length(string? text) { return text?.Length ?? 0; } - - /// - /// Indicates whether the specified regular expression finds a match in the specified input string. - /// - /// The string to search for a match. - /// The regular expression pattern to match. - /// A string consisting of the desired options for the test. The possible values are those of the separated by commas. - /// true if the regular expression finds a match; otherwise, false. - [ConditionMethod("regex-matches")] - public static bool RegexMatches(string input, string pattern, [Optional, DefaultParameterValue("")] string options) - { - RegexOptions regexOpts = ParseRegexOptions(options) | RegexOptions.ExplicitCapture; - return Regex.IsMatch(input, pattern, regexOpts); - } - - /// - /// - /// - /// - /// - private static RegexOptions ParseRegexOptions(string options) - { - if (string.IsNullOrEmpty(options)) - { - return RegexOptions.None; - } - - return (RegexOptions)Enum.Parse(typeof(RegexOptions), options, true); - } } } diff --git a/src/NLog/Conditions/ConditionNotExpression.cs b/src/NLog/Conditions/ConditionNotExpression.cs index 136f08769c..9a7bd21cf3 100644 --- a/src/NLog/Conditions/ConditionNotExpression.cs +++ b/src/NLog/Conditions/ConditionNotExpression.cs @@ -62,7 +62,8 @@ public override string ToString() /// protected override object EvaluateNode(LogEventInfo context) { - return (bool)Expression.Evaluate(context) ? BoxedFalse : BoxedTrue; + var objectValue = Expression.Evaluate(context) ?? BoxedFalse; + return (bool)objectValue ? BoxedFalse : BoxedTrue; } } } diff --git a/src/NLog/Conditions/ConditionOrExpression.cs b/src/NLog/Conditions/ConditionOrExpression.cs index 9bc658c791..4fbc4261f0 100644 --- a/src/NLog/Conditions/ConditionOrExpression.cs +++ b/src/NLog/Conditions/ConditionOrExpression.cs @@ -74,17 +74,13 @@ public override string ToString() /// The value of the alternative operator. protected override object EvaluateNode(LogEventInfo context) { - var bval1 = (bool)LeftExpression.Evaluate(context); - if (bval1) - { + var leftValue = LeftExpression.Evaluate(context) ?? BoxedFalse; + if ((bool)leftValue) return BoxedTrue; - } - var bval2 = (bool)RightExpression.Evaluate(context); - if (bval2) - { + var rightValue = RightExpression.Evaluate(context) ?? BoxedFalse; + if ((bool)rightValue) return BoxedTrue; - } return BoxedFalse; } diff --git a/src/NLog/Conditions/ConditionParser.cs b/src/NLog/Conditions/ConditionParser.cs index a1571f3676..50f23b3682 100644 --- a/src/NLog/Conditions/ConditionParser.cs +++ b/src/NLog/Conditions/ConditionParser.cs @@ -69,6 +69,7 @@ private ConditionParser(SimpleStringReader stringReader, ConfigurationItemFactor /// The root of the expression syntax tree which can be used to get the value of the condition in a specified context. public static ConditionExpression ParseExpression(string expressionText) { + Guard.ThrowIfNull(expressionText); return ParseExpression(expressionText, ConfigurationItemFactory.Default); } @@ -81,10 +82,7 @@ public static ConditionExpression ParseExpression(string expressionText) /// The root of the expression syntax tree which can be used to get the value of the condition in a specified context. public static ConditionExpression ParseExpression(string expressionText, ConfigurationItemFactory configurationItemFactories) { - if (expressionText is null) - { - return null; - } + Guard.ThrowIfNull(expressionText); var parser = new ConditionParser(new SimpleStringReader(expressionText), configurationItemFactories); ConditionExpression expression = parser.ParseExpression(); @@ -109,7 +107,6 @@ internal static ConditionExpression ParseExpression(SimpleStringReader stringRea { var parser = new ConditionParser(stringReader, configurationItemFactories); ConditionExpression expression = parser.ParseExpression(); - return expression; } @@ -152,30 +149,30 @@ private ConditionMethodExpression CreateMethodExpression(string functionName, Li // Attempt to lookup functionName that can handle the provided number of input-parameters if (inputParameters.Count == 0) { - Func method = _configurationItemFactory.ConditionMethodFactory.TryCreateInstanceWithNoParameters(functionName); + Func? method = _configurationItemFactory.ConditionMethodFactory.TryCreateInstanceWithNoParameters(functionName); if (method != null) return ConditionMethodExpression.CreateMethodNoParameters(functionName, method); } else if (inputParameters.Count == 1) { - Func method = _configurationItemFactory.ConditionMethodFactory.TryCreateInstanceWithOneParameter(functionName); + Func? method = _configurationItemFactory.ConditionMethodFactory.TryCreateInstanceWithOneParameter(functionName); if (method != null) return ConditionMethodExpression.CreateMethodOneParameter(functionName, method, inputParameters); } else if (inputParameters.Count == 2) { - Func method = _configurationItemFactory.ConditionMethodFactory.TryCreateInstanceWithTwoParameters(functionName); + Func? method = _configurationItemFactory.ConditionMethodFactory.TryCreateInstanceWithTwoParameters(functionName); if (method != null) return ConditionMethodExpression.CreateMethodTwoParameters(functionName, method, inputParameters); } else if (inputParameters.Count == 3) { - Func method = _configurationItemFactory.ConditionMethodFactory.TryCreateInstanceWithThreeParameters(functionName); + Func? method = _configurationItemFactory.ConditionMethodFactory.TryCreateInstanceWithThreeParameters(functionName); if (method != null) return ConditionMethodExpression.CreateMethodThreeParameters(functionName, method, inputParameters); } - Func manyParameterMethod = _configurationItemFactory.ConditionMethodFactory.TryCreateInstanceWithManyParameters(functionName, out var manyParameterMinCount, out var manyParameterMaxCount, out var manyParameterWithLogEvent); + Func? manyParameterMethod = _configurationItemFactory.ConditionMethodFactory.TryCreateInstanceWithManyParameters(functionName, out var manyParameterMinCount, out var manyParameterMaxCount, out var manyParameterWithLogEvent); if (manyParameterMethod is null) throw new ConditionParseException($"Unknown condition method '{functionName}'"); if (manyParameterMinCount > inputParameters.Count) @@ -213,7 +210,8 @@ private ConditionExpression ParseLiteralExpression() if (_tokenizer.TokenType == ConditionTokenType.String) { - var simpleLayout = new SimpleLayout(_tokenizer.StringTokenValue, _configurationItemFactory); + var stringTokenValue = _tokenizer.TokenValue.Substring(1, _tokenizer.TokenValue.Length - 2).Replace("''", "'"); + var simpleLayout = string.IsNullOrEmpty(stringTokenValue) ? SimpleLayout.Default : new SimpleLayout(stringTokenValue, _configurationItemFactory); _tokenizer.GetNextToken(); if (simpleLayout.IsFixedText) return new ConditionLiteralExpression(simpleLayout.FixedText); @@ -225,7 +223,7 @@ private ConditionExpression ParseLiteralExpression() { string keyword = _tokenizer.EatKeyword(); - if (TryPlainKeywordToExpression(keyword, out var expression)) + if (TryPlainKeywordToExpression(keyword, out var expression) && expression != null) { return expression; } @@ -248,7 +246,7 @@ private ConditionExpression ParseLiteralExpression() /// /// /// success? - private bool TryPlainKeywordToExpression(string keyword, out ConditionExpression expression) + private bool TryPlainKeywordToExpression(string keyword, out ConditionExpression? expression) { if (string.Equals(keyword, "level", StringComparison.OrdinalIgnoreCase)) { @@ -283,19 +281,19 @@ private bool TryPlainKeywordToExpression(string keyword, out ConditionExpression if (string.Equals(keyword, "true", StringComparison.OrdinalIgnoreCase)) { - expression = new ConditionLiteralExpression(ConditionExpression.BoxedTrue); + expression = ConditionLiteralExpression.True; return true; } if (string.Equals(keyword, "false", StringComparison.OrdinalIgnoreCase)) { - expression = new ConditionLiteralExpression(ConditionExpression.BoxedFalse); + expression = ConditionLiteralExpression.False; return true; } if (string.Equals(keyword, "null", StringComparison.OrdinalIgnoreCase)) { - expression = new ConditionLiteralExpression(null); + expression = ConditionLiteralExpression.Null; return true; } @@ -315,7 +313,6 @@ private ConditionExpression ParseNumber(bool negative) if (numberString.IndexOf('.') >= 0) { var d = double.Parse(numberString, CultureInfo.InvariantCulture); - return new ConditionLiteralExpression(negative ? -d : d); } diff --git a/src/NLog/Conditions/ConditionRelationalExpression.cs b/src/NLog/Conditions/ConditionRelationalExpression.cs index aa2fed282d..3b31764a5c 100644 --- a/src/NLog/Conditions/ConditionRelationalExpression.cs +++ b/src/NLog/Conditions/ConditionRelationalExpression.cs @@ -84,8 +84,8 @@ public override string ToString() /// protected override object EvaluateNode(LogEventInfo context) { - object v1 = LeftExpression.Evaluate(context); - object v2 = RightExpression.Evaluate(context); + var v1 = LeftExpression.Evaluate(context); + var v2 = RightExpression.Evaluate(context); return Compare(v1, v2, RelationalOperator) ? BoxedTrue : BoxedFalse; } @@ -97,13 +97,9 @@ protected override object EvaluateNode(LogEventInfo context) /// The second value. /// The relational operator. /// Result of the given relational operator. - private static bool Compare(object leftValue, object rightValue, ConditionRelationalOperator relationalOperator) + private static bool Compare(object? leftValue, object? rightValue, ConditionRelationalOperator relationalOperator) { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - System.Collections.IComparer comparer = StringComparer.InvariantCulture; -#else System.Collections.IComparer comparer = StringComparer.Ordinal; -#endif PromoteTypes(ref leftValue, ref rightValue); switch (relationalOperator) { @@ -135,7 +131,7 @@ private static bool Compare(object leftValue, object rightValue, ConditionRelati /// /// /// - private static void PromoteTypes(ref object leftValue, ref object rightValue) + private static void PromoteTypes(ref object? leftValue, ref object? rightValue) { if (ReferenceEquals(leftValue, rightValue) || leftValue is null || rightValue is null) { diff --git a/src/NLog/Conditions/ConditionTokenizer.cs b/src/NLog/Conditions/ConditionTokenizer.cs index 48f1ead58d..d3c87f2890 100644 --- a/src/NLog/Conditions/ConditionTokenizer.cs +++ b/src/NLog/Conditions/ConditionTokenizer.cs @@ -53,6 +53,7 @@ public ConditionTokenizer(SimpleStringReader stringReader) { _stringReader = stringReader; TokenType = ConditionTokenType.BeginningOfInput; + TokenValue = string.Empty; GetNextToken(); } @@ -60,16 +61,6 @@ public ConditionTokenizer(SimpleStringReader stringReader) public string TokenValue { get; private set; } - public string StringTokenValue - { - get - { - string s = TokenValue; - - return s.Substring(1, s.Length - 2).Replace("''", "'"); - } - } - /// /// Asserts current token type and advances to the next token. /// @@ -106,7 +97,7 @@ public string EatKeyword() /// /// The keyword. /// - /// A value of true if current keyword is equal to the specified value; otherwise, false. + /// A value of if current keyword is equal to the specified value; otherwise, . /// public bool IsKeyword(string keyword) { @@ -122,7 +113,7 @@ public bool IsKeyword(string keyword) /// Gets or sets a value indicating whether the tokenizer has reached the end of the token stream. ///
/// - /// A value of true if the tokenizer has reached the end of the token stream; otherwise, false. + /// A value of if the tokenizer has reached the end of the token stream; otherwise, . /// public bool IsEOF() { @@ -133,7 +124,7 @@ public bool IsEOF() /// Gets or sets a value indicating whether current token is a number. ///
/// - /// A value of true if current token is a number; otherwise, false. + /// A value of if current token is a number; otherwise, . /// public bool IsNumber() { @@ -145,7 +136,7 @@ public bool IsNumber() ///
/// The token type. /// - /// A value of true if current token is of specified type; otherwise, false. + /// A value of if current token is of specified type; otherwise, . /// public bool IsToken(ConditionTokenType tokenType) { @@ -497,7 +488,11 @@ private int ReadChar() /// /// Mapping between characters and token types for punctuations. /// - private struct CharToTokenType + private +#if !NETFRAMEWORK + readonly +#endif + struct CharToTokenType { public readonly char Character; public readonly ConditionTokenType TokenType; diff --git a/src/NLog/Config/ArrayParameterAttribute.cs b/src/NLog/Config/ArrayParameterAttribute.cs index 6f5959a0d5..596a902f94 100644 --- a/src/NLog/Config/ArrayParameterAttribute.cs +++ b/src/NLog/Config/ArrayParameterAttribute.cs @@ -31,11 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using JetBrains.Annotations; - namespace NLog.Config { using System; + using JetBrains.Annotations; /// /// Used to mark configurable parameters which are arrays. diff --git a/src/NLog/Config/AssemblyExtensionLoader.cs b/src/NLog/Config/AssemblyExtensionLoader.cs index 6e27a14ad5..84bd6ad64c 100644 --- a/src/NLog/Config/AssemblyExtensionLoader.cs +++ b/src/NLog/Config/AssemblyExtensionLoader.cs @@ -43,13 +43,7 @@ namespace NLog.Config [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] internal sealed class AssemblyExtensionLoader : IAssemblyExtensionLoader { - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow extension loading from config", "IL2072")] - public void LoadTypeFromName(ConfigurationItemFactory factory, string typeName, string itemNamePrefix) - { - var configType = PropertyTypeConverter.ConvertToType(typeName, true); - factory.RegisterType(configType, itemNamePrefix); - } - + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.GetTypes() Incompatible with trimming.")] public void LoadAssemblyFromName(ConfigurationItemFactory factory, string assemblyName, string itemNamePrefix) { if (SkipAlreadyLoadedAssembly(factory, assemblyName, itemNamePrefix)) @@ -60,9 +54,79 @@ public void LoadAssemblyFromName(ConfigurationItemFactory factory, string assemb InternalLogger.Info("Loading assembly name: {0}{1}", assemblyName, string.IsNullOrEmpty(itemNamePrefix) ? "" : $" (Prefix={itemNamePrefix})"); Assembly extensionAssembly = LoadAssemblyFromName(assemblyName); - InternalLogger.LogAssemblyVersion(extensionAssembly); - factory.RegisterItemsFromAssembly(extensionAssembly, itemNamePrefix); - InternalLogger.Debug("Loading assembly name: {0} succeeded!", assemblyName); + LoadAssembly(factory, extensionAssembly, itemNamePrefix); + } + + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.GetTypes() Incompatible with trimming.")] + public void LoadAssembly(ConfigurationItemFactory factory, Assembly assembly, string itemNamePrefix) + { + AssemblyHelpers.LogAssemblyVersion(assembly); + + InternalLogger.Debug("ScanAssembly('{0}')", assembly.FullName); + var typesToScan = SafeGetTypes(assembly); + if (typesToScan?.Length > 0) + { + if (ReferenceEquals(assembly, typeof(LogFactory).Assembly)) + { + typesToScan = typesToScan.Where(t => t.IsPublic && t.IsClass).ToArray(); + } + + foreach (Type type in typesToScan) + { + try + { + RegisterTypeFromAssembly(factory, type, itemNamePrefix); + } + catch (Exception exception) + { + InternalLogger.Error(exception, "Failed to add type '{0}'.", type.FullName); + + if (exception.MustBeRethrown()) + { + throw; + } + } + } + } + + InternalLogger.Debug("Loading assembly name: {0} succeeded!", assembly.FullName); + } + + /// + /// Gets all usable exported types from the given assembly. + /// + /// Assembly to scan. + /// Usable types from the given assembly. + /// Types which cannot be loaded are skipped. + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.GetTypes() Incompatible with trimming.")] + private static Type[] SafeGetTypes(Assembly assembly) + { + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException typeLoadException) + { + var result = typeLoadException.Types?.Where(t => t != null).ToArray() ?? ArrayHelper.Empty(); + InternalLogger.Warn(typeLoadException, "Loaded {0} valid types from Assembly: {1}", result.Length, assembly.FullName); + foreach (var ex in typeLoadException.LoaderExceptions ?? ArrayHelper.Empty()) + { + InternalLogger.Warn(ex, "Type load exception."); + } + return result; + } + catch (Exception ex) + { + InternalLogger.Warn(ex, "Failed to load types from Assembly: {0}", assembly.FullName); + return ArrayHelper.Empty(); + } + } + + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow extension loading from config", "IL2072")] + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow extension loading from config", "IL2067")] + private static void RegisterTypeFromAssembly(ConfigurationItemFactory factory, Type type, string itemNamePrefix) + { + factory.RegisterType(type, itemNamePrefix); } private static bool SkipAlreadyLoadedAssembly(ConfigurationItemFactory factory, string assemblyName, string itemNamePrefix) @@ -95,12 +159,12 @@ private static bool SkipAlreadyLoadedAssembly(ConfigurationItemFactory factory, return false; } - private static Dictionary ResolveLoadedAssemblyTypes(ConfigurationItemFactory factory) + private static Dictionary ResolveLoadedAssemblyTypes(ConfigurationItemFactory factory) { - var loadedAssemblies = new Dictionary(); + var loadedAssemblies = new Dictionary(); foreach (var itemType in factory.ItemTypes) { - var assembly = itemType.GetAssembly(); + var assembly = itemType.Assembly; if (assembly is null) continue; @@ -120,7 +184,7 @@ private static Dictionary ResolveLoadedAssemblyTypes(Configurati return loadedAssemblies; } - private static bool IsNLogConfigurationItemType(Type itemType) + private static bool IsNLogConfigurationItemType(Type? itemType) { if (itemType is null) { @@ -146,79 +210,63 @@ private static bool IsNLogConfigurationItemType(Type itemType) var nameAttribute = itemType.GetFirstCustomAttribute(); return !string.IsNullOrEmpty(nameAttribute?.Name); } - else - { - return false; - } + + return false; } - private static bool IsNLogItemTypeAlreadyRegistered(ConfigurationItemFactory factory, Type itemType, string itemNamePrefix) + private static bool IsNLogItemTypeAlreadyRegistered(ConfigurationItemFactory factory, Type? itemType, string itemNamePrefix) { if (itemType is null) { return false; } - else if (IsNLogItemTypeAlreadyRegistered, Layouts.Layout, Layouts.LayoutAttribute>(factory.LayoutFactory, itemType, itemNamePrefix)) + else if (typeof(Layouts.Layout).IsAssignableFrom(itemType)) { - return true; + return IsNLogItemTypeAlreadyRegistered(factory.LayoutFactory, itemType, itemNamePrefix); } - else if (IsNLogItemTypeAlreadyRegistered, LayoutRenderers.LayoutRenderer, LayoutRenderers.LayoutRendererAttribute>(factory.LayoutRendererFactory, itemType, itemNamePrefix)) + else if (typeof(LayoutRenderers.LayoutRenderer).IsAssignableFrom(itemType)) { - return true; + return IsNLogItemTypeAlreadyRegistered(factory.LayoutRendererFactory, itemType, itemNamePrefix); } - else if (IsNLogItemTypeAlreadyRegistered, Targets.Target, Targets.TargetAttribute>(factory.TargetFactory, itemType, itemNamePrefix)) + else if (typeof(Targets.Target).IsAssignableFrom(itemType)) { - return true; + return IsNLogItemTypeAlreadyRegistered(factory.TargetFactory, itemType, itemNamePrefix); } - else if (IsNLogItemTypeAlreadyRegistered, Filters.Filter, Filters.FilterAttribute>(factory.FilterFactory, itemType, itemNamePrefix)) + else if (typeof(Filters.Filter).IsAssignableFrom(itemType)) { - return true; + return IsNLogItemTypeAlreadyRegistered(factory.FilterFactory, itemType, itemNamePrefix); } return false; } - private static bool IsNLogItemTypeAlreadyRegistered(TFactory factory, Type itemType, string itemNamePrefix) + private static bool IsNLogItemTypeAlreadyRegistered(IFactory factory, Type itemType, string itemNamePrefix) where TAttribute : NameBaseAttribute - where TFactory : IFactory where TBaseType : class { - if (typeof(TBaseType).IsAssignableFrom(itemType)) + var nameAttribute = itemType.GetFirstCustomAttribute()?.Name ?? string.Empty; + if (!string.IsNullOrEmpty(nameAttribute)) { - var nameAttribute = itemType.GetFirstCustomAttribute(); - if (!string.IsNullOrEmpty(nameAttribute?.Name)) - { - var typeAlias = string.IsNullOrEmpty(itemNamePrefix) ? nameAttribute.Name : itemNamePrefix + nameAttribute.Name; - return factory.TryCreateInstance(typeAlias, out var _); - } + var typeAlias = string.IsNullOrEmpty(itemNamePrefix) ? nameAttribute : itemNamePrefix + nameAttribute; + return factory.TryCreateInstance(typeAlias, out var _); } return false; } - public void LoadAssemblyFromPath(ConfigurationItemFactory factory, string assemblyFileName, string baseDirectory, string itemNamePrefix) + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.GetTypes() Incompatible with trimming.")] + public void LoadAssemblyFromPath(ConfigurationItemFactory factory, string assemblyFileName, string? baseDirectory, string itemNamePrefix) { - RegisterAssemblyFromPath(factory, assemblyFileName, baseDirectory, itemNamePrefix); - } - - private static Assembly RegisterAssemblyFromPath(ConfigurationItemFactory factory, string assemblyFileName, string baseDirectory = null, string itemNamePrefix = null) - { - InternalLogger.Info("Loading assembly file: {0}{1}", assemblyFileName, string.IsNullOrEmpty(itemNamePrefix) ? "" : $" (Prefix={itemNamePrefix})"); - var extensionAssembly = LoadAssemblyFromPath(assemblyFileName, baseDirectory); - if (extensionAssembly is null) - return null; - - InternalLogger.LogAssemblyVersion(extensionAssembly); - factory.RegisterItemsFromAssembly(extensionAssembly, itemNamePrefix); - InternalLogger.Debug("Loading assembly file: {0} succeeded!", assemblyFileName); - return extensionAssembly; + var assembly = LoadAssemblyFromPath(assemblyFileName, baseDirectory); + if (assembly != null) + LoadAssembly(factory, assembly, itemNamePrefix); } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.GetTypes() Incompatible with trimming.")] public void ScanForAutoLoadExtensions(ConfigurationItemFactory factory) { -#if !NETSTANDARD1_3 try { - var nlogAssembly = typeof(LogFactory).GetAssembly(); + var nlogAssembly = typeof(LogFactory).Assembly; var assemblyLocation = string.Empty; var extensionDlls = ArrayHelper.Empty(); var fileLocations = GetAutoLoadingFileLocations(); @@ -259,13 +307,10 @@ public void ScanForAutoLoadExtensions(ConfigurationItemFactory factory) } } InternalLogger.Debug("Auto loading done"); -#else - // Nothing to do for Sonar Cube -#endif } -#if !NETSTANDARD1_3 - private static HashSet LoadNLogExtensionAssemblies(ConfigurationItemFactory factory, Assembly nlogAssembly, string[] extensionDlls) + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.LoadFrom() Incompatible with trimming.")] + private HashSet LoadNLogExtensionAssemblies(ConfigurationItemFactory factory, Assembly nlogAssembly, string[] extensionDlls) { HashSet alreadyRegistered = new HashSet(StringComparer.OrdinalIgnoreCase) { @@ -276,9 +321,12 @@ private static HashSet LoadNLogExtensionAssemblies(ConfigurationItemFact { try { - var extensionAssembly = RegisterAssemblyFromPath(factory, extensionDll); + var extensionAssembly = LoadAssemblyFromPath(extensionDll); if (extensionAssembly != null) + { + LoadAssembly(factory, extensionAssembly, string.Empty); alreadyRegistered.Add(extensionAssembly.FullName); + } } catch (Exception ex) { @@ -298,20 +346,17 @@ private static HashSet LoadNLogExtensionAssemblies(ConfigurationItemFact return alreadyRegistered; } - private static void RegisterAppDomainAssemblies(ConfigurationItemFactory factory, Assembly nlogAssembly, HashSet alreadyRegistered) + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.GetTypes() Incompatible with trimming.")] + private void RegisterAppDomainAssemblies(ConfigurationItemFactory factory, Assembly nlogAssembly, HashSet alreadyRegistered) { alreadyRegistered.Add(nlogAssembly.FullName); -#if !NETSTANDARD1_5 var allAssemblies = LogFactory.DefaultAppEnvironment.GetAppDomainRuntimeAssemblies(); -#else - var allAssemblies = new[] { nlogAssembly }; -#endif foreach (var assembly in allAssemblies) { if (assembly.FullName.StartsWith("NLog.", StringComparison.OrdinalIgnoreCase) && !alreadyRegistered.Contains(assembly.FullName)) { - factory.RegisterItemsFromAssembly(assembly); + LoadAssembly(factory, assembly, string.Empty); } if (IncludeAsHiddenAssembly(assembly.FullName)) @@ -349,20 +394,20 @@ private static bool IncludeAsHiddenAssembly(string assemblyFullName) internal static IEnumerable> GetAutoLoadingFileLocations() { - var nlogAssembly = typeof(LogFactory).GetAssembly(); - var nlogAssemblyLocation = PathHelpers.TrimDirectorySeparators(AssemblyHelpers.GetAssemblyFileLocation(nlogAssembly)); - InternalLogger.Debug("Auto loading based on NLog-Assembly found location: {0}", nlogAssemblyLocation); - if (!string.IsNullOrEmpty(nlogAssemblyLocation)) - yield return new KeyValuePair(nlogAssemblyLocation, nameof(nlogAssemblyLocation)); + var nlogAssemblyLocation = AssemblyHelpers.GetAssemblyFileLocation(typeof(LogFactory).Assembly); + var nlogAssemblyDirectory = string.IsNullOrEmpty(nlogAssemblyLocation) ? nlogAssemblyLocation : PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation)); + InternalLogger.Debug("Auto loading based on NLog-Assembly found location: {0}", nlogAssemblyDirectory); + if (!string.IsNullOrEmpty(nlogAssemblyDirectory)) + yield return new KeyValuePair(nlogAssemblyDirectory, nameof(nlogAssemblyLocation)); var entryAssemblyLocation = PathHelpers.TrimDirectorySeparators(LogFactory.DefaultAppEnvironment.EntryAssemblyLocation); InternalLogger.Debug("Auto loading based on GetEntryAssembly-Assembly found location: {0}", entryAssemblyLocation); - if (!string.IsNullOrEmpty(entryAssemblyLocation) && !string.Equals(entryAssemblyLocation, nlogAssemblyLocation, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(entryAssemblyLocation) && !string.Equals(entryAssemblyLocation, nlogAssemblyDirectory, StringComparison.OrdinalIgnoreCase)) yield return new KeyValuePair(entryAssemblyLocation, nameof(entryAssemblyLocation)); var baseDirectory = PathHelpers.TrimDirectorySeparators(LogFactory.DefaultAppEnvironment.AppDomainBaseDirectory); InternalLogger.Debug("Auto loading based on AppDomain-BaseDirectory found location: {0}", baseDirectory); - if (!string.IsNullOrEmpty(baseDirectory) && !string.Equals(baseDirectory, nlogAssemblyLocation, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(baseDirectory) && !string.Equals(baseDirectory, nlogAssemblyDirectory, StringComparison.OrdinalIgnoreCase) && !string.Equals(baseDirectory, entryAssemblyLocation, StringComparison.OrdinalIgnoreCase)) yield return new KeyValuePair(baseDirectory, nameof(baseDirectory)); } @@ -411,7 +456,6 @@ private static string[] GetNLogExtensionFiles(string assemblyLocation) return ArrayHelper.Empty(); } } -#endif /// /// Load from url @@ -419,38 +463,17 @@ private static string[] GetNLogExtensionFiles(string assemblyLocation) /// file or path, including .dll /// basepath, optional /// - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow extension loading from config", "IL2026")] - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - private static Assembly LoadAssemblyFromPath(string assemblyFileName, string baseDirectory = null) + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.LoadFrom() Incompatible with trimming.")] + private static Assembly LoadAssemblyFromPath(string assemblyFileName, string? baseDirectory = null) { -#if NETSTANDARD1_3 - return null; -#else string fullFileName = assemblyFileName; if (!string.IsNullOrEmpty(baseDirectory)) { fullFileName = System.IO.Path.Combine(baseDirectory, assemblyFileName); - InternalLogger.Debug("Loading assembly file: {0}", fullFileName); } -#if NETSTANDARD1_5 - try - { - var assemblyName = System.Runtime.Loader.AssemblyLoadContext.GetAssemblyName(fullFileName); - return Assembly.Load(assemblyName); - } - catch (Exception ex) - { - // this doesn't usually work - InternalLogger.Warn(ex, "Fallback to AssemblyLoadContext.Default.LoadFromAssemblyPath for file: {0}", fullFileName); - return System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(fullFileName); - } -#else - Assembly asm = Assembly.LoadFrom(fullFileName); - return asm; -#endif - -#endif + InternalLogger.Info("Loading assembly file: {0}", fullFileName); + return Assembly.LoadFrom(fullFileName); } /// @@ -458,7 +481,6 @@ private static Assembly LoadAssemblyFromPath(string assemblyFileName, string bas /// private static Assembly LoadAssemblyFromName(string assemblyName) { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 try { Assembly assembly = Assembly.Load(assemblyName); @@ -478,13 +500,8 @@ private static Assembly LoadAssemblyFromName(string assemblyName) InternalLogger.Trace("Haven't found' '{0}' in current domain", assemblyName); throw; } -#else - var name = new AssemblyName(assemblyName); - return Assembly.Load(name); -#endif } -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 private static bool IsAssemblyMatch(AssemblyName expected, AssemblyName actual) { if (expected is null || actual is null) @@ -500,17 +517,19 @@ private static bool IsAssemblyMatch(AssemblyName expected, AssemblyName actual) var correctToken = expectedKeyToken is null || expectedKeyToken.SequenceEqual(actual.GetPublicKeyToken()); return correctToken; } -#endif } internal interface IAssemblyExtensionLoader { + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.GetTypes() Incompatible with trimming.")] void ScanForAutoLoadExtensions(ConfigurationItemFactory factory); + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.GetTypes() Incompatible with trimming.")] + void LoadAssemblyFromPath(ConfigurationItemFactory factory, string assemblyFileName, string? baseDirectory, string itemNamePrefix); - void LoadAssemblyFromPath(ConfigurationItemFactory factory, string assemblyFileName, string baseDirectory, string itemNamePrefix); - + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.GetTypes() Incompatible with trimming.")] void LoadAssemblyFromName(ConfigurationItemFactory factory, string assemblyName, string itemNamePrefix); - void LoadTypeFromName(ConfigurationItemFactory factory, string typeName, string itemNamePrefix); + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Assembly.GetTypes() Incompatible with trimming.")] + void LoadAssembly(ConfigurationItemFactory factory, Assembly assembly, string itemNamePrefix); } } diff --git a/src/NLog/Config/AssemblyExtensionTypes.cs b/src/NLog/Config/AssemblyExtensionTypes.cs index daaf92ef8b..310bc955c0 100644 --- a/src/NLog/Config/AssemblyExtensionTypes.cs +++ b/src/NLog/Config/AssemblyExtensionTypes.cs @@ -38,11 +38,342 @@ namespace NLog.Config /// internal static class AssemblyExtensionTypes { - public static void RegisterTypes(ConfigurationItemFactory factory) - { #pragma warning disable CS0618 // Type or member is obsolete + public static void RegisterTargetTypes(ConfigurationItemFactory factory, bool skipCheckExists) + { factory.RegisterTypeProperties(() => null); +#if NETFRAMEWORK + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("EventLog")) + factory.GetTargetFactory().RegisterType("EventLog"); +#endif + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("ColoredConsole")) + factory.GetTargetFactory().RegisterType("ColoredConsole"); + factory.RegisterType(); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("Console")) + factory.GetTargetFactory().RegisterType("Console"); + factory.RegisterType(); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("Debugger")) + factory.GetTargetFactory().RegisterType("Debugger"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("DebugSystem")) + factory.GetTargetFactory().RegisterType("DebugSystem"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("Debug")) + factory.GetTargetFactory().RegisterType("Debug"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("File")) + factory.GetTargetFactory().RegisterType("File"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("Memory")) + factory.GetTargetFactory().RegisterType("Memory"); + factory.RegisterType(); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("MethodCall")) + factory.GetTargetFactory().RegisterType("MethodCall"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("Null")) + factory.GetTargetFactory().RegisterType("Null"); + factory.RegisterType(); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("AsyncWrapper")) + factory.GetTargetFactory().RegisterType("AsyncWrapper"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("AutoFlushWrapper")) + factory.GetTargetFactory().RegisterType("AutoFlushWrapper"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("BufferingWrapper")) + factory.GetTargetFactory().RegisterType("BufferingWrapper"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("FallbackGroup")) + factory.GetTargetFactory().RegisterType("FallbackGroup"); + factory.RegisterType(); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("FilteringWrapper")) + factory.GetTargetFactory().RegisterType("FilteringWrapper"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("GroupByWrapper")) + factory.GetTargetFactory().RegisterType("GroupByWrapper"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("LimitingWrapper")) + factory.GetTargetFactory().RegisterType("LimitingWrapper"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("PostFilteringWrapper")) + factory.GetTargetFactory().RegisterType("PostFilteringWrapper"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("RandomizeGroup")) + factory.GetTargetFactory().RegisterType("RandomizeGroup"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("RepeatingWrapper")) + factory.GetTargetFactory().RegisterType("RepeatingWrapper"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("RetryingWrapper")) + factory.GetTargetFactory().RegisterType("RetryingWrapper"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("RoundRobinGroup")) + factory.GetTargetFactory().RegisterType("RoundRobinGroup"); + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("SplitGroup")) + factory.GetTargetFactory().RegisterType("SplitGroup"); + } + + public static void RegisterLayoutTypes(ConfigurationItemFactory factory, bool skipCheckExists) + { factory.RegisterTypeProperties(() => null); + if (skipCheckExists || !factory.GetLayoutFactory().CheckTypeAliasExists("CompoundLayout")) + factory.GetLayoutFactory().RegisterType("CompoundLayout"); + factory.RegisterType(); + if (skipCheckExists || !factory.GetLayoutFactory().CheckTypeAliasExists("CsvLayout")) + factory.GetLayoutFactory().RegisterType("CsvLayout"); + if (skipCheckExists || !factory.GetLayoutFactory().CheckTypeAliasExists("JsonArrayLayout")) + factory.GetLayoutFactory().RegisterType("JsonArrayLayout"); + factory.RegisterType(); + if (skipCheckExists || !factory.GetLayoutFactory().CheckTypeAliasExists("JsonLayout")) + factory.GetLayoutFactory().RegisterType("JsonLayout"); + if (skipCheckExists || !factory.GetLayoutFactory().CheckTypeAliasExists("LayoutWithHeaderAndFooter")) + factory.GetLayoutFactory().RegisterType("LayoutWithHeaderAndFooter"); + if (skipCheckExists || !factory.GetLayoutFactory().CheckTypeAliasExists("SimpleLayout")) + factory.GetLayoutFactory().RegisterType("SimpleLayout"); + factory.RegisterType(); + factory.RegisterType(); + if (skipCheckExists || !factory.GetLayoutFactory().CheckTypeAliasExists("XmlLayout")) + factory.GetLayoutFactory().RegisterType("XmlLayout"); + } + + public static void RegisterLayoutRendererTypes(ConfigurationItemFactory factory, bool skipCheckExists) + { +#if NETFRAMEWORK + factory.GetLayoutRendererFactory().RegisterType("appsetting"); +#endif + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("all-event-properties")) + factory.GetLayoutRendererFactory().RegisterType("all-event-properties"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("appdomain")) + factory.GetLayoutRendererFactory().RegisterType("appdomain"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("assembly-version")) + factory.GetLayoutRendererFactory().RegisterType("assembly-version"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("basedir")) + factory.GetLayoutRendererFactory().RegisterType("basedir"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("callsite-filename")) + factory.GetLayoutRendererFactory().RegisterType("callsite-filename"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("callsite")) + factory.GetLayoutRendererFactory().RegisterType("callsite"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("callsite-linenumber")) + factory.GetLayoutRendererFactory().RegisterType("callsite-linenumber"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("counter")) + factory.GetLayoutRendererFactory().RegisterType("counter"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("currentdir")) + factory.GetLayoutRendererFactory().RegisterType("currentdir"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("date")) + factory.GetLayoutRendererFactory().RegisterType("date"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("db-null")) + factory.GetLayoutRendererFactory().RegisterType("db-null"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("dir-separator")) + factory.GetLayoutRendererFactory().RegisterType("dir-separator"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("environment")) + factory.GetLayoutRendererFactory().RegisterType("environment"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("environment-user")) + factory.GetLayoutRendererFactory().RegisterType("environment-user"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("event-properties")) + factory.GetLayoutRendererFactory().RegisterType("event-properties"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("event-property")) + factory.GetLayoutRendererFactory().RegisterType("event-property"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("event-context")) + factory.GetLayoutRendererFactory().RegisterType("event-context"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("exceptiondata")) + factory.GetLayoutRendererFactory().RegisterType("exceptiondata"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("exception-data")) + factory.GetLayoutRendererFactory().RegisterType("exception-data"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("exception")) + factory.GetLayoutRendererFactory().RegisterType("exception"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("gc")) + factory.GetLayoutRendererFactory().RegisterType("gc"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("gdc")) + factory.GetLayoutRendererFactory().RegisterType("gdc"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("guid")) + factory.GetLayoutRendererFactory().RegisterType("guid"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("hostname")) + factory.GetLayoutRendererFactory().RegisterType("hostname"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("identity")) + factory.GetLayoutRendererFactory().RegisterType("identity"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("install-context")) + factory.GetLayoutRendererFactory().RegisterType("install-context"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("level")) + factory.GetLayoutRendererFactory().RegisterType("level"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("loglevel")) + factory.GetLayoutRendererFactory().RegisterType("loglevel"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("literal")) + factory.GetLayoutRendererFactory().RegisterType("literal"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("loggername")) + factory.GetLayoutRendererFactory().RegisterType("loggername"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("logger")) + factory.GetLayoutRendererFactory().RegisterType("logger"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("longdate")) + factory.GetLayoutRendererFactory().RegisterType("longdate"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("machinename")) + factory.GetLayoutRendererFactory().RegisterType("machinename"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("message")) + factory.GetLayoutRendererFactory().RegisterType("message"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("newline")) + factory.GetLayoutRendererFactory().RegisterType("newline"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("nlogdir")) + factory.GetLayoutRendererFactory().RegisterType("nlogdir"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("processdir")) + factory.GetLayoutRendererFactory().RegisterType("processdir"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("processid")) + factory.GetLayoutRendererFactory().RegisterType("processid"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("processinfo")) + factory.GetLayoutRendererFactory().RegisterType("processinfo"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("processname")) + factory.GetLayoutRendererFactory().RegisterType("processname"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("processtime")) + factory.GetLayoutRendererFactory().RegisterType("processtime"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("scopeindent")) + factory.GetLayoutRendererFactory().RegisterType("scopeindent"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("scopenested")) + factory.GetLayoutRendererFactory().RegisterType("scopenested"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("ndc")) + factory.GetLayoutRendererFactory().RegisterType("ndc"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("ndlc")) + factory.GetLayoutRendererFactory().RegisterType("ndlc"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("scopeproperty")) + factory.GetLayoutRendererFactory().RegisterType("scopeproperty"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("mdc")) + factory.GetLayoutRendererFactory().RegisterType("mdc"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("mdlc")) + factory.GetLayoutRendererFactory().RegisterType("mdlc"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("scopetiming")) + factory.GetLayoutRendererFactory().RegisterType("scopetiming"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("ndlctiming")) + factory.GetLayoutRendererFactory().RegisterType("ndlctiming"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("sequenceid")) + factory.GetLayoutRendererFactory().RegisterType("sequenceid"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("shortdate")) + factory.GetLayoutRendererFactory().RegisterType("shortdate"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("userApplicationDataDir")) + factory.GetLayoutRendererFactory().RegisterType("userApplicationDataDir"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("commonApplicationDataDir")) + factory.GetLayoutRendererFactory().RegisterType("commonApplicationDataDir"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("specialfolder")) + factory.GetLayoutRendererFactory().RegisterType("specialfolder"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("userLocalApplicationDataDir")) + factory.GetLayoutRendererFactory().RegisterType("userLocalApplicationDataDir"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("stacktrace")) + factory.GetLayoutRendererFactory().RegisterType("stacktrace"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("tempdir")) + factory.GetLayoutRendererFactory().RegisterType("tempdir"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("threadid")) + factory.GetLayoutRendererFactory().RegisterType("threadid"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("threadname")) + factory.GetLayoutRendererFactory().RegisterType("threadname"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("ticks")) + factory.GetLayoutRendererFactory().RegisterType("ticks"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("time")) + factory.GetLayoutRendererFactory().RegisterType("time"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("var")) + factory.GetLayoutRendererFactory().RegisterType("var"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("cached")) + factory.GetLayoutRendererFactory().RegisterType("cached"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("Cached")) + factory.GetAmbientPropertyFactory().RegisterType("Cached"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("ClearCache")) + factory.GetAmbientPropertyFactory().RegisterType("ClearCache"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("CachedSeconds")) + factory.GetAmbientPropertyFactory().RegisterType("CachedSeconds"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("filesystem-normalize")) + factory.GetLayoutRendererFactory().RegisterType("filesystem-normalize"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("FSNormalize")) + factory.GetAmbientPropertyFactory().RegisterType("FSNormalize"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("json-encode")) + factory.GetLayoutRendererFactory().RegisterType("json-encode"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("JsonEncode")) + factory.GetAmbientPropertyFactory().RegisterType("JsonEncode"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("left")) + factory.GetLayoutRendererFactory().RegisterType("left"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("Truncate")) + factory.GetAmbientPropertyFactory().RegisterType("Truncate"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("lowercase")) + factory.GetLayoutRendererFactory().RegisterType("lowercase"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("Lowercase")) + factory.GetAmbientPropertyFactory().RegisterType("Lowercase"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("ToLower")) + factory.GetAmbientPropertyFactory().RegisterType("ToLower"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("norawvalue")) + factory.GetLayoutRendererFactory().RegisterType("norawvalue"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("NoRawValue")) + factory.GetAmbientPropertyFactory().RegisterType("NoRawValue"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("Object-Path")) + factory.GetLayoutRendererFactory().RegisterType("Object-Path"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("ObjectPath")) + factory.GetAmbientPropertyFactory().RegisterType("ObjectPath"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("onexception")) + factory.GetLayoutRendererFactory().RegisterType("onexception"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("onhasproperties")) + factory.GetLayoutRendererFactory().RegisterType("onhasproperties"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("pad")) + factory.GetLayoutRendererFactory().RegisterType("pad"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("Padding")) + factory.GetAmbientPropertyFactory().RegisterType("Padding"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("PadCharacter")) + factory.GetAmbientPropertyFactory().RegisterType("PadCharacter"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("FixedLength")) + factory.GetAmbientPropertyFactory().RegisterType("FixedLength"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("AlignmentOnTruncation")) + factory.GetAmbientPropertyFactory().RegisterType("AlignmentOnTruncation"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("replace")) + factory.GetLayoutRendererFactory().RegisterType("replace"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("replace-newlines")) + factory.GetLayoutRendererFactory().RegisterType("replace-newlines"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("ReplaceNewLines")) + factory.GetAmbientPropertyFactory().RegisterType("ReplaceNewLines"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("right")) + factory.GetLayoutRendererFactory().RegisterType("right"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("rot13")) + factory.GetLayoutRendererFactory().RegisterType("rot13"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("substring")) + factory.GetLayoutRendererFactory().RegisterType("substring"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("trim-whitespace")) + factory.GetLayoutRendererFactory().RegisterType("trim-whitespace"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("TrimWhiteSpace")) + factory.GetAmbientPropertyFactory().RegisterType("TrimWhiteSpace"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("uppercase")) + factory.GetLayoutRendererFactory().RegisterType("uppercase"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("Uppercase")) + factory.GetAmbientPropertyFactory().RegisterType("Uppercase"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("ToUpper")) + factory.GetAmbientPropertyFactory().RegisterType("ToUpper"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("url-encode")) + factory.GetLayoutRendererFactory().RegisterType("url-encode"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("whenEmpty")) + factory.GetLayoutRendererFactory().RegisterType("whenEmpty"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("WhenEmpty")) + factory.GetAmbientPropertyFactory().RegisterType("WhenEmpty"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("when")) + factory.GetLayoutRendererFactory().RegisterType("when"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("When")) + factory.GetAmbientPropertyFactory().RegisterType("When"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("wrapline")) + factory.GetLayoutRendererFactory().RegisterType("wrapline"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("WrapLine")) + factory.GetAmbientPropertyFactory().RegisterType("WrapLine"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("xml-encode")) + factory.GetLayoutRendererFactory().RegisterType("xml-encode"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("XmlEncode")) + factory.GetAmbientPropertyFactory().RegisterType("XmlEncode"); + } + + public static void RegisterFilterTypes(ConfigurationItemFactory factory, bool skipCheckExists) + { + if (skipCheckExists || !factory.GetFilterFactory().CheckTypeAliasExists("when")) + factory.GetFilterFactory().RegisterType("when"); + if (skipCheckExists || !factory.GetFilterFactory().CheckTypeAliasExists("whenContains")) + factory.GetFilterFactory().RegisterType("whenContains"); + if (skipCheckExists || !factory.GetFilterFactory().CheckTypeAliasExists("whenEqual")) + factory.GetFilterFactory().RegisterType("whenEqual"); + if (skipCheckExists || !factory.GetFilterFactory().CheckTypeAliasExists("whenNotContains")) + factory.GetFilterFactory().RegisterType("whenNotContains"); + if (skipCheckExists || !factory.GetFilterFactory().CheckTypeAliasExists("whenNotEqual")) + factory.GetFilterFactory().RegisterType("whenNotEqual"); + if (skipCheckExists || !factory.GetFilterFactory().CheckTypeAliasExists("whenRepeated")) + factory.GetFilterFactory().RegisterType("whenRepeated"); + } + + public static void RegisterTimeSourceTypes(ConfigurationItemFactory factory, bool skipCheckExists) + { + if (skipCheckExists || !factory.GetTimeSourceFactory().CheckTypeAliasExists("AccurateLocal")) + factory.GetTimeSourceFactory().RegisterType("AccurateLocal"); + if (skipCheckExists || !factory.GetTimeSourceFactory().CheckTypeAliasExists("AccurateUTC")) + factory.GetTimeSourceFactory().RegisterType("AccurateUTC"); + if (skipCheckExists || !factory.GetTimeSourceFactory().CheckTypeAliasExists("FastLocal")) + factory.GetTimeSourceFactory().RegisterType("FastLocal"); + if (skipCheckExists || !factory.GetTimeSourceFactory().CheckTypeAliasExists("FastUTC")) + factory.GetTimeSourceFactory().RegisterType("FastUTC"); + } + + public static void RegisterConditionTypes(ConfigurationItemFactory factory, bool skipCheckExists) + { factory.RegisterTypeProperties(() => null); factory.RegisterTypeProperties(() => null); factory.RegisterTypeProperties(() => null); @@ -54,240 +385,32 @@ public static void RegisterTypes(ConfigurationItemFactory factory) factory.RegisterTypeProperties(() => null); factory.RegisterTypeProperties(() => null); factory.RegisterTypeProperties(() => null); - factory.RegisterType(); - factory.FilterFactory.RegisterType("when"); - factory.FilterFactory.RegisterType("whenContains"); - factory.FilterFactory.RegisterType("whenEqual"); - factory.FilterFactory.RegisterType("whenNotContains"); - factory.FilterFactory.RegisterType("whenNotEqual"); - factory.FilterFactory.RegisterType("whenRepeated"); - factory.LayoutRendererFactory.RegisterType("all-event-properties"); - factory.LayoutRendererFactory.RegisterType("appdomain"); -#if NETFRAMEWORK - factory.LayoutRendererFactory.RegisterType("appsetting"); -#endif - factory.LayoutRendererFactory.RegisterType("assembly-version"); - factory.LayoutRendererFactory.RegisterType("basedir"); - factory.LayoutRendererFactory.RegisterType("callsite-filename"); - factory.LayoutRendererFactory.RegisterType("callsite"); - factory.LayoutRendererFactory.RegisterType("callsite-linenumber"); - factory.LayoutRendererFactory.RegisterType("counter"); - factory.LayoutRendererFactory.RegisterType("currentdir"); - factory.LayoutRendererFactory.RegisterType("date"); - factory.LayoutRendererFactory.RegisterType("db-null"); - factory.LayoutRendererFactory.RegisterType("dir-separator"); - factory.LayoutRendererFactory.RegisterType("environment"); -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - factory.LayoutRendererFactory.RegisterType("environment-user"); -#endif - factory.LayoutRendererFactory.RegisterType("event-properties"); - factory.LayoutRendererFactory.RegisterType("event-property"); - factory.LayoutRendererFactory.RegisterType("event-context"); - factory.LayoutRendererFactory.RegisterType("exceptiondata"); - factory.LayoutRendererFactory.RegisterType("exception-data"); - factory.LayoutRendererFactory.RegisterType("exception"); - factory.LayoutRendererFactory.RegisterType("file-contents"); - factory.RegisterTypeProperties(() => null); - factory.LayoutRendererFactory.RegisterType("gc"); - factory.LayoutRendererFactory.RegisterType("gdc"); - factory.LayoutRendererFactory.RegisterType("guid"); - factory.LayoutRendererFactory.RegisterType("hostname"); -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - factory.LayoutRendererFactory.RegisterType("identity"); -#endif - factory.LayoutRendererFactory.RegisterType("install-context"); - factory.LayoutRendererFactory.RegisterType("level"); - factory.LayoutRendererFactory.RegisterType("loglevel"); - factory.LayoutRendererFactory.RegisterType("literal"); - factory.RegisterTypeProperties(() => null); -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - factory.LayoutRendererFactory.RegisterType("local-ip"); -#endif - factory.LayoutRendererFactory.RegisterType("log4jxmlevent"); - factory.LayoutRendererFactory.RegisterType("loggername"); - factory.LayoutRendererFactory.RegisterType("logger"); - factory.LayoutRendererFactory.RegisterType("longdate"); - factory.LayoutRendererFactory.RegisterType("machinename"); - factory.LayoutRendererFactory.RegisterType("mdc"); - factory.LayoutRendererFactory.RegisterType("mdlc"); - factory.LayoutRendererFactory.RegisterType("message"); - factory.LayoutRendererFactory.RegisterType("ndc"); - factory.LayoutRendererFactory.RegisterType("ndlc"); - factory.LayoutRendererFactory.RegisterType("ndlctiming"); - factory.LayoutRendererFactory.RegisterType("newline"); -#if !NETSTANDARD1_3 - factory.LayoutRendererFactory.RegisterType("nlogdir"); -#endif -#if !NETSTANDARD1_3 - factory.LayoutRendererFactory.RegisterType("processdir"); -#endif -#if !NETSTANDARD1_3 - factory.LayoutRendererFactory.RegisterType("processid"); -#endif -#if !NETSTANDARD1_3 - factory.LayoutRendererFactory.RegisterType("processinfo"); -#endif -#if !NETSTANDARD1_3 - factory.LayoutRendererFactory.RegisterType("processname"); -#endif - factory.LayoutRendererFactory.RegisterType("processtime"); - factory.LayoutRendererFactory.RegisterType("scopeindent"); - factory.LayoutRendererFactory.RegisterType("scopenested"); - factory.LayoutRendererFactory.RegisterType("scopeproperty"); - factory.LayoutRendererFactory.RegisterType("scopetiming"); - factory.LayoutRendererFactory.RegisterType("sequenceid"); - factory.LayoutRendererFactory.RegisterType("shortdate"); -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - factory.LayoutRendererFactory.RegisterType("userApplicationDataDir"); -#endif -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - factory.LayoutRendererFactory.RegisterType("commonApplicationDataDir"); -#endif -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - factory.LayoutRendererFactory.RegisterType("specialfolder"); -#endif -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - factory.LayoutRendererFactory.RegisterType("userLocalApplicationDataDir"); -#endif - factory.LayoutRendererFactory.RegisterType("stacktrace"); - factory.LayoutRendererFactory.RegisterType("tempdir"); - factory.LayoutRendererFactory.RegisterType("threadid"); - factory.LayoutRendererFactory.RegisterType("threadname"); - factory.LayoutRendererFactory.RegisterType("ticks"); - factory.LayoutRendererFactory.RegisterType("time"); -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - factory.LayoutRendererFactory.RegisterType("activityid"); -#endif - factory.LayoutRendererFactory.RegisterType("var"); - factory.LayoutRendererFactory.RegisterType("cached"); - factory.AmbientRendererFactory.RegisterType("Cached"); - factory.AmbientRendererFactory.RegisterType("ClearCache"); - factory.AmbientRendererFactory.RegisterType("CachedSeconds"); - factory.LayoutRendererFactory.RegisterType("filesystem-normalize"); - factory.AmbientRendererFactory.RegisterType("FSNormalize"); - factory.LayoutRendererFactory.RegisterType("json-encode"); - factory.AmbientRendererFactory.RegisterType("JsonEncode"); - factory.LayoutRendererFactory.RegisterType("left"); - factory.AmbientRendererFactory.RegisterType("Truncate"); - factory.LayoutRendererFactory.RegisterType("lowercase"); - factory.AmbientRendererFactory.RegisterType("Lowercase"); - factory.AmbientRendererFactory.RegisterType("ToLower"); - factory.LayoutRendererFactory.RegisterType("norawvalue"); - factory.AmbientRendererFactory.RegisterType("NoRawValue"); - factory.LayoutRendererFactory.RegisterType("Object-Path"); - factory.AmbientRendererFactory.RegisterType("ObjectPath"); - factory.LayoutRendererFactory.RegisterType("onexception"); - factory.LayoutRendererFactory.RegisterType("onhasproperties"); - factory.LayoutRendererFactory.RegisterType("pad"); - factory.AmbientRendererFactory.RegisterType("Padding"); - factory.AmbientRendererFactory.RegisterType("PadCharacter"); - factory.AmbientRendererFactory.RegisterType("FixedLength"); - factory.AmbientRendererFactory.RegisterType("AlignmentOnTruncation"); - factory.LayoutRendererFactory.RegisterType("replace"); - factory.LayoutRendererFactory.RegisterType("replace-newlines"); - factory.AmbientRendererFactory.RegisterType("ReplaceNewLines"); - factory.LayoutRendererFactory.RegisterType("right"); - factory.LayoutRendererFactory.RegisterType("rot13"); - factory.LayoutRendererFactory.RegisterType("substring"); - factory.LayoutRendererFactory.RegisterType("trim-whitespace"); - factory.AmbientRendererFactory.RegisterType("TrimWhiteSpace"); - factory.LayoutRendererFactory.RegisterType("uppercase"); - factory.AmbientRendererFactory.RegisterType("Uppercase"); - factory.AmbientRendererFactory.RegisterType("ToUpper"); - factory.LayoutRendererFactory.RegisterType("url-encode"); - factory.LayoutRendererFactory.RegisterType("whenEmpty"); - factory.AmbientRendererFactory.RegisterType("WhenEmpty"); - factory.LayoutRendererFactory.RegisterType("when"); - factory.AmbientRendererFactory.RegisterType("When"); - factory.LayoutRendererFactory.RegisterType("wrapline"); - factory.AmbientRendererFactory.RegisterType("WrapLine"); - factory.LayoutRendererFactory.RegisterType("xml-encode"); - factory.AmbientRendererFactory.RegisterType("XmlEncode"); - factory.LayoutFactory.RegisterType("CompoundLayout"); - factory.RegisterType(); - factory.LayoutFactory.RegisterType("CsvLayout"); - factory.LayoutFactory.RegisterType("JsonArrayLayout"); - factory.RegisterType(); - factory.LayoutFactory.RegisterType("JsonLayout"); - factory.LayoutFactory.RegisterType("LayoutWithHeaderAndFooter"); - factory.LayoutFactory.RegisterType("Log4JXmlEventLayout"); - factory.LayoutFactory.RegisterType("SimpleLayout"); - factory.RegisterType(); - factory.RegisterType(); - factory.LayoutFactory.RegisterType("XmlLayout"); - factory.TargetFactory.RegisterType("Chainsaw"); -#if !NETSTANDARD1_3 - factory.TargetFactory.RegisterType("ColoredConsole"); -#endif -#if !NETSTANDARD1_3 - factory.RegisterType(); -#endif -#if !NETSTANDARD1_3 - factory.TargetFactory.RegisterType("Console"); -#endif -#if !NETSTANDARD1_3 - factory.RegisterType(); -#endif -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - factory.TargetFactory.RegisterType("Debugger"); -#endif - factory.TargetFactory.RegisterType("DebugSystem"); - factory.TargetFactory.RegisterType("Debug"); -#if NETFRAMEWORK - factory.TargetFactory.RegisterType("EventLog"); -#endif - factory.TargetFactory.RegisterType("File"); -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - factory.TargetFactory.RegisterType("Mail"); - factory.TargetFactory.RegisterType("Email"); - factory.TargetFactory.RegisterType("Smtp"); - factory.TargetFactory.RegisterType("SmtpClient"); -#endif - factory.TargetFactory.RegisterType("Memory"); - factory.RegisterType(); - factory.TargetFactory.RegisterType("MethodCall"); - factory.TargetFactory.RegisterType("Network"); - factory.RegisterType(); - factory.TargetFactory.RegisterType("NLogViewer"); - factory.TargetFactory.RegisterType("Null"); - factory.RegisterType(); -#if !NETSTANDARD1_3 - factory.TargetFactory.RegisterType("Trace"); - factory.TargetFactory.RegisterType("TraceSystem"); -#endif - factory.TargetFactory.RegisterType("WebService"); - factory.TargetFactory.RegisterType("AsyncWrapper"); - factory.TargetFactory.RegisterType("AutoFlushWrapper"); - factory.TargetFactory.RegisterType("BufferingWrapper"); - factory.TargetFactory.RegisterType("FallbackGroup"); - factory.RegisterType(); - factory.TargetFactory.RegisterType("FilteringWrapper"); - factory.TargetFactory.RegisterType("GroupByWrapper"); - factory.TargetFactory.RegisterType("LimitingWrapper"); - factory.TargetFactory.RegisterType("PostFilteringWrapper"); - factory.TargetFactory.RegisterType("RandomizeGroup"); - factory.TargetFactory.RegisterType("RepeatingWrapper"); - factory.TargetFactory.RegisterType("RetryingWrapper"); - factory.TargetFactory.RegisterType("RoundRobinGroup"); - factory.TargetFactory.RegisterType("SplitGroup"); - factory.TimeSourceFactory.RegisterType("AccurateLocal"); - factory.TimeSourceFactory.RegisterType("AccurateUTC"); - factory.TimeSourceFactory.RegisterType("FastLocal"); - factory.TimeSourceFactory.RegisterType("FastUTC"); - factory.ConditionMethodFactory.RegisterOneParameter("length", (logEvent, arg1) => NLog.Conditions.ConditionMethods.Length(arg1?.ToString())); - factory.ConditionMethodFactory.RegisterTwoParameters("equals", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterTwoParameters("strequals", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterThreeParameters("strequals", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); - factory.ConditionMethodFactory.RegisterTwoParameters("contains", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Contains(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterThreeParameters("contains", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.Contains(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); - factory.ConditionMethodFactory.RegisterTwoParameters("starts-with", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.StartsWith(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterThreeParameters("starts-with", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.StartsWith(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); - factory.ConditionMethodFactory.RegisterTwoParameters("ends-with", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.EndsWith(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterThreeParameters("ends-with", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.EndsWith(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); - factory.ConditionMethodFactory.RegisterTwoParameters("regex-matches", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.RegexMatches(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterThreeParameters("regex-matches", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.RegexMatches(arg1?.ToString(), arg2?.ToString(), arg3?.ToString() ?? string.Empty)); -#pragma warning restore CS0618 // Type or member is obsolete + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("length")) + factory.GetConditionMethodFactory().RegisterOneParameter("length", (logEvent, arg1) => NLog.Conditions.ConditionMethods.Length(arg1?.ToString())); + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("equals")) + factory.GetConditionMethodFactory().RegisterTwoParameters("equals", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString())); + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("strequals")) + { + factory.GetConditionMethodFactory().RegisterTwoParameters("strequals", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString())); + factory.GetConditionMethodFactory().RegisterThreeParameters("strequals", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); + } + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("contains")) + { + factory.GetConditionMethodFactory().RegisterTwoParameters("contains", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Contains(arg1?.ToString(), arg2?.ToString())); + factory.GetConditionMethodFactory().RegisterThreeParameters("contains", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.Contains(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); + } + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("starts-with")) + { + factory.GetConditionMethodFactory().RegisterTwoParameters("starts-with", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.StartsWith(arg1?.ToString(), arg2?.ToString())); + factory.GetConditionMethodFactory().RegisterThreeParameters("starts-with", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.StartsWith(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); + } + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("ends-with")) + { + factory.GetConditionMethodFactory().RegisterTwoParameters("ends-with", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.EndsWith(arg1?.ToString(), arg2?.ToString())); + factory.GetConditionMethodFactory().RegisterThreeParameters("ends-with", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.EndsWith(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); + } } +#pragma warning restore CS0618 // Type or member is obsolete } } diff --git a/src/NLog/Config/AssemblyExtensionTypes.tt b/src/NLog/Config/AssemblyExtensionTypes.tt index bb77e0cafb..92f9797c36 100644 --- a/src/NLog/Config/AssemblyExtensionTypes.tt +++ b/src/NLog/Config/AssemblyExtensionTypes.tt @@ -48,111 +48,102 @@ namespace NLog.Config /// internal static class AssemblyExtensionTypes { - public static void RegisterTypes(ConfigurationItemFactory factory) - { #pragma warning disable CS0618 // Type or member is obsolete - factory.RegisterTypeProperties(() => null); - factory.RegisterTypeProperties(() => null); <# - var types = typeof(NLog.LogFactory).Assembly.GetTypes().OrderBy(t => t.ToString()); - - var netFramework = new string[] - { + string[] NetFrameworkOnly = new [] { "NLog.LayoutRenderers.AppSettingLayoutRenderer", "NLog.Targets.EventLogTarget", }; - var netStandard2 = new string[] - { - "NLog.Targets.MailTarget", - "NLog.Targets.DebuggerTarget", - "NLog.LayoutRenderers.EnvironmentUserLayoutRenderer", - "NLog.LayoutRenderers.IdentityLayoutRenderer", - "NLog.LayoutRenderers.LocalIpAddressLayoutRenderer", - "NLog.LayoutRenderers.SpecialFolderApplicationDataLayoutRenderer", - "NLog.LayoutRenderers.SpecialFolderCommonApplicationDataLayoutRenderer", - "NLog.LayoutRenderers.SpecialFolderLayoutRenderer", - "NLog.LayoutRenderers.SpecialFolderLocalApplicationDataLayoutRenderer", - "NLog.LayoutRenderers.TraceActivityIdLayoutRenderer", - "NLog.LayoutRenderers.TraceActivityIdLayoutRenderer", - }; - var netStandard15 = new string[] - { - "NLog.LayoutRenderers.NLogDirLayoutRenderer", - "NLog.LayoutRenderers.ProcessDirLayoutRenderer", - "NLog.LayoutRenderers.ProcessIdLayoutRenderer", - "NLog.LayoutRenderers.ProcessInfoLayoutRenderer", - "NLog.LayoutRenderers.ProcessNameLayoutRenderer", - "NLog.Targets.ColoredConsoleTarget", - "NLog.Targets.ConsoleRowHighlightingRule", - "NLog.Targets.ConsoleWordHighlightingRule", - "NLog.Targets.ConsoleTarget", - "NLog.Targets.TraceTarget", - }; - - foreach(var type in types) - { - if (type.IsAbstract || type.IsPrimitive || !type.IsClass || type.IsNested) - continue; - - if (netFramework.Contains(type.ToString())) - { + Type[] AllTypes = typeof(NLog.LogFactory).Assembly.GetTypes().Where(t => !t.IsAbstract && !t.IsPrimitive && t.IsClass && !t.IsNested && !NetFrameworkOnly.Contains(t.ToString())).OrderBy(t => t.ToString()).ToArray(); #> -#if !NETSTANDARD -<# - } - else if (netStandard2.Contains(type.ToString())) + public static void RegisterTargetTypes(ConfigurationItemFactory factory, bool skipCheckExists) { -#> -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 + factory.RegisterTypeProperties(() => null); +#if NETFRAMEWORK + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("EventLog")) + factory.GetTargetFactory().RegisterType("EventLog"); +#endif <# - } - else if (netStandard15.Contains(type.ToString())) + foreach(var type in AllTypes) + { + if (typeof(NLog.Targets.Target).IsAssignableFrom(type)) { + var targetAttributes = type.GetCustomAttributes(false); + foreach (var targetAlias in targetAttributes) + { + var targetAliasName = targetAlias.Name; #> -#if !NETSTANDARD1_3 + if (skipCheckExists || !factory.GetTargetFactory().CheckTypeAliasExists("<#= targetAliasName #>")) + factory.GetTargetFactory().RegisterType<<#= type #>>("<#= targetAliasName #>"); <# + } } - - if (!type.IsPublic) + else if (type.ToString().StartsWith("NLog.Targets")) { var configAttribute = type.GetCustomAttributes(true); if (configAttribute?.Any() == true) { #> - factory.RegisterTypeProperties<<#= type #>>(() => null); + factory.RegisterType<<#= type #>>(); <# } } - else if (typeof(NLog.Targets.Target).IsAssignableFrom(type)) + } +#> + } + + public static void RegisterLayoutTypes(ConfigurationItemFactory factory, bool skipCheckExists) { - var targetAttributes = type.GetCustomAttributes(false); - foreach (var targetAlias in targetAttributes) + factory.RegisterTypeProperties(() => null); +<# + foreach(var type in AllTypes) + { + if (typeof(NLog.Layouts.Layout).IsAssignableFrom(type)) + { + var layoutAttributes = type.GetCustomAttributes(false); + foreach (var layoutAlias in layoutAttributes) { - var targetAliasName = targetAlias.Name; + var layoutAliasName = layoutAlias.Name; #> - factory.TargetFactory.RegisterType<<#= type #>>("<#= targetAliasName #>"); + if (skipCheckExists || !factory.GetLayoutFactory().CheckTypeAliasExists("<#= layoutAliasName #>")) + factory.GetLayoutFactory().RegisterType<<#= type #>>("<#= layoutAliasName #>"); <# } } - else if (typeof(NLog.Layouts.Layout).IsAssignableFrom(type)) + else if (type.ToString().StartsWith("NLog.Layouts")) { - var layoutAttributes = type.GetCustomAttributes(false); - foreach (var layoutAlias in layoutAttributes) + var configAttribute = type.GetCustomAttributes(true); + if (configAttribute?.Any() == true) { - var layoutAliasName = layoutAlias.Name; #> - factory.LayoutFactory.RegisterType<<#= type #>>("<#= layoutAliasName #>"); + factory.RegisterType<<#= type #>>(); <# } } - else if (typeof(NLog.LayoutRenderers.LayoutRenderer).IsAssignableFrom(type)) + } +#> + } + + public static void RegisterLayoutRendererTypes(ConfigurationItemFactory factory, bool skipCheckExists) + { +#if NETFRAMEWORK + factory.GetLayoutRendererFactory().RegisterType("appsetting"); +#endif + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); +<# + foreach(var type in AllTypes) + { + if (typeof(NLog.LayoutRenderers.LayoutRenderer).IsAssignableFrom(type)) { var layoutAttributes = type.GetCustomAttributes(false); foreach (var layoutAlias in layoutAttributes) { var layoutAliasName = layoutAlias.Name; #> - factory.LayoutRendererFactory.RegisterType<<#= type #>>("<#= layoutAliasName #>"); + if (skipCheckExists || !factory.GetLayoutRendererFactory().CheckTypeAliasExists("<#= layoutAliasName #>")) + factory.GetLayoutRendererFactory().RegisterType<<#= type #>>("<#= layoutAliasName #>"); <# } @@ -161,33 +152,74 @@ namespace NLog.Config { var layoutAliasName = layoutAlias.Name; #> - factory.AmbientRendererFactory.RegisterType<<#= type #>>("<#= layoutAliasName #>"); + if (skipCheckExists || !factory.GetAmbientPropertyFactory().CheckTypeAliasExists("<#= layoutAliasName #>")) + factory.GetAmbientPropertyFactory().RegisterType<<#= type #>>("<#= layoutAliasName #>"); <# } } - else if (typeof(NLog.Filters.Filter).IsAssignableFrom(type)) + else if (type.ToString().StartsWith("NLog.LayoutRenderers")) + { + var configAttribute = type.GetCustomAttributes(true); + if (configAttribute?.Any() == true) + { +#> + factory.RegisterType<<#= type #>>(); +<# + } + } + } +#> + } + + public static void RegisterFilterTypes(ConfigurationItemFactory factory, bool skipCheckExists) + { +<# + foreach(var type in AllTypes) + { + if (typeof(NLog.Filters.Filter).IsAssignableFrom(type)) { var filterAttributes = type.GetCustomAttributes(false); foreach (var filterAlias in filterAttributes) { - var filterName = filterAlias.Name; + var filterAliasName = filterAlias.Name; #> - factory.FilterFactory.RegisterType<<#= type #>>("<#= filterName #>"); + if (skipCheckExists || !factory.GetFilterFactory().CheckTypeAliasExists("<#= filterAliasName #>")) + factory.GetFilterFactory().RegisterType<<#= type #>>("<#= filterAliasName #>"); <# } } - else if (typeof(NLog.Time.TimeSource).IsAssignableFrom(type)) + else if (type.ToString().StartsWith("NLog.Filters")) + { + var configAttribute = type.GetCustomAttributes(true); + if (configAttribute?.Any() == true) + { +#> + factory.RegisterType<<#= type #>>(); +<# + } + } + } +#> + } + + public static void RegisterTimeSourceTypes(ConfigurationItemFactory factory, bool skipCheckExists) + { +<# + foreach(var type in AllTypes) + { + if (typeof(NLog.Time.TimeSource).IsAssignableFrom(type)) { var timeSourceAttribute = type.GetCustomAttributes(false); foreach (var timeAlias in timeSourceAttribute) { - var timeSourceName = timeAlias.Name; + var timeAliasName = timeAlias.Name; #> - factory.TimeSourceFactory.RegisterType<<#= type #>>("<#= timeSourceName #>"); + if (skipCheckExists || !factory.GetTimeSourceFactory().CheckTypeAliasExists("<#= timeAliasName #>")) + factory.GetTimeSourceFactory().RegisterType<<#= type #>>("<#= timeAliasName #>"); <# } } - else + else if (type.ToString().StartsWith("NLog.Time")) { var configAttribute = type.GetCustomAttributes(true); if (configAttribute?.Any() == true) @@ -197,29 +229,49 @@ namespace NLog.Config <# } } + } +#> + } - if (netFramework.Contains(type.ToString()) || netStandard2.Contains(type.ToString()) || netStandard15.Contains(type.ToString())) + public static void RegisterConditionTypes(ConfigurationItemFactory factory, bool skipCheckExists) + { +<# + foreach(var type in AllTypes) + { + if (typeof(NLog.Conditions.ConditionExpression).IsAssignableFrom(type)) { #> -#endif + factory.RegisterTypeProperties<<#= type #>>(() => null); <# } } #> - factory.ConditionMethodFactory.RegisterOneParameter("length", (logEvent, arg1) => NLog.Conditions.ConditionMethods.Length(arg1?.ToString())); - factory.ConditionMethodFactory.RegisterTwoParameters("equals", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterTwoParameters("strequals", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterThreeParameters("strequals", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); - factory.ConditionMethodFactory.RegisterTwoParameters("contains", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Contains(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterThreeParameters("contains", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.Contains(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); - factory.ConditionMethodFactory.RegisterTwoParameters("starts-with", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.StartsWith(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterThreeParameters("starts-with", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.StartsWith(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); - factory.ConditionMethodFactory.RegisterTwoParameters("ends-with", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.EndsWith(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterThreeParameters("ends-with", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.EndsWith(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); - factory.ConditionMethodFactory.RegisterTwoParameters("regex-matches", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.RegexMatches(arg1?.ToString(), arg2?.ToString())); - factory.ConditionMethodFactory.RegisterThreeParameters("regex-matches", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.RegexMatches(arg1?.ToString(), arg2?.ToString(), arg3?.ToString() ?? string.Empty)); -#pragma warning restore CS0618 // Type or member is obsolete + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("length")) + factory.GetConditionMethodFactory().RegisterOneParameter("length", (logEvent, arg1) => NLog.Conditions.ConditionMethods.Length(arg1?.ToString())); + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("equals")) + factory.GetConditionMethodFactory().RegisterTwoParameters("equals", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString())); + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("strequals")) + { + factory.GetConditionMethodFactory().RegisterTwoParameters("strequals", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString())); + factory.GetConditionMethodFactory().RegisterThreeParameters("strequals", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); + } + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("contains")) + { + factory.GetConditionMethodFactory().RegisterTwoParameters("contains", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Contains(arg1?.ToString(), arg2?.ToString())); + factory.GetConditionMethodFactory().RegisterThreeParameters("contains", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.Contains(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); + } + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("starts-with")) + { + factory.GetConditionMethodFactory().RegisterTwoParameters("starts-with", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.StartsWith(arg1?.ToString(), arg2?.ToString())); + factory.GetConditionMethodFactory().RegisterThreeParameters("starts-with", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.StartsWith(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); + } + if (skipCheckExists || !factory.GetConditionMethodFactory().CheckTypeAliasExists("ends-with")) + { + factory.GetConditionMethodFactory().RegisterTwoParameters("ends-with", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.EndsWith(arg1?.ToString(), arg2?.ToString())); + factory.GetConditionMethodFactory().RegisterThreeParameters("ends-with", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.EndsWith(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true)); + } } +#pragma warning restore CS0618 // Type or member is obsolete } } diff --git a/src/NLog/Config/ConfigSectionHandler.cs b/src/NLog/Config/ConfigSectionHandler.cs index e09dd2c0c1..8ca8779255 100644 --- a/src/NLog/Config/ConfigSectionHandler.cs +++ b/src/NLog/Config/ConfigSectionHandler.cs @@ -51,20 +51,47 @@ namespace NLog.Config ///
public sealed class ConfigSectionHandler : ConfigurationSection { - private XmlLoggingConfiguration _config; + private XmlLoggingConfiguration? _config; + + /// + /// Gets the default object by parsing + /// the application configuration file (app.exe.config). + /// + public static LoggingConfiguration? AppConfig + { + get + { + try + { + object o = System.Configuration.ConfigurationManager.GetSection("nlog"); + return o as LoggingConfiguration; + } + catch (Exception exception) + { + // Load can fail due to an invalid XML file (app.config) etc. + InternalLogger.Error(exception, "Failed loading XML configuration from NLog ConfigSection in application configuration file (app.config / web.config)"); + if (exception.MustBeRethrown()) + throw; + } + + return null; + } + } /// /// Overriding base implementation to just store /// of the relevant app.config section. /// /// The XmlReader that reads from the configuration file. - /// true to serialize only the collection key properties; otherwise, false. + /// to serialize only the collection key properties; otherwise, . protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey) { try { string configFileName = LogFactory.DefaultAppEnvironment.AppDomainConfigurationFile; +#pragma warning disable CS0618 // Type or member is obsolete _config = new XmlLoggingConfiguration(reader, configFileName, LogManager.LogFactory); +#pragma warning restore CS0618 // Type or member is obsolete } catch (Exception exception) { @@ -85,7 +112,7 @@ protected override void DeserializeElement(XmlReader reader, bool serializeColle /// /// A instance, that has been deserialized from app.config. /// - protected override object GetRuntimeObject() + protected override object? GetRuntimeObject() { return _config; } diff --git a/src/NLog/Config/ConfigurationItemFactory.cs b/src/NLog/Config/ConfigurationItemFactory.cs index e6f2f43d2f..dc1adbf403 100644 --- a/src/NLog/Config/ConfigurationItemFactory.cs +++ b/src/NLog/Config/ConfigurationItemFactory.cs @@ -52,9 +52,9 @@ namespace NLog.Config /// /// Supports creating item-instance from their type-alias, when parsing NLog configuration ///
- public class ConfigurationItemFactory + public sealed class ConfigurationItemFactory { - private static ConfigurationItemFactory _defaultInstance; + private static ConfigurationItemFactory? _defaultInstance; internal static readonly object SyncRoot = new object(); @@ -72,60 +72,40 @@ public class ConfigurationItemFactory private readonly Factory _timeSources; private readonly Dictionary _itemFactories = new Dictionary(256); - private struct ItemFactory + private +#if !NETFRAMEWORK + readonly +#endif + struct ItemFactory { public readonly Func> ItemProperties; - public readonly Func ItemCreator; + public readonly Func ItemCreator; - public ItemFactory(Func> itemProperties, Func itemCreator) + public ItemFactory(Func> itemProperties, Func itemCreator) { ItemProperties = itemProperties; ItemCreator = itemCreator; } } - /// - /// Obsolete since dynamic assembly loading is not compatible with publish as trimmed application. - /// Called before the assembly with NLog extensions is being loaded. - /// - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static event EventHandler AssemblyLoading; - /// /// Initializes a new instance of the class. /// public ConfigurationItemFactory() - : this(LogManager.LogFactory.ServiceRepository, null) - { - } - - /// - /// Obsolete since dynamic assembly loading is not compatible with publish as trimmed application. - /// Initializes a new instance of the class. - /// - /// The assemblies to scan for named items. - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public ConfigurationItemFactory(params Assembly[] assemblies) - : this(LogManager.LogFactory.ServiceRepository, null) + : this(LogManager.LogFactory.ServiceRepository) { - foreach (var asm in assemblies) - { - RegisterItemsFromAssembly(asm); - } } - internal ConfigurationItemFactory(ServiceRepository serviceRepository, ConfigurationItemFactory globalDefaultFactory) + internal ConfigurationItemFactory(ServiceRepository serviceRepository) { _serviceRepository = Guard.ThrowIfNull(serviceRepository); - _targets = new Factory(this, globalDefaultFactory?._targets); - _filters = new Factory(this, globalDefaultFactory?._filters); - _layoutRenderers = new LayoutRendererFactory(this, globalDefaultFactory?._layoutRenderers); - _layouts = new Factory(this, globalDefaultFactory?._layouts); - _conditionMethods = new MethodFactory(globalDefaultFactory?._conditionMethods); - _ambientProperties = new Factory(this, globalDefaultFactory?._ambientProperties); - _timeSources = new Factory(this, globalDefaultFactory?._timeSources); + _targets = new Factory(this); + _filters = new Factory(this); + _layoutRenderers = new LayoutRendererFactory(this); + _layouts = new Factory(this); + _conditionMethods = new MethodFactory(); + _ambientProperties = new Factory(this); + _timeSources = new Factory(this); _allFactories = new IFactory[] { _targets, @@ -136,6 +116,7 @@ internal ConfigurationItemFactory(ServiceRepository serviceRepository, Configura _ambientProperties, _timeSources, }; + RegisterType(); } /// @@ -147,39 +128,137 @@ internal ConfigurationItemFactory(ServiceRepository serviceRepository, Configura /// public static ConfigurationItemFactory Default { - get => _defaultInstance ?? (_defaultInstance = BuildDefaultFactory()); + get => _defaultInstance ?? (_defaultInstance = new ConfigurationItemFactory(LogManager.LogFactory.ServiceRepository)); set => _defaultInstance = value; } /// /// Gets the factory. /// - public IFactory TargetFactory => _targets; + public IFactory TargetFactory + { + get + { + if (!_targets.Initialized) + { + _targets.Initialize(skipCheckExists => RegisterAllTargets(skipCheckExists)); + // Targets can depend on filters + if (!_filters.Initialized) + _filters.Initialize(skipCheckExists => RegisterAllFilters(skipCheckExists)); + // Targets can depend on conditions + if (!_conditionMethods.Initialized) + _conditionMethods.Initialize(skipCheckExists => RegisterAllConditionMethods(skipCheckExists)); + // Targets can depend on layouts + if (!_layouts.Initialized) + _layouts.Initialize(skipCheckExists => RegisterAllLayouts(skipCheckExists)); + // Targets can depend on layoutrenderers + if (!_layoutRenderers.Initialized) + _layoutRenderers.Initialize(skipCheckExists => RegisterAllLayoutRenderers(skipCheckExists)); + } + return _targets; + } + } + /// /// Gets the factory. /// - public IFactory LayoutFactory => _layouts; + public IFactory LayoutFactory + { + get + { + if (!_layouts.Initialized) + { + _layouts.Initialize(skipCheckExists => RegisterAllLayouts(skipCheckExists)); + // Layout can depend on layoutrenderers + if (!_layoutRenderers.Initialized) + _layoutRenderers.Initialize(skipCheckExists => RegisterAllLayoutRenderers(skipCheckExists)); + // When-LayoutRenderers depends on conditions + if (!_conditionMethods.Initialized) + _conditionMethods.Initialize(skipCheckExists => RegisterAllConditionMethods(skipCheckExists)); + } + return _layouts; + } + } + /// /// Gets the factory. /// - public IFactory LayoutRendererFactory => _layoutRenderers; + public IFactory LayoutRendererFactory + { + get + { + if (!_layoutRenderers.Initialized) + _layoutRenderers.Initialize(skipCheckExists => RegisterAllLayoutRenderers(skipCheckExists)); + // When-LayoutRenderers depends on conditions + if (!_conditionMethods.Initialized) + _conditionMethods.Initialize(skipCheckExists => RegisterAllConditionMethods(skipCheckExists)); + return _layoutRenderers; + } + } + /// /// Gets the ambient property factory. /// - public IFactory AmbientRendererFactory => _ambientProperties; + public IFactory AmbientRendererFactory + { + get + { + if (!_layoutRenderers.Initialized) + _layoutRenderers.Initialize(skipCheckExists => RegisterAllLayoutRenderers(skipCheckExists)); + // When-LayoutRenderers depends on conditions + if (!_conditionMethods.Initialized) + _conditionMethods.Initialize(skipCheckExists => RegisterAllConditionMethods(skipCheckExists)); + return _ambientProperties; + } + } + /// /// Gets the factory. /// - public IFactory FilterFactory => _filters; + public IFactory FilterFactory + { + get + { + if (!_filters.Initialized) + _filters.Initialize(skipCheckExists => RegisterAllFilters(skipCheckExists)); + // Filters can depend on conditions + if (!_conditionMethods.Initialized) + _conditionMethods.Initialize(skipCheckExists => RegisterAllConditionMethods(skipCheckExists)); + return _filters; + } + } + /// /// Gets the factory. /// - public IFactory TimeSourceFactory => _timeSources; - internal MethodFactory ConditionMethodFactory => _conditionMethods; + public IFactory TimeSourceFactory + { + get + { + if (!_timeSources.Initialized) + _timeSources.Initialize(skipCheckExists => RegisterAllTimeSources(skipCheckExists)); + return _timeSources; + } + } + + internal MethodFactory ConditionMethodFactory + { + get + { + if (!_conditionMethods.Initialized) + _conditionMethods.Initialize(skipCheckExists => RegisterAllConditionMethods(skipCheckExists)); + return _conditionMethods; + } + } internal Factory GetTargetFactory() => _targets; internal Factory GetLayoutFactory() => _layouts; internal LayoutRendererFactory GetLayoutRendererFactory() => _layoutRenderers; + internal Factory GetAmbientPropertyFactory() => _ambientProperties; + internal Factory GetFilterFactory() => _filters; + internal Factory GetTimeSourceFactory() => _timeSources; + internal MethodFactory GetConditionMethodFactory() => _conditionMethods; + internal ICollection ItemTypes { get @@ -189,80 +268,6 @@ internal ICollection ItemTypes } } - /// - /// Obsolete and replaced by with NLog v5.2. - /// Gets or sets the creator delegate used to instantiate configuration objects. - /// - /// - /// By overriding this property, one can enable dependency injection or interception for created objects. - /// - [Obsolete("Instead use NLog.LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public ConfigurationItemCreator CreateInstance { get; set; } = FactoryHelper.CreateInstance; - - /// - /// Obsolete and replaced by with NLog v5.2. - /// Gets the factory. - /// - /// The target factory. - [Obsolete("Instead use LogManager.Setup().SetupExtensions(ext => ext.RegisterTarget()). Marked obsolete with NLog v5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public INamedItemFactory Targets => _targets; - - /// - /// Obsolete and replaced by with NLog v5.2. - /// Gets the factory. - /// - /// The layout factory. - [Obsolete("Instead use LogManager.Setup().SetupExtensions(ext => ext.RegisterLayout()). Marked obsolete with NLog v5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public INamedItemFactory Layouts => _layouts; - - /// - /// Obsolete and replaced by with NLog v5.2. - /// Gets the factory. - /// - /// The layout renderer factory. - [Obsolete("Instead use LogManager.Setup().SetupExtensions(ext => ext.RegisterLayoutRenderer()). Marked obsolete with NLog v5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public INamedItemFactory LayoutRenderers => _layoutRenderers; - - /// - /// Obsolete and replaced by with NLog v5.2. - /// Gets the ambient property factory. - /// - /// The ambient property factory. - [Obsolete("Instead use NLog.LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public INamedItemFactory AmbientProperties => _ambientProperties; - - /// - /// Obsolete and replaced by with NLog v5.2. - /// Gets the factory. - /// - /// The filter factory. - [Obsolete("Instead use NLog.LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public INamedItemFactory Filters => _filters; - - /// - /// Obsolete and replaced by with NLog v5.2. - /// Gets the time source factory. - /// - /// The time source factory. - [Obsolete("Instead use NLog.LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public INamedItemFactory TimeSources => _timeSources; - - /// - /// Obsolete and replaced by with NLog v5.2. - /// Gets the condition method factory. - /// - /// The condition method factory. - [Obsolete("Instead use NLog.LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public INamedItemFactory ConditionMethods => _conditionMethods; - /// /// Obsolete and replaced by with NLog v5.2. /// Gets or sets the JSON serializer to use with @@ -275,42 +280,18 @@ public IJsonConverter JsonConverter set => _serviceRepository.RegisterJsonConverter(value); } - /// - /// Obsolete and replaced by with NLog v5.2. - /// Gets or sets the string serializer to use with - /// - [Obsolete("Instead use NLog.LogManager.Setup().SetupSerialization(s => s.RegisterValueFormatter()) or ResolveService(). Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IValueFormatter ValueFormatter - { - get => _serviceRepository.GetService(); - set => _serviceRepository.RegisterValueFormatter(value); - } - - /// - /// Obsolete and replaced by with NLog v5.2. - /// Gets or sets the parameter converter to use with or - /// - [Obsolete("Instead use LogFactory.ServiceRepository.RegisterService(). Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IPropertyTypeConverter PropertyTypeConverter - { - get => _serviceRepository.GetService(); - set => _serviceRepository.RegisterPropertyTypeConverter(value); - } - /// /// Perform message template parsing and formatting of LogEvent messages (True = Always, False = Never, Null = Auto Detect) /// /// - /// - Null (Auto Detect) : NLog-parser checks for positional parameters, and will then fallback to string.Format-rendering. - /// - True: Always performs the parsing of and rendering of using the NLog-parser (Allows custom formatting with ) - /// - False: Always performs parsing and rendering using string.Format (Fastest if not using structured logging) + /// - (Auto Detect) : NLog-parser checks for positional parameters, and will then fallback to string.Format-rendering. + /// - : Always performs the parsing of and rendering of using the NLog-parser (Allows custom formatting with ) + /// - : Always performs parsing and rendering using string.Format (Fastest if not using structured logging) /// public bool? ParseMessageTemplates { get => _serviceRepository.ResolveParseMessageTemplates(); - set => _serviceRepository.ParseMessageTemplates(value); + set => _serviceRepository.ParseMessageTemplates(LogManager.LogFactory, value); } /// @@ -322,7 +303,7 @@ public bool? ParseMessageTemplates [EditorBrowsable(EditorBrowsableState.Never)] public void RegisterItemsFromAssembly(Assembly assembly) { - RegisterItemsFromAssembly(assembly, string.Empty); + AssemblyLoader.LoadAssembly(this, assembly, string.Empty); } /// @@ -335,120 +316,7 @@ public void RegisterItemsFromAssembly(Assembly assembly) [EditorBrowsable(EditorBrowsableState.Never)] public void RegisterItemsFromAssembly(Assembly assembly, string itemNamePrefix) { - if (AssemblyLoading != null) - { - var args = new AssemblyLoadingEventArgs(assembly); - AssemblyLoading.Invoke(null, args); - if (args.Cancel) - { - InternalLogger.Info("Loading assembly '{0}' is canceled", assembly.FullName); - return; - } - } - - InternalLogger.Debug("ScanAssembly('{0}')", assembly.FullName); - var typesToScan = AssemblyHelpers.SafeGetTypes(assembly); - if (typesToScan?.Length > 0) - { - string assemblyName = string.Empty; - - if (ReferenceEquals(assembly, typeof(LogFactory).GetAssembly())) - { - typesToScan = typesToScan.Where(t => t.IsPublic() && t.IsClass()).ToArray(); - } - else - { - assemblyName = new AssemblyName(assembly.FullName).Name; - PreloadAssembly(typesToScan); - } - - lock (SyncRoot) - { - foreach (IFactory f in _allFactories) - { - f.ScanTypes(typesToScan, assemblyName, itemNamePrefix); - } - } - } - } - - /// - /// Obsolete since dynamic assembly loading is not compatible with publish as trimmed application. - /// Call Preload for NLogPackageLoader - /// - /// - /// Every package could implement a class "NLogPackageLoader" (namespace not important) with the public static method "Preload" (no arguments) - /// This method will be called just before registering all items in the assembly. - /// - /// - [Obsolete("Instead use NLog.LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] - [UnconditionalSuppressMessage("Trimming - Ignore since obsolete", "IL2072")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void PreloadAssembly(Type[] typesToScan) - { - var types = typesToScan.Where(t => t.Name.Equals("NLogPackageLoader", StringComparison.OrdinalIgnoreCase)); - - foreach (var type in types) - { - CallPreload(type); - } - } - - /// - /// Call the Preload method for . The Preload method must be static. - /// - /// - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - private void CallPreload([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) - { - if (type is null) - { - return; - } - - InternalLogger.Debug("Found for preload'{0}'", type.FullName); - var preloadMethod = type.GetMethod("Preload"); - if (preloadMethod != null) - { - if (preloadMethod.IsStatic) - { - InternalLogger.Debug("NLogPackageLoader contains Preload method"); - - //only static, so first param null - try - { - var parameters = CreatePreloadParameters(preloadMethod, this); - - preloadMethod.Invoke(null, parameters); - InternalLogger.Debug("Preload successfully invoked for '{0}'", type.FullName); - } - catch (Exception e) - { - InternalLogger.Warn(e, "Invoking Preload for '{0}' failed", type.FullName); - } - } - else - { - InternalLogger.Debug("NLogPackageLoader contains a preload method, but isn't static"); - } - } - else - { - InternalLogger.Debug("{0} doesn't contain Preload method", type.FullName); - } - } - - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - private static object[] CreatePreloadParameters(MethodInfo preloadMethod, ConfigurationItemFactory configurationItemFactory) - { - var firstParam = preloadMethod.GetParameters().FirstOrDefault(); - object[] parameters = null; - if (firstParam?.ParameterType == typeof(ConfigurationItemFactory)) - { - parameters = new object[] { configurationItemFactory }; - } - - return parameters; + AssemblyLoader.LoadAssembly(this, assembly, itemNamePrefix); } /// @@ -473,13 +341,13 @@ public void Clear() /// The item name prefix. [Obsolete("Instead use NLog.LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] [EditorBrowsable(EditorBrowsableState.Never)] - public void RegisterType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string itemNamePrefix) + public void RegisterType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string? itemNamePrefix) { lock (SyncRoot) { foreach (IFactory f in _allFactories) { - f.RegisterType(type, itemNamePrefix); + f.RegisterType(type, itemNamePrefix ?? string.Empty); } } } @@ -497,13 +365,13 @@ public void RegisterType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTy } } - internal void RegisterTypeProperties<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] TType>(Func itemCreator) + internal void RegisterTypeProperties<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] TType>(Func itemCreator) { lock (SyncRoot) { if (!_itemFactories.ContainsKey(typeof(TType))) { - Dictionary properties = null; + Dictionary? properties = null; var itemProperties = new Func>(() => properties ?? (properties = typeof(TType).GetProperties(BindingFlags.Public | BindingFlags.Instance).ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase))); var itemFactory = new ItemFactory(itemProperties, itemCreator); _itemFactories[typeof(TType)] = itemFactory; @@ -511,13 +379,13 @@ public void RegisterType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTy } } - internal void RegisterTypeProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] Type itemType, Func itemCreator) + internal void RegisterTypeProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] Type itemType, Func itemCreator) { lock (SyncRoot) { if (!_itemFactories.ContainsKey(itemType)) { - Dictionary properties = null; + Dictionary? properties = null; var itemProperties = new Func>(() => properties ?? (properties = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase))); var itemFactory = new ItemFactory(itemProperties, itemCreator); _itemFactories[itemType] = itemFactory; @@ -535,13 +403,14 @@ internal Dictionary TryGetTypeProperties(Type itemType) } } - if (itemType.IsAbstract()) + if (itemType.IsAbstract) return new Dictionary(); - if (itemType.IsGenericType() && itemType.GetGenericTypeDefinition() == typeof(Layout<>)) + if (itemType.IsGenericType && itemType.GetGenericTypeDefinition() == typeof(Layout<>)) return new Dictionary(); #pragma warning disable CS0618 // Type or member is obsolete + InternalLogger.Debug("Object reflection needed to configure instance of type: {0}", itemType); return ResolveTypePropertiesLegacy(itemType); #pragma warning restore CS0618 // Type or member is obsolete } @@ -552,7 +421,6 @@ internal Dictionary TryGetTypeProperties(Type itemType) [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] private Dictionary ResolveTypePropertiesLegacy(Type itemType) { - InternalLogger.Debug("Object reflection needed to configure instance of type: {0}", itemType); Dictionary properties = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); lock (SyncRoot) { @@ -561,9 +429,9 @@ private Dictionary ResolveTypePropertiesLegacy(Type itemTy return properties; } - internal bool TryCreateInstance(Type itemType, out object instance) + internal bool TryCreateInstance(Type itemType, out object? instance) { - Func itemCreator = null; + Func? itemCreator = null; lock (SyncRoot) { @@ -574,6 +442,7 @@ internal bool TryCreateInstance(Type itemType, out object instance) if (itemCreator is null) { #pragma warning disable CS0618 // Type or member is obsolete + InternalLogger.Debug("Object reflection needed to create instance of type: {0}", itemType); instance = ResolveCreateInstanceLegacy(itemType); #pragma warning restore CS0618 // Type or member is obsolete } @@ -589,10 +458,9 @@ internal bool TryCreateInstance(Type itemType, out object instance) [UnconditionalSuppressMessage("Trimming - Ignore since obsolete", "IL2070")] [UnconditionalSuppressMessage("Trimming - Ignore since obsolete", "IL2072")] [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - private object ResolveCreateInstanceLegacy(Type itemType) + private object? ResolveCreateInstanceLegacy(Type itemType) { - InternalLogger.Debug("Object reflection needed to create instance of type: {0}", itemType); - Dictionary properties = null; + Dictionary? properties = null; var itemProperties = new Func>(() => properties ?? (properties = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase))); var itemFactory = new ItemFactory(itemProperties, () => Activator.CreateInstance(itemType)); lock (SyncRoot) @@ -603,52 +471,95 @@ private object ResolveCreateInstanceLegacy(Type itemType) return itemFactory.ItemCreator.Invoke(); } - /// - /// Builds the default configuration item factory. - /// - /// Default factory. - private static ConfigurationItemFactory BuildDefaultFactory() + private static void SafeRegisterNamedType(Factory factory, string typeAlias, string fullTypeName, bool skipCheckExists) + where TBaseType : class + where TAttributeType : NameBaseAttribute { - var factory = new ConfigurationItemFactory(LogManager.LogFactory.ServiceRepository, null); - lock (SyncRoot) - { - AssemblyExtensionTypes.RegisterTypes(factory); #pragma warning disable CS0618 // Type or member is obsolete - factory.RegisterExternalItems(); + if (skipCheckExists || !factory.CheckTypeAliasExists(typeAlias)) + factory.RegisterNamedType(typeAlias, fullTypeName); #pragma warning restore CS0618 // Type or member is obsolete - } - return factory; } - /// - /// Registers items in using late-bound types, so that we don't need a reference to the dll. - /// - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - private void RegisterExternalItems() + private void RegisterAllTargets(bool skipCheckExists) + { + AssemblyExtensionTypes.RegisterTargetTypes(this, skipCheckExists); + SafeRegisterNamedType(_targets, "diagnosticlistener", "NLog.Targets.DiagnosticListenerTarget, NLog.DiagnosticSource", skipCheckExists); + SafeRegisterNamedType(_targets, "database", "NLog.Targets.DatabaseTarget, NLog.Database", skipCheckExists); + SafeRegisterNamedType(_targets, "atomfile", "NLog.Targets.AtomicFileTarget, NLog.Targets.AtomicFile", skipCheckExists); + SafeRegisterNamedType(_targets, "atomicfile", "NLog.Targets.AtomicFileTarget, NLog.Targets.AtomicFile", skipCheckExists); +#if !NETFRAMEWORK + SafeRegisterNamedType(_targets, "eventlog", "NLog.Targets.EventLogTarget, NLog.WindowsEventLog", skipCheckExists); +#endif + SafeRegisterNamedType(_targets, "gzipfile", "NLog.Targets.GZipFileTarget, NLog.Targets.GZipFile", skipCheckExists); + SafeRegisterNamedType(_targets, "impersonatingwrapper", "NLog.Targets.Wrappers.ImpersonatingTargetWrapper, NLog.WindowsIdentity", skipCheckExists); + SafeRegisterNamedType(_targets, "logreceiverservice", "NLog.Targets.LogReceiverWebServiceTarget, NLog.Wcf", skipCheckExists); + SafeRegisterNamedType(_targets, "outputdebugstring", "NLog.Targets.OutputDebugStringTarget, NLog.OutputDebugString", skipCheckExists); + SafeRegisterNamedType(_targets, "network", "NLog.Targets.NetworkTarget, NLog.Targets.Network", skipCheckExists); + SafeRegisterNamedType(_targets, "log4jxml", "NLog.Targets.Log4JXmlTarget, NLog.Targets.Network", skipCheckExists); + SafeRegisterNamedType(_targets, "chainsaw", "NLog.Targets.Log4JXmlTarget, NLog.Targets.Network", skipCheckExists); + SafeRegisterNamedType(_targets, "nlogviewer", "NLog.Targets.Log4JXmlTarget, NLog.Targets.Network", skipCheckExists); + SafeRegisterNamedType(_targets, "syslog", "NLog.Targets.SyslogTarget, NLog.Targets.Network", skipCheckExists); + SafeRegisterNamedType(_targets, "gelf", "NLog.Targets.GelfTarget, NLog.Targets.Network", skipCheckExists); + SafeRegisterNamedType(_targets, "mail", "NLog.Targets.MailTarget, NLog.Targets.Mail", skipCheckExists); + SafeRegisterNamedType(_targets, "email", "NLog.Targets.MailTarget, NLog.Targets.Mail", skipCheckExists); + SafeRegisterNamedType(_targets, "smtp", "NLog.Targets.MailTarget, NLog.Targets.Mail", skipCheckExists); + SafeRegisterNamedType(_targets, "mailkit", "NLog.MailKit.MailTarget, NLog.MailKit", skipCheckExists); + SafeRegisterNamedType(_targets, "performancecounter", "NLog.Targets.PerformanceCounterTarget, NLog.PerformanceCounter", skipCheckExists); + SafeRegisterNamedType(_targets, "richtextbox", "NLog.Windows.Forms.RichTextBoxTarget, NLog.Windows.Forms", skipCheckExists); + SafeRegisterNamedType(_targets, "messagebox", "NLog.Windows.Forms.MessageBoxTarget, NLog.Windows.Forms", skipCheckExists); + SafeRegisterNamedType(_targets, "formcontrol", "NLog.Windows.Forms.FormControlTarget, NLog.Windows.Forms", skipCheckExists); + SafeRegisterNamedType(_targets, "toolstripitem", "NLog.Windows.Forms.ToolStripItemTarget, NLog.Windows.Forms", skipCheckExists); + SafeRegisterNamedType(_targets, "trace", "NLog.Targets.TraceTarget, NLog.Targets.Trace", skipCheckExists); + SafeRegisterNamedType(_targets, "tracesystem", "NLog.Targets.TraceTarget, NLog.Targets.Trace", skipCheckExists); + SafeRegisterNamedType(_targets, "webservice", "NLog.Targets.WebServiceTarget, NLog.Targets.WebService", skipCheckExists); + } + + private void RegisterAllLayouts(bool skipCheckExists) { + AssemblyExtensionTypes.RegisterLayoutTypes(this, skipCheckExists); #if !NET35 && !NET40 - _layouts.RegisterNamedType("microsoftconsolejsonlayout", "NLog.Extensions.Logging.MicrosoftConsoleJsonLayout, NLog.Extensions.Logging"); - _layoutRenderers.RegisterNamedType("configsetting", "NLog.Extensions.Logging.ConfigSettingLayoutRenderer, NLog.Extensions.Logging"); - _layoutRenderers.RegisterNamedType("microsoftconsolelayout", "NLog.Extensions.Logging.MicrosoftConsoleLayoutRenderer, NLog.Extensions.Logging"); + SafeRegisterNamedType(_layouts, "microsoftconsolejsonlayout", "NLog.Extensions.Logging.MicrosoftConsoleJsonLayout, NLog.Extensions.Logging", skipCheckExists); #endif - _layoutRenderers.RegisterNamedType("performancecounter", "NLog.LayoutRenderers.PerformanceCounterLayoutRenderer, NLog.PerformanceCounter"); - _layoutRenderers.RegisterNamedType("registry", "NLog.LayoutRenderers.RegistryLayoutRenderer, NLog.WindowsRegistry"); - _layoutRenderers.RegisterNamedType("windows-identity", "NLog.LayoutRenderers.WindowsIdentityLayoutRenderer, NLog.WindowsIdentity"); - _layoutRenderers.RegisterNamedType("rtblink", "NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer, NLog.Windows.Forms"); - _layoutRenderers.RegisterNamedType("activity", "NLog.LayoutRenderers.ActivityTraceLayoutRenderer, NLog.DiagnosticSource"); - _targets.RegisterNamedType("diagnosticlistener", "NLog.Targets.DiagnosticListenerTarget, NLog.DiagnosticSource"); - _targets.RegisterNamedType("database", "NLog.Targets.DatabaseTarget, NLog.Database"); -#if NETSTANDARD - _targets.RegisterNamedType("eventlog", "NLog.Targets.EventLogTarget, NLog.WindowsEventLog"); + SafeRegisterNamedType(_layouts, "sysloglayout", "NLog.Targets.SyslogLayout, NLog.Targets.Network", skipCheckExists); + SafeRegisterNamedType(_layouts, "log4jxmllayout", "NLog.Targets.Log4JXmlEventLayout, NLog.Targets.Network", skipCheckExists); + SafeRegisterNamedType(_layouts, "log4jxmleventlayout", "NLog.Targets.Log4JXmlEventLayout, NLog.Targets.Network", skipCheckExists); + SafeRegisterNamedType(_layouts, "gelflayout", "NLog.Targets.GelfLayout, NLog.Targets.Network", skipCheckExists); + } + + private void RegisterAllLayoutRenderers(bool skipCheckExists) + { + AssemblyExtensionTypes.RegisterLayoutRendererTypes(this, skipCheckExists); +#if !NET35 && !NET40 + SafeRegisterNamedType(_layoutRenderers, "configsetting", "NLog.Extensions.Logging.ConfigSettingLayoutRenderer, NLog.Extensions.Logging", skipCheckExists); + SafeRegisterNamedType(_layoutRenderers, "microsoftconsolelayout", "NLog.Extensions.Logging.MicrosoftConsoleLayoutRenderer, NLog.Extensions.Logging", skipCheckExists); + SafeRegisterNamedType(_layoutRenderers, "hostappname", "NLog.Extensions.Hosting.HostAppNameLayoutRenderer, NLog.Extensions.Hosting", skipCheckExists); + SafeRegisterNamedType(_layoutRenderers, "hostenvironment", "NLog.Extensions.Hosting.HostEnvironmentLayoutRenderer, NLog.Extensions.Hosting", skipCheckExists); + SafeRegisterNamedType(_layoutRenderers, "hostrootdir", "NLog.Extensions.Hosting.HostRootDirLayoutRenderer, NLog.Extensions.Hosting", skipCheckExists); #endif - _targets.RegisterNamedType("impersonatingwrapper", "NLog.Targets.Wrappers.ImpersonatingTargetWrapper, NLog.WindowsIdentity"); - _targets.RegisterNamedType("logreceiverservice", "NLog.Targets.LogReceiverWebServiceTarget, NLog.Wcf"); - _targets.RegisterNamedType("outputdebugstring", "NLog.Targets.OutputDebugStringTarget, NLog.OutputDebugString"); - _targets.RegisterNamedType("performancecounter", "NLog.Targets.PerformanceCounterTarget, NLog.PerformanceCounter"); - _targets.RegisterNamedType("richtextbox", "NLog.Windows.Forms.RichTextBoxTarget, NLog.Windows.Forms"); - _targets.RegisterNamedType("messagebox", "NLog.Windows.Forms.MessageBoxTarget, NLog.Windows.Forms"); - _targets.RegisterNamedType("formcontrol", "NLog.Windows.Forms.FormControlTarget, NLog.Windows.Forms"); - _targets.RegisterNamedType("toolstripitem", "NLog.Windows.Forms.ToolStripItemTarget, NLog.Windows.Forms"); + SafeRegisterNamedType(_layoutRenderers, "localip", "NLog.LayoutRenderers.LocalIpAddressLayoutRenderer, NLog.Targets.Network", skipCheckExists); + SafeRegisterNamedType(_layoutRenderers, "performancecounter", "NLog.LayoutRenderers.PerformanceCounterLayoutRenderer, NLog.PerformanceCounter", skipCheckExists); + SafeRegisterNamedType(_layoutRenderers, "registry", "NLog.LayoutRenderers.RegistryLayoutRenderer, NLog.WindowsRegistry", skipCheckExists); + SafeRegisterNamedType(_layoutRenderers, "regexreplace", "NLog.LayoutRenderers.RegexReplaceLayoutRendererWrapper, NLog.RegEx", skipCheckExists); + SafeRegisterNamedType(_layoutRenderers, "windowsidentity", "NLog.LayoutRenderers.WindowsIdentityLayoutRenderer, NLog.WindowsIdentity", skipCheckExists); + SafeRegisterNamedType(_layoutRenderers, "rtblink", "NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer, NLog.Windows.Forms", skipCheckExists); + SafeRegisterNamedType(_layoutRenderers, "activity", "NLog.LayoutRenderers.ActivityTraceLayoutRenderer, NLog.DiagnosticSource", skipCheckExists); + SafeRegisterNamedType(_layoutRenderers, "activityid", "NLog.LayoutRenderers.TraceActivityIdLayoutRenderer, NLog.Targets.Trace", skipCheckExists); + } + + private void RegisterAllFilters(bool skipCheckExists) + { + AssemblyExtensionTypes.RegisterFilterTypes(this, skipCheckExists); + } + + private void RegisterAllConditionMethods(bool skipCheckExists) + { + AssemblyExtensionTypes.RegisterConditionTypes(this, skipCheckExists); + } + + private void RegisterAllTimeSources(bool skipCheckExists) + { + AssemblyExtensionTypes.RegisterTimeSourceTypes(this, skipCheckExists); } } } diff --git a/src/NLog/Config/DefaultParameterAttribute.cs b/src/NLog/Config/DefaultParameterAttribute.cs index 8f11b15bcb..2358261460 100644 --- a/src/NLog/Config/DefaultParameterAttribute.cs +++ b/src/NLog/Config/DefaultParameterAttribute.cs @@ -31,11 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using JetBrains.Annotations; - namespace NLog.Config { using System; + using JetBrains.Annotations; /// /// Attribute used to mark the default parameters for layout renderers. diff --git a/src/NLog/Config/DynamicLogLevelFilter.cs b/src/NLog/Config/DynamicLogLevelFilter.cs index 0d1d95a9c9..a21b363bc9 100644 --- a/src/NLog/Config/DynamicLogLevelFilter.cs +++ b/src/NLog/Config/DynamicLogLevelFilter.cs @@ -45,15 +45,15 @@ namespace NLog.Config internal sealed class DynamicLogLevelFilter : ILoggingRuleLevelFilter { private readonly LoggingRule _loggingRule; - private readonly SimpleLayout _levelFilter; - private readonly SimpleLayout _finalMinLevelFilter; + private readonly SimpleLayout? _levelFilter; + private readonly SimpleLayout? _finalMinLevelFilter; private KeyValuePair _activeFilter; public bool[] LogLevels => GenerateLogLevels(); - public LogLevel FinalMinLevel => GenerateFinalMinLevel(); + public LogLevel? FinalMinLevel => GenerateFinalMinLevel(); - public DynamicLogLevelFilter(LoggingRule loggingRule, SimpleLayout levelFilter, SimpleLayout finalMinLevelFilter) + public DynamicLogLevelFilter(LoggingRule loggingRule, SimpleLayout? levelFilter, SimpleLayout? finalMinLevelFilter) { _loggingRule = loggingRule; _levelFilter = levelFilter; @@ -68,7 +68,7 @@ public LoggingRuleLevelFilter GetSimpleFilterForUpdate() private bool[] GenerateLogLevels() { - var levelFilter = _levelFilter.Render(LogEventInfo.CreateNullEvent()); + var levelFilter = _levelFilter?.Render(LogEventInfo.CreateNullEvent())?.Trim() ?? string.Empty; if (string.IsNullOrEmpty(levelFilter)) return LoggingRuleLevelFilter.Off.LogLevels; @@ -94,9 +94,9 @@ private bool[] GenerateLogLevels() return activeFilter.Value; } - private LogLevel GenerateFinalMinLevel() + private LogLevel? GenerateFinalMinLevel() { - var levelFilter = _finalMinLevelFilter?.Render(LogEventInfo.CreateNullEvent()); + var levelFilter = _finalMinLevelFilter?.Render(LogEventInfo.CreateNullEvent())?.Trim() ?? string.Empty; return ParseLogLevel(levelFilter, null); } @@ -111,14 +111,14 @@ private bool[] ParseSingleLevel(string levelFilter) return logLevels; } - private LogLevel ParseLogLevel(string logLevel, LogLevel levelIfEmpty) + private LogLevel? ParseLogLevel(string logLevel, LogLevel? levelIfEmpty) { try { if (string.IsNullOrEmpty(logLevel)) return levelIfEmpty; - return LogLevel.FromString(logLevel.Trim()); + return LogLevel.FromString(logLevel); } catch (ArgumentException ex) { diff --git a/src/NLog/Config/DynamicRangeLevelFilter.cs b/src/NLog/Config/DynamicRangeLevelFilter.cs index 4b6effdbcb..13120abeb5 100644 --- a/src/NLog/Config/DynamicRangeLevelFilter.cs +++ b/src/NLog/Config/DynamicRangeLevelFilter.cs @@ -44,16 +44,16 @@ namespace NLog.Config internal sealed class DynamicRangeLevelFilter : ILoggingRuleLevelFilter { private readonly LoggingRule _loggingRule; - private readonly SimpleLayout _minLevel; - private readonly SimpleLayout _maxLevel; - private readonly SimpleLayout _finalMinLevelFilter; + private readonly SimpleLayout? _minLevel; + private readonly SimpleLayout? _maxLevel; + private readonly SimpleLayout? _finalMinLevelFilter; private KeyValuePair _activeFilter; public bool[] LogLevels => GenerateLogLevels(); - public LogLevel FinalMinLevel => GenerateFinalMinLevel(); + public LogLevel? FinalMinLevel => GenerateFinalMinLevel(); - public DynamicRangeLevelFilter(LoggingRule loggingRule, SimpleLayout minLevel, SimpleLayout maxLevel, SimpleLayout finalMinLevelFilter) + public DynamicRangeLevelFilter(LoggingRule loggingRule, SimpleLayout? minLevel, SimpleLayout? maxLevel, SimpleLayout? finalMinLevelFilter) { _loggingRule = loggingRule; _minLevel = minLevel; @@ -69,10 +69,8 @@ public LoggingRuleLevelFilter GetSimpleFilterForUpdate() private bool[] GenerateLogLevels() { - var minLevelFilter = _minLevel?.Render(LogEventInfo.CreateNullEvent()) ?? string.Empty; - var maxLevelFilter = _maxLevel?.Render(LogEventInfo.CreateNullEvent()) ?? string.Empty; - if (string.IsNullOrEmpty(minLevelFilter) && string.IsNullOrEmpty(maxLevelFilter)) - return LoggingRuleLevelFilter.Off.LogLevels; + var minLevelFilter = _minLevel?.Render(LogEventInfo.CreateNullEvent())?.Trim() ?? string.Empty; + var maxLevelFilter = _maxLevel?.Render(LogEventInfo.CreateNullEvent())?.Trim() ?? string.Empty; var activeFilter = _activeFilter; if (!activeFilter.Key.Equals(new MinMaxLevels(minLevelFilter, maxLevelFilter))) @@ -83,16 +81,19 @@ private bool[] GenerateLogLevels() return activeFilter.Value; } - private LogLevel GenerateFinalMinLevel() + private LogLevel? GenerateFinalMinLevel() { - var levelFilter = _finalMinLevelFilter?.Render(LogEventInfo.CreateNullEvent()); + var levelFilter = _finalMinLevelFilter?.Render(LogEventInfo.CreateNullEvent())?.Trim() ?? string.Empty; return ParseLogLevel(levelFilter, null); } private bool[] ParseLevelRange(string minLevelFilter, string maxLevelFilter) { - LogLevel minLevel = ParseLogLevel(minLevelFilter, LogLevel.MinLevel); - LogLevel maxLevel = ParseLogLevel(maxLevelFilter, LogLevel.MaxLevel); + if (string.IsNullOrEmpty(minLevelFilter) && string.IsNullOrEmpty(maxLevelFilter)) + return LoggingRuleLevelFilter.Off.LogLevels; + + var minLevel = ParseLogLevel(minLevelFilter, LogLevel.MinLevel); + var maxLevel = ParseLogLevel(maxLevelFilter, LogLevel.MaxLevel); bool[] logLevels = new bool[LogLevel.MaxLevel.Ordinal + 1]; if (minLevel != null && maxLevel != null) @@ -105,14 +106,14 @@ private bool[] ParseLevelRange(string minLevelFilter, string maxLevelFilter) return logLevels; } - private LogLevel ParseLogLevel(string logLevel, LogLevel levelIfEmpty) + private LogLevel? ParseLogLevel(string logLevel, LogLevel? levelIfEmpty) { try { if (string.IsNullOrEmpty(logLevel)) return levelIfEmpty; - return LogLevel.FromString(logLevel.Trim()); + return LogLevel.FromString(logLevel); } catch (ArgumentException ex) { @@ -121,7 +122,11 @@ private LogLevel ParseLogLevel(string logLevel, LogLevel levelIfEmpty) } } - private struct MinMaxLevels : IEquatable + private +#if !NETFRAMEWORK + readonly +#endif + struct MinMaxLevels : IEquatable { private readonly string _minLevel; private readonly string _maxLevel; diff --git a/src/NLog/Config/Factory.cs b/src/NLog/Config/Factory.cs index 9533235be9..3a43288add 100644 --- a/src/NLog/Config/Factory.cs +++ b/src/NLog/Config/Factory.cs @@ -36,7 +36,6 @@ namespace NLog.Config using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using System.Reflection; using NLog.Common; using NLog.Internal; using NLog.LayoutRenderers; @@ -48,66 +47,65 @@ namespace NLog.Config /// The type of the attribute used to annotate items. internal class Factory : IFactory, IFactory -#pragma warning disable CS0618 // Type or member is obsolete - , INamedItemFactory -#pragma warning restore CS0618 // Type or member is obsolete where TBaseType : class where TAttributeType : NameBaseAttribute { - private struct ItemFactory - { - public readonly GetTypeDelegate ItemType; - public readonly Func ItemCreator; - - public ItemFactory(GetTypeDelegate type, Func itemCreator) - { - ItemType = type; - ItemCreator = itemCreator; - } - } - - private readonly Dictionary _items; + private readonly Dictionary> _items; private readonly ConfigurationItemFactory _parentFactory; - private readonly Factory _globalDefaultFactory; - internal Factory(ConfigurationItemFactory parentFactory, Factory globalDefaultFactory) + public bool Initialized { get; private set; } + + internal Factory(ConfigurationItemFactory parentFactory) { _parentFactory = parentFactory; - _globalDefaultFactory = globalDefaultFactory; - _items = new Dictionary(globalDefaultFactory is null ? 16 : 0, StringComparer.OrdinalIgnoreCase); + _items = new Dictionary>(16, StringComparer.OrdinalIgnoreCase); } private delegate Type GetTypeDelegate(); - /// - /// Scans the assembly. - /// - /// The types to scan. - /// The assembly name for the types. - /// The prefix. - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - [UnconditionalSuppressMessage("Trimming - Ignore since obsolete", "IL2072")] - [UnconditionalSuppressMessage("Trimming - Ignore since obsolete", "IL2062")] - public void ScanTypes(Type[] types, string assemblyName, string itemNamePrefix) + public void Initialize(Action itemRegistration) { - foreach (Type t in types) + lock (ConfigurationItemFactory.SyncRoot) { + if (Initialized) + return; + try { - RegisterType(t, itemNamePrefix); + var skipCheckExists = _items.Count == 0; + itemRegistration.Invoke(skipCheckExists); } - catch (Exception exception) + finally { - InternalLogger.Error(exception, "Failed to add type '{0}'.", t.FullName); - - if (exception.MustBeRethrown()) - { - throw; - } + Initialized = true; } } } + public bool CheckTypeAliasExists(string typeAlias) => _items.ContainsKey(FactoryExtensions.NormalizeName(typeAlias)); + + public void RegisterType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] TType>(string typeAlias) where TType : TBaseType, new() + { + typeAlias = FactoryExtensions.NormalizeName(typeAlias); + + lock (ConfigurationItemFactory.SyncRoot) + { + _parentFactory.RegisterTypeProperties(() => new TType()); + _items[typeAlias] = () => new TType(); + } + } + + public void RegisterType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] TType>(string typeAlias, Func itemCreator) where TType : TBaseType + { + typeAlias = FactoryExtensions.NormalizeName(typeAlias); + + lock (ConfigurationItemFactory.SyncRoot) + { + _parentFactory.RegisterTypeProperties(() => itemCreator()); + _items[typeAlias] = () => itemCreator(); + } + } + /// /// Registers the type. /// @@ -139,7 +137,7 @@ public void RegisterNamedType(string itemName, string typeName) { itemName = FactoryExtensions.NormalizeName(itemName); - Type itemType = null; + Type? itemType = null; GetTypeDelegate typeLookup = () => { @@ -151,7 +149,7 @@ public void RegisterNamedType(string itemName, string typeName) return itemType; }; - Func typeCreator = () => + Func typeCreator = () => { var type = typeLookup(); return type != null ? (TBaseType)Activator.CreateInstance(type) : null; @@ -159,7 +157,7 @@ public void RegisterNamedType(string itemName, string typeName) lock (ConfigurationItemFactory.SyncRoot) { - _items[itemName] = new ItemFactory(typeLookup, typeCreator); + _items[itemName] = typeCreator; } } @@ -174,20 +172,6 @@ public virtual void Clear() } } - /// - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - [UnconditionalSuppressMessage("Trimming - Ignore since obsolete", "IL2067")] - void INamedItemFactory.RegisterDefinition(string itemName, Type itemDefinition) - { - if (string.IsNullOrEmpty(itemName)) - throw new ArgumentException($"Missing NLog {typeof(TBaseType).Name} type-alias", nameof(itemName)); - - if (!typeof(TBaseType).IsAssignableFrom(itemDefinition)) - throw new ArgumentException($"Not of type NLog {typeof(TBaseType).Name}", nameof(itemDefinition)); - - RegisterDefinition(itemDefinition, itemName, string.Empty); - } - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] public void RegisterDefinition(string typeAlias, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] Type itemType) { @@ -205,74 +189,14 @@ private void RegisterDefinition([DynamicallyAccessedMembers(DynamicallyAccessedM typeAlias = FactoryExtensions.NormalizeName(typeAlias); Func itemCreator = () => (TBaseType)Activator.CreateInstance(itemType); - var itemFactory = new ItemFactory(() => itemType, itemCreator); lock (ConfigurationItemFactory.SyncRoot) { _parentFactory.RegisterTypeProperties(itemType, () => itemCreator.Invoke()); - _items[itemNamePrefix + typeAlias] = itemFactory; - } - } - - public void RegisterType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] TType>(string typeAlias) where TType : TBaseType, new() - { - typeAlias = FactoryExtensions.NormalizeName(typeAlias); - - var itemFactory = new ItemFactory(() => typeof(TType), () => new TType()); - lock (ConfigurationItemFactory.SyncRoot) - { - _parentFactory.RegisterTypeProperties(() => new TType()); - _items[typeAlias] = itemFactory; - } - } - - public void RegisterType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] TType>(string typeAlias, Func itemCreator) where TType : TBaseType - { - typeAlias = FactoryExtensions.NormalizeName(typeAlias); - - var itemFactory = new ItemFactory(() => typeof(TType), () => itemCreator()); - lock (ConfigurationItemFactory.SyncRoot) - { - _parentFactory.RegisterTypeProperties(() => itemCreator()); - _items[typeAlias] = itemFactory; - } - } - - /// - [Obsolete("Use TryCreateInstance instead. Marked obsolete with NLog v5.2")] - public bool TryGetDefinition(string itemName, out Type result) - { - itemName = FactoryExtensions.NormalizeName(itemName); - - if (!TryGetItemFactory(itemName, out var itemFactory) || itemFactory.ItemType is null) - { - if (_globalDefaultFactory != null && _globalDefaultFactory.TryGetDefinition(itemName, out result)) - { - return true; - } - - result = null; - return false; - } - - try - { - result = itemFactory.ItemType.Invoke(); - return result != null; - } - catch (Exception ex) - { - if (ex.MustBeRethrown()) - { - throw; - } - - // delegate invocation failed - type is not available - result = null; - return false; + _items[itemNamePrefix + typeAlias] = () => itemCreator.Invoke(); } } - private bool TryGetItemFactory(string typeAlias, out ItemFactory itemFactory) + private bool TryGetItemFactory(string typeAlias, out Func? itemFactory) { lock (ConfigurationItemFactory.SyncRoot) { @@ -280,53 +204,20 @@ private bool TryGetItemFactory(string typeAlias, out ItemFactory itemFactory) } } - bool INamedItemFactory.TryCreateInstance(string itemName, out TBaseType result) - { - return TryCreateInstance(itemName, out result); - } - /// - public virtual bool TryCreateInstance(string typeAlias, out TBaseType result) + public virtual bool TryCreateInstance(string typeAlias, out TBaseType? result) { -#pragma warning disable CS0618 // Type or member is obsolete - if (TryCreateInstanceLegacy(typeAlias, out result)) - return true; -#pragma warning restore CS0618 // Type or member is obsolete - typeAlias = FactoryExtensions.NormalizeName(typeAlias); - if (!TryGetItemFactory(typeAlias, out var itemFactory) || itemFactory.ItemCreator is null) + if (!TryGetItemFactory(typeAlias, out var itemFactory) || itemFactory is null) { result = null; return false; } - result = itemFactory.ItemCreator.Invoke(); + result = itemFactory.Invoke(); return !(result is null); } - - [Obsolete("Use TryCreateInstance instead. Marked obsolete with NLog v5.2")] - private bool TryCreateInstanceLegacy(string itemName, out TBaseType result) - { - if (!ReferenceEquals(_parentFactory.CreateInstance, FactoryHelper.CreateInstance)) - { - if (TryGetDefinition(itemName, out var itemType)) - { - result = (TBaseType)_parentFactory.CreateInstance(itemType); - return true; - } - } - - result = null; - return false; - } - - /// - [Obsolete("Use TryCreateInstance instead. Marked obsolete with NLog v5.2")] - TBaseType INamedItemFactory.CreateInstance(string itemName) - { - return FactoryExtensions.CreateInstance(this, itemName); - } } internal static class FactoryExtensions @@ -337,6 +228,9 @@ public static TBaseType CreateInstance(this IFactory facto { if (factory.TryCreateInstance(typeAlias, out var result)) { + if (result is null) + throw new NLogConfigurationException($"Failed to create {typeof(TBaseType).Name} of type: '{typeAlias}' - Factory method returned null"); + return result; } } @@ -350,16 +244,72 @@ public static TBaseType CreateInstance(this IFactory facto if (normalName != null && (normalName.StartsWith("aspnet", StringComparison.OrdinalIgnoreCase) || normalName.StartsWith("iis", StringComparison.OrdinalIgnoreCase))) { -#if NETSTANDARD - message += " - Extension NLog.Web.AspNetCore not included?"; -#else +#if NETFRAMEWORK message += " - Extension NLog.Web not included?"; +#else + message += " - Extension NLog.Web.AspNetCore not included?"; #endif } else if (normalName?.StartsWith("database", StringComparison.OrdinalIgnoreCase) == true) { message += " - Extension NLog.Database not included?"; } + else if (normalName?.StartsWith("network", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.Network not included?"; + } + else if (normalName?.StartsWith("nlogviewer", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.Network not included?"; + } + else if (normalName?.StartsWith("chainsaw", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.Network not included?"; + } + else if (normalName?.StartsWith("Log4JXml", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.Network not included?"; + } + else if (normalName?.StartsWith("syslog", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.Network not included?"; + } + else if (normalName?.StartsWith("gelf", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.Network not included?"; + } + else if (normalName?.StartsWith("localip", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.Network not included?"; + } + else if (normalName?.StartsWith("webservice", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.WebService not included?"; + } + else if (normalName?.StartsWith("atomFile", StringComparison.OrdinalIgnoreCase) == true || normalName?.StartsWith("atomicFile", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.AtomicFile not included?"; + } + else if (normalName?.StartsWith("GZipFile", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.GZipFile not included?"; + } + else if (normalName?.StartsWith("trace", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.Trace not included?"; + } + else if (normalName?.StartsWith("activityid", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.Trace not included?"; + } + else if (normalName?.StartsWith("mailkit", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.MailKit not included?"; + } + else if (normalName?.StartsWith("mail", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.Targets.Mail not included?"; + } else if (normalName?.StartsWith("eventlog", StringComparison.OrdinalIgnoreCase) == true) { message += " - Extension NLog.WindowsEventLog not included?"; @@ -376,6 +326,10 @@ public static TBaseType CreateInstance(this IFactory facto { message += " - Extension NLog.PerformanceCounter not included?"; } + else if (normalName?.StartsWith("regexreplace", StringComparison.OrdinalIgnoreCase) == true) + { + message += " - Extension NLog.RegEx not included?"; + } else { message += " - Verify type-alias and check extension is included."; @@ -416,12 +370,10 @@ public static string NormalizeName(string itemName) internal sealed class LayoutRendererFactory : Factory { private readonly Dictionary _funcRenderers = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly LayoutRendererFactory _globalDefaultFactory; - public LayoutRendererFactory(ConfigurationItemFactory parentFactory, LayoutRendererFactory globalDefaultFactory) - : base(parentFactory, globalDefaultFactory) + public LayoutRendererFactory(ConfigurationItemFactory parentFactory) + : base(parentFactory) { - _globalDefaultFactory = globalDefaultFactory; } /// @@ -446,29 +398,16 @@ public void RegisterFuncLayout(string itemName, FuncLayoutRenderer renderer) } /// - public override bool TryCreateInstance(string typeAlias, out LayoutRenderer result) + public override bool TryCreateInstance(string typeAlias, out LayoutRenderer? result) { //first try func renderers, as they should have the possibility to overwrite a current one. - FuncLayoutRenderer funcResult; typeAlias = FactoryExtensions.NormalizeName(typeAlias); if (_funcRenderers.Count > 0) { lock (ConfigurationItemFactory.SyncRoot) { - if (_funcRenderers.TryGetValue(typeAlias, out funcResult)) - { - result = funcResult; - return true; - } - } - } - - if (_globalDefaultFactory?._funcRenderers?.Count > 0) - { - lock (ConfigurationItemFactory.SyncRoot) - { - if (_globalDefaultFactory._funcRenderers.TryGetValue(typeAlias, out funcResult)) + if (_funcRenderers.TryGetValue(typeAlias, out var funcResult)) { result = funcResult; return true; diff --git a/src/NLog/Config/IFactory.cs b/src/NLog/Config/IFactory.cs index 2f63629a24..b00a17fd64 100644 --- a/src/NLog/Config/IFactory.cs +++ b/src/NLog/Config/IFactory.cs @@ -43,9 +43,6 @@ internal interface IFactory { void Clear(); - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - void ScanTypes(Type[] types, string assemblyName, string itemNamePrefix); - void RegisterType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string itemNamePrefix); } @@ -54,14 +51,10 @@ internal interface IFactory /// public interface IFactory where TBaseType : class { - /// - /// Registers type-creation with type-alias - /// - void RegisterType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] TType>(string typeAlias) where TType : TBaseType, new(); /// /// Tries to create an item instance with type-alias /// - /// True if instance was created successfully, false otherwise. - bool TryCreateInstance(string typeAlias, out TBaseType result); + /// if instance was created successfully, otherwise. + bool TryCreateInstance(string typeAlias, out TBaseType? result); } } diff --git a/src/NLog/Config/ILoggingConfigurationElement.cs b/src/NLog/Config/ILoggingConfigurationElement.cs index b0088a8c50..591b8b86e6 100644 --- a/src/NLog/Config/ILoggingConfigurationElement.cs +++ b/src/NLog/Config/ILoggingConfigurationElement.cs @@ -47,7 +47,7 @@ public interface ILoggingConfigurationElement /// /// Configuration Key/Value Pairs /// - IEnumerable> Values { get; } + IEnumerable> Values { get; } /// /// Child configuration elements /// diff --git a/src/NLog/Config/ILoggingConfigurationLoader.cs b/src/NLog/Config/ILoggingConfigurationLoader.cs index 0d8f82cdaa..4c6b7e64b4 100644 --- a/src/NLog/Config/ILoggingConfigurationLoader.cs +++ b/src/NLog/Config/ILoggingConfigurationLoader.cs @@ -47,20 +47,13 @@ internal interface ILoggingConfigurationLoader : IDisposable /// LogFactory that owns the NLog configuration /// Name of NLog.config file (optional) /// NLog configuration (or null if none found) - LoggingConfiguration Load(LogFactory logFactory, string filename = null); - - /// - /// Notifies when LoggingConfiguration has been successfully applied - /// - /// LogFactory that owns the NLog configuration - /// NLog Config - void Activated(LogFactory logFactory, LoggingConfiguration config); + LoggingConfiguration? Load(LogFactory logFactory, string? filename = null); /// /// Get file paths (including filename) for the possible NLog config files. /// /// Name of NLog.config file (optional) /// The file paths to the possible config file - IEnumerable GetDefaultCandidateConfigFilePaths(string filename = null); + IEnumerable GetDefaultCandidateConfigFilePaths(string? filename = null); } } diff --git a/src/NLog/Config/ILoggingRuleLevelFilter.cs b/src/NLog/Config/ILoggingRuleLevelFilter.cs index e1eccdedaf..3c6036d4c5 100644 --- a/src/NLog/Config/ILoggingRuleLevelFilter.cs +++ b/src/NLog/Config/ILoggingRuleLevelFilter.cs @@ -40,7 +40,7 @@ internal interface ILoggingRuleLevelFilter /// bool[] LogLevels { get; } - LogLevel FinalMinLevel { get; } + LogLevel? FinalMinLevel { get; } /// /// Converts the filter into a simple diff --git a/src/NLog/Config/INamedItemFactory.cs b/src/NLog/Config/INamedItemFactory.cs deleted file mode 100644 index 4be1932703..0000000000 --- a/src/NLog/Config/INamedItemFactory.cs +++ /dev/null @@ -1,80 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.Config -{ - using System; - using System.ComponentModel; - - /// - /// Obsolete since dynamic type loading is not compatible with publish as trimmed application. Replaced by . - /// Represents a factory of named items (such as targets, layouts, layout renderers, etc.). - /// - /// Base type for each item instance. - /// Item definition type (typically ). - [Obsolete("Instead use NLog.LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public interface INamedItemFactory - where TInstanceType : class - { - /// - /// Registers new item definition. - /// - /// Name of the item. - /// Item definition. - void RegisterDefinition(string itemName, TDefinitionType itemDefinition); - - /// - /// Tries to get registered item definition. - /// - /// Name of the item. - /// Reference to a variable which will store the item definition. - /// Item definition. - bool TryGetDefinition(string itemName, out TDefinitionType result); - - /// - /// Creates item instance. - /// - /// Name of the item. - /// Newly created item instance. - TInstanceType CreateInstance(string itemName); - - /// - /// Tries to create an item instance. - /// - /// Name of the item. - /// The result. - /// True if instance was created successfully, false otherwise. - bool TryCreateInstance(string itemName, out TInstanceType result); - } -} diff --git a/src/NLog/Config/IPropertyTypeConverter.cs b/src/NLog/Config/IPropertyTypeConverter.cs index 05af05979d..a7dbc9070e 100644 --- a/src/NLog/Config/IPropertyTypeConverter.cs +++ b/src/NLog/Config/IPropertyTypeConverter.cs @@ -48,6 +48,6 @@ public interface IPropertyTypeConverter /// Format to use when parsing /// Culture to use when parsing /// Output value with wanted type - object Convert(object propertyValue, Type propertyType, string format, IFormatProvider formatProvider); + object? Convert(object? propertyValue, Type propertyType, string? format, IFormatProvider? formatProvider); } } diff --git a/src/NLog/Config/InstallationContext.cs b/src/NLog/Config/InstallationContext.cs index 0078d09be2..6893e09371 100644 --- a/src/NLog/Config/InstallationContext.cs +++ b/src/NLog/Config/InstallationContext.cs @@ -38,15 +38,12 @@ namespace NLog.Config using System.ComponentModel; using System.Globalization; using System.IO; - // ReSharper disable once RedundantUsingDirective - using NLog.Internal; /// /// Provides context for install/uninstall operations. /// public sealed class InstallationContext : IDisposable { -#if !NETSTANDARD1_3 /// /// Mapping between log levels and console output colors. /// @@ -59,7 +56,6 @@ public sealed class InstallationContext : IDisposable { LogLevel.Error, ConsoleColor.Red }, { LogLevel.Fatal, ConsoleColor.DarkRed }, }; -#endif /// /// Initializes a new instance of the class. @@ -100,12 +96,12 @@ public InstallationContext(TextWriter logOutput) /// /// Gets the installation parameters. /// - public IDictionary Parameters { get; private set; } + public IDictionary Parameters { get; } /// /// Gets or sets the log output. /// - public TextWriter LogOutput { get; set; } + public TextWriter? LogOutput { get; set; } /// /// Logs the specified trace message. @@ -192,21 +188,17 @@ private void Log(LogLevel logLevel, string message, object[] arguments) message = string.Format(CultureInfo.InvariantCulture, message, arguments); } -#if !NETSTANDARD1_3 var oldColor = Console.ForegroundColor; Console.ForegroundColor = LogLevel2ConsoleColor[logLevel]; try { - LogOutput.WriteLine(message); + LogOutput?.WriteLine(message); } finally { Console.ForegroundColor = oldColor; } -#else - this.LogOutput.WriteLine(message); -#endif } } } diff --git a/src/NLog/Config/LoggerNameMatcher.cs b/src/NLog/Config/LoggerNameMatcher.cs index ee7bebb8f0..236ae65fb2 100644 --- a/src/NLog/Config/LoggerNameMatcher.cs +++ b/src/NLog/Config/LoggerNameMatcher.cs @@ -34,15 +34,17 @@ namespace NLog.Config { using System; - using System.Text.RegularExpressions; + using NLog.Internal; /// /// Encapsulates and the logic to match the actual logger name /// All subclasses defines immutable objects. /// Concrete subclasses defines various matching rules through /// - abstract class LoggerNameMatcher + internal abstract class LoggerNameMatcher { + public static LoggerNameMatcher Off => NoneLoggerNameMatcher.Instance; + /// /// Creates a concrete based on . /// @@ -101,9 +103,9 @@ public static LoggerNameMatcher Create(string loggerNamePattern) /// public string Pattern { get; } - protected readonly string _matchingArgument; + protected readonly string? _matchingArgument; private readonly string _toString; - protected LoggerNameMatcher(string pattern, string matchingArgument) + protected LoggerNameMatcher(string pattern, string? matchingArgument) { Pattern = pattern; _matchingArgument = matchingArgument; @@ -131,7 +133,7 @@ private sealed class NoneLoggerNameMatcher : LoggerNameMatcher protected override string MatchMode => "None"; public static readonly NoneLoggerNameMatcher Instance = new NoneLoggerNameMatcher(); private NoneLoggerNameMatcher() - : base(null, null) + : base(string.Empty, null) { } @@ -233,25 +235,77 @@ public override bool NameMatches(string loggerName) private sealed class MultiplePatternLoggerNameMatcher : LoggerNameMatcher { protected override string MatchMode => "MultiplePattern"; - private readonly Regex _regex; - private static string ConvertToRegex(string wildcardsPattern) - { - return - '^' + - Regex.Escape(wildcardsPattern) - .Replace("\\*", ".*") - .Replace("\\?", ".") - + '$'; - } + private readonly string _wildCardPattern; + public MultiplePatternLoggerNameMatcher(string pattern) - : base(pattern, ConvertToRegex(pattern)) + : base(pattern, pattern) { - _regex = new Regex(_matchingArgument, RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture); + _wildCardPattern = Guard.ThrowIfNull(pattern); } + public override bool NameMatches(string loggerName) { - if (loggerName is null) return false; - return _regex.IsMatch(loggerName); + return MatchingName(0, _wildCardPattern.Length, loggerName, 0, loggerName?.Length ?? 0); + } + + bool MatchingName(int wildcardStart, int wildcardEnd, string loggerName, int loggerNameStart, int loggerNameEnd) + { + for (int i = wildcardStart; i < wildcardEnd; ++i) + { + char matchToken = _wildCardPattern[i]; + if (matchToken == '*') + break; + + if (loggerNameStart >= loggerNameEnd) + return false; + + char inputToken = loggerName[loggerNameStart]; + if (matchToken != '?' && matchToken != inputToken) + return false; + + ++loggerNameStart; + ++wildcardStart; + } + + for (int i = wildcardEnd - 1; i >= wildcardStart; --i) + { + char matchToken = _wildCardPattern[i]; + if (matchToken == '*') + break; + + if (loggerNameStart >= loggerNameEnd) + return false; + + char inputToken = loggerName[loggerNameEnd - 1]; + if (matchToken != '?' && matchToken != inputToken) + return false; + + --loggerNameEnd; + --wildcardEnd; + } + + var nextMatch = '*'; + for (int i = wildcardStart; i < wildcardEnd; ++i) + { + nextMatch = _wildCardPattern[i]; + if (nextMatch != '*') + break; + ++wildcardStart; + } + if (nextMatch == '*') + return true; + + for (int i = loggerNameStart; i < loggerNameEnd; ++i) + { + char inputToken = loggerName[i]; + if (nextMatch == '?' || inputToken == nextMatch) + { + // Found match, now scan the remaining sub-string + return MatchingName(wildcardStart, wildcardEnd, loggerName, i, loggerNameEnd); + } + } + + return false; } } } diff --git a/src/NLog/Config/LoggingConfiguration.cs b/src/NLog/Config/LoggingConfiguration.cs index 1a82fa78be..b582e6d9e5 100644 --- a/src/NLog/Config/LoggingConfiguration.cs +++ b/src/NLog/Config/LoggingConfiguration.cs @@ -36,12 +36,10 @@ namespace NLog.Config using System; using System.Collections.Generic; using System.Collections.ObjectModel; + using System.ComponentModel; using System.Globalization; using System.Linq; - using System.Reflection; using System.Threading; - using JetBrains.Annotations; - using NLog.Common; using NLog.Internal; using NLog.Layouts; @@ -104,22 +102,25 @@ public LoggingConfiguration(LogFactory logFactory) /// /// Gets the collection of file names which should be watched for changes by NLog. /// + [Obsolete("NLog LogFactory no longer supports FileWatcher. Marked obsolete with NLog v6")] + [EditorBrowsable(EditorBrowsableState.Never)] public virtual IEnumerable FileNamesToWatch => ArrayHelper.Empty(); /// /// Gets the collection of logging rules. /// - public IList LoggingRules { get; } = new List(); + public IList LoggingRules => _loggingRules; + private readonly List _loggingRules = new List(); - internal List GetLoggingRulesThreadSafe() { lock (LoggingRules) return LoggingRules.ToList(); } - private void AddLoggingRulesThreadSafe(LoggingRule rule) { lock (LoggingRules) LoggingRules.Add(rule); } + internal LoggingRule[] GetLoggingRulesThreadSafe() { lock (_loggingRules) return _loggingRules.ToArray(); } + private void AddLoggingRulesThreadSafe(LoggingRule rule) { lock (_loggingRules) _loggingRules.Add(rule); } private bool TryGetTargetThreadSafe(string name, out Target target) { lock (_targets) return _targets.TryGetValue(name, out target); } private List GetAllTargetsThreadSafe() { lock (_targets) return _targets.Values.ToList(); } - private Target RemoveTargetThreadSafe(string name) + private Target? RemoveTargetThreadSafe(string name) { - Target target; + Target? target; lock (_targets) { if (_targets.TryGetValue(name, out target)) @@ -136,19 +137,15 @@ private Target RemoveTargetThreadSafe(string name) return target; } - private void AddTargetThreadSafe(Target target, string targetAlias = null) + private void AddTargetThreadSafe(Target target, string? targetAlias = null) { - if (string.IsNullOrEmpty(target.Name) && string.IsNullOrEmpty(targetAlias)) - return; - lock (_targets) { - if (string.IsNullOrEmpty(targetAlias)) + if (targetAlias is null || string.IsNullOrEmpty(targetAlias)) { - if (_targets.ContainsKey(target.Name)) + targetAlias = target.Name ?? string.Empty; + if (_targets.ContainsKey(targetAlias)) return; - - targetAlias = target.Name; } if (_targets.TryGetValue(targetAlias, out var oldTarget) && ReferenceEquals(oldTarget, target)) @@ -173,8 +170,7 @@ private void AddTargetThreadSafe(Target target, string targetAlias = null) /// /// Specific culture info or null to use /// - [CanBeNull] - public CultureInfo DefaultCultureInfo { get; set; } + public CultureInfo? DefaultCultureInfo { get; set; } /// /// Gets all targets. @@ -211,7 +207,7 @@ internal bool TryLookupDynamicVariable(string key, out Layout value) /// The target object with a non /// /// when is - public void AddTarget([NotNull] Target target) + public void AddTarget(Target target) { Guard.ThrowIfNull(target); @@ -229,7 +225,7 @@ public void AddTarget([NotNull] Target target) /// The target object. /// when is /// when is - public void AddTarget(string name, [NotNull] Target target) + public void AddTarget(string name, Target target) { Guard.ThrowIfNull(name); Guard.ThrowIfNull(target); @@ -250,15 +246,16 @@ public void AddTarget(string name, [NotNull] Target target) /// /// Found target or when the target is not found. /// - public Target FindTargetByName(string name) + public Target? FindTargetByName(string name) { - Target value; - if (!TryGetTargetThreadSafe(name, out value)) + Guard.ThrowIfNull(name); + + if (TryGetTargetThreadSafe(name, out var target)) { - return null; + return target; } - return value; + return null; } /// @@ -271,9 +268,11 @@ public Target FindTargetByName(string name) /// /// Found target or when the target is not found of not of type /// - public TTarget FindTargetByName(string name) + public TTarget? FindTargetByName(string name) where TTarget : Target { + Guard.ThrowIfNull(name); + var target = FindTargetByName(name); if (target is TTarget specificTarget) { @@ -299,6 +298,8 @@ public TTarget FindTargetByName(string name) /// Logger name pattern. It may include the '*' wildcard at the beginning, at the end or at both ends. public void AddRule(LogLevel minLevel, LogLevel maxLevel, string targetName, string loggerNamePattern = "*") { + Guard.ThrowIfNull(targetName); + var target = FindTargetByName(targetName); if (target is null) { @@ -342,6 +343,7 @@ public void AddRule(LogLevel minLevel, LogLevel maxLevel, Target target, string /// rule object to add public void AddRule(LoggingRule rule) { + Guard.ThrowIfNull(rule); AddLoggingRulesThreadSafe(rule); } @@ -353,6 +355,8 @@ public void AddRule(LoggingRule rule) /// Logger name pattern. It may include the '*' wildcard at the beginning, at the end or at both ends. public void AddRuleForOneLevel(LogLevel level, string targetName, string loggerNamePattern = "*") { + Guard.ThrowIfNull(targetName); + var target = FindTargetByName(targetName); if (target is null) { @@ -397,6 +401,8 @@ public void AddRuleForOneLevel(LogLevel level, Target target, string loggerNameP /// Logger name pattern. It may include the '*' wildcard at the beginning, at the end or at both ends. public void AddRuleForAllLevels(string targetName, string loggerNamePattern = "*") { + Guard.ThrowIfNull(targetName); + var target = FindTargetByName(targetName); if (target is null) { @@ -437,13 +443,12 @@ public void AddRuleForAllLevels(Target target, string loggerNamePattern, bool fi /// /// The name of the logging rule to be found. /// Found logging rule or when not found. - public LoggingRule FindRuleByName(string ruleName) + public LoggingRule? FindRuleByName(string ruleName) { - if (ruleName is null) - return null; + Guard.ThrowIfNull(ruleName); var loggingRules = GetLoggingRulesThreadSafe(); - for (int i = loggingRules.Count - 1; i >= 0; i--) + for (int i = loggingRules.Length - 1; i >= 0; i--) { if (string.Equals(loggingRules[i].RuleName, ruleName, StringComparison.OrdinalIgnoreCase)) { @@ -460,8 +465,7 @@ public LoggingRule FindRuleByName(string ruleName) /// Found one or more logging rule to remove, or when not found. public bool RemoveRuleByName(string ruleName) { - if (ruleName is null) - return false; + Guard.ThrowIfNull(ruleName); HashSet removedRules = new HashSet(); var loggingRules = GetLoggingRulesThreadSafe(); @@ -491,7 +495,7 @@ public bool RemoveRuleByName(string ruleName) } /// - /// Called by LogManager when one of the log configuration files changes. + /// Loads the NLog LoggingConfiguration from its original source (Ex. read from original config-file after it was updated) /// /// /// A new instance of that represents the updated configuration. @@ -519,7 +523,7 @@ protected void PrepareForReload(LoggingConfiguration oldConfig) /// Notify the configuration when has been assigned / unassigned. /// /// LogFactory that configuration has been assigned to. - protected internal virtual void OnConfigurationAssigned(LogFactory logFactory) + protected internal virtual void OnConfigurationAssigned(LogFactory? logFactory) { if (!ReferenceEquals(logFactory, LogFactory) && logFactory != null) { @@ -536,8 +540,10 @@ protected internal virtual void OnConfigurationAssigned(LogFactory logFactory) /// Name of the target. public void RemoveTarget(string name) { + Guard.ThrowIfNull(name); + HashSet removedTargets = new HashSet(); - Target removedTarget = RemoveTargetThreadSafe(name); + var removedTarget = RemoveTargetThreadSafe(name); if (removedTarget != null) { removedTargets.Add(removedTarget); @@ -566,7 +572,7 @@ public void RemoveTarget(string name) } } - private void CleanupRulesForRemovedTarget(string name, Target removedTarget, HashSet removedTargets) + private void CleanupRulesForRemovedTarget(string name, Target? removedTarget, HashSet removedTargets) { var loggingRules = GetLoggingRulesThreadSafe(); @@ -749,24 +755,6 @@ internal void ValidateConfig() } _configItems = ObjectGraphScanner.FindReachableObjects(ConfigurationItemFactory.Default, true, roots.ToArray()); - - InternalLogger.Info("Validating config: {0}", this); - - foreach (object o in _configItems) - { - try - { - if (o is ISupportsInitialize) - continue; // Target + Layout + LayoutRenderer validate on Initialize() - - PropertyHelper.CheckRequiredParameters(ConfigurationItemFactory.Default, o); - } - catch (Exception ex) - { - if (ex.MustBeRethrown()) - throw; - } - } } internal void InitializeAll() @@ -843,12 +831,13 @@ internal void CheckForMissingServiceTypes(Type serviceType) private static bool IsMissingServiceType(NLogDependencyResolveException resolveException, Type serviceType) { if (resolveException.ServiceType.IsAssignableFrom(serviceType)) + { return true; + } - resolveException = resolveException.InnerException as NLogDependencyResolveException; - if (resolveException != null) + if (resolveException.InnerException is NLogDependencyResolveException dependencyResolveException) { - return IsMissingServiceType(resolveException, serviceType); + return IsMissingServiceType(dependencyResolveException, serviceType); } return false; @@ -874,21 +863,20 @@ private List GetSupportsInitializes(bool reverse = false) /// /// /// - [NotNull] - internal string ExpandSimpleVariables(string input) + internal string ExpandSimpleVariables(string? input) { return ExpandSimpleVariables(input, out var _); } - [NotNull] - internal string ExpandSimpleVariables(string input, out string matchingVariableName) + internal string ExpandSimpleVariables(string? input, out string? matchingVariableName) { - string output = input; - var culture = StringComparison.OrdinalIgnoreCase; + var output = input; matchingVariableName = null; - if (Variables.Count > 0 && output?.IndexOf('$') >= 0) + if (output != null && !StringHelpers.IsNullOrWhiteSpace(output) && Variables.Count > 0 && output.IndexOf('$') >= 0) { + var culture = StringComparison.OrdinalIgnoreCase; + foreach (var kvp in _variables) { var layout = kvp.Value; @@ -912,7 +900,7 @@ internal string ExpandSimpleVariables(string input, out string matchingVariableN } else { - if (string.Equals(layoutText, input.Trim(), culture)) + if (string.Equals(layoutText, input?.Trim() ?? string.Empty, culture)) { matchingVariableName = kvp.Key; } @@ -937,11 +925,10 @@ internal void CheckUnusedTargets() var targetNamesAtRules = new HashSet(GetLoggingRulesThreadSafe().SelectMany(r => r.Targets).Select(t => t.Name)); var allTargets = AllTargets; - var wrappedTargets = allTargets.OfType().ToLookup(wt => wt.WrappedTarget, wt => wt); - var compoundTargets = allTargets.OfType().SelectMany(wt => wt.Targets.Select(t => new KeyValuePair(t, wt))).ToLookup(p => p.Key, p => p.Value); + ILookup wrappedTargets = allTargets.OfType().ToLookup(wt => wt.WrappedTarget, wt => (Target)wt); + ILookup compoundTargets = allTargets.OfType().SelectMany(wt => wt.Targets.Select(t => new KeyValuePair(t, wt))).ToLookup(p => p.Key, p => p.Value); - bool IsUnusedInList(Target target1, ILookup targets) - where T : Target + bool IsUnusedInList(Target target1, ILookup targets) { if (targets.Contains(target1)) { @@ -979,129 +966,88 @@ bool IsUnusedInList(Target target1, ILookup targets) InternalLogger.Debug("Unused target checking is completed. Total Rule Count: {0}, Total Target Count: {1}, Unused Target Count: {2}", LoggingRules.Count, configuredNamedTargets.Count, unusedCount); } - internal bool FlushAllTargets(TimeSpan timeout, AsyncContinuation asyncContinuation, bool throwExceptions) + internal AsyncContinuation? FlushAllTargets(AsyncContinuation flushCompletion) { - if (asyncContinuation != null) + var pendingTargets = GetAllTargetsToFlush(); + if (pendingTargets.Count == 0) { - FlushAllTargetsAsync(this, asyncContinuation, timeout); - return true; - } - else - { - return FlushAllTargetsSync(this, timeout, throwExceptions); + flushCompletion.Invoke(null); + return null; } - } - /// - /// Flushes any pending log messages on all appenders. - /// - /// Config containing Targets to Flush - /// Flush completed notification (success / timeout) - /// Optional timeout that guarantees that completed notification is called. - /// - private static AsyncContinuation FlushAllTargetsAsync(LoggingConfiguration loggingConfiguration, AsyncContinuation asyncContinuation, TimeSpan? asyncTimeout) - { - var pendingTargets = loggingConfiguration.GetAllTargetsToFlush(); + InternalLogger.Trace("Flushing all {0} targets...", pendingTargets.Count); + + Exception? lastException = null; - AsynchronousAction flushAction = (target, cont) => + Action flushAction = (t, ex) => { - target.Flush(ex => + if (ex != null) { - if (ex != null) - InternalLogger.Warn(ex, "Flush failed for target {0}(Name={1})", target.GetType(), target.Name); - lock (pendingTargets) - { - pendingTargets.Remove(target); - } - cont(ex); - }); - }; - AsyncContinuation flushContinuation = (ex) => - { + InternalLogger.Warn(ex, "Flush failed for target {0}(Name={1})", t.GetType(), t.Name); + } + bool completed = false; lock (pendingTargets) { - foreach (var pendingTarget in pendingTargets) - InternalLogger.Debug("Flush timeout for target {0}(Name={1})", pendingTarget.GetType(), pendingTarget.Name); - pendingTargets.Clear(); + if (ex != null) + lastException = ex; + if (pendingTargets.Remove(t) && pendingTargets.Count == 0) + completed = true; + } + if (completed) + { + if (lastException != null) + InternalLogger.Warn("Flush completed with errors"); + else + InternalLogger.Debug("Flush completed"); + flushCompletion.Invoke(lastException); } - if (ex != null) - InternalLogger.Warn(ex, "Flush completed with errors"); - else - InternalLogger.Debug("Flush completed"); - asyncContinuation(ex); }; - if (asyncTimeout.HasValue) - { - flushContinuation = AsyncHelpers.WithTimeout(flushContinuation, asyncTimeout.Value); - } - else + foreach (var target in pendingTargets.ToArray()) { - flushContinuation = AsyncHelpers.PreventMultipleCalls(flushContinuation); - } - - InternalLogger.Trace("Flushing all {0} targets...", pendingTargets.Count); - AsyncHelpers.ForEachItemInParallel(pendingTargets, flushContinuation, flushAction); - return flushContinuation; - } - - private static bool FlushAllTargetsSync(LoggingConfiguration oldConfig, TimeSpan timeout, bool throwExceptions) - { - Exception lastException = null; - - try - { - ManualResetEvent flushCompletedEvent = new ManualResetEvent(false); - - var flushContinuation = FlushAllTargetsAsync(oldConfig, (ex) => + var flushTarget = target; + AsyncHelpers.StartAsyncTask(s => { - if (ex != null) - lastException = ex; - flushCompletedEvent.Set(); + try + { + flushTarget.Flush(ex => + { + flushAction(flushTarget, ex); + }); + } + catch (Exception ex) + { + flushAction(flushTarget, ex); + throw; + } }, null); - - bool flushCompleted = flushCompletedEvent.WaitOne(timeout); - if (!flushCompleted) - flushContinuation(new TimeoutException($"Timeout when flushing all targets, after waiting {timeout.TotalSeconds} seconds.")); } - catch (Exception ex) - { -#if DEBUG - if (ex.MustBeRethrownImmediately()) - throw; // Throwing exceptions here might crash the entire application (.NET 2.0 behavior) -#endif - - InternalLogger.Error(ex, "LogFactory failed to flush targets."); - if (throwExceptions) - throw new NLogRuntimeException("Asynchronous exception has occurred.", ex); - - return false; - } - - if (lastException != null) + AsyncContinuation flushTimeoutHandler = (ex) => { - if (throwExceptions) - throw new NLogRuntimeException("Asynchronous exception has occurred.", lastException); - else - return false; - } + lock (pendingTargets) + { + foreach (var pendingTarget in pendingTargets) + InternalLogger.Warn("Flush timeout for target {0}(Name={1})", pendingTarget.GetType(), pendingTarget.Name); + pendingTargets.Clear(); + } + }; - return true; + return flushTimeoutHandler; } - /// - /// Change this method with NLog v6 to disconnect LogFactory from Targets/Layouts - /// - Remove LoggingRule-List-parameter - /// - Return ITargetWithFilterChain[] - /// - internal TargetWithFilterChain[] BuildLoggerConfiguration(string loggerName, LogLevel globalLogLevel, List loggingRules) + internal ITargetWithFilterChain[] BuildLoggerConfiguration(string loggerName, LogLevel globalLogLevel) { + if (LoggingRules.Count == 0 || LogLevel.Off.Equals(globalLogLevel)) + return TargetWithFilterChain.NoTargetsByLevel; + + var loggingRules = GetLoggingRulesThreadSafe(); var targetsByLevel = TargetWithFilterChain.BuildLoggerConfiguration(loggerName, loggingRules, globalLogLevel); if (InternalLogger.IsDebugEnabled && !DumpTargetConfigurationForLogger(loggerName, targetsByLevel)) { InternalLogger.Debug("Targets not configured for Logger: {0}", loggerName); } + return targetsByLevel ?? TargetWithFilterChain.NoTargetsByLevel; } @@ -1110,7 +1056,7 @@ private static bool DumpTargetConfigurationForLogger(string loggerName, TargetWi if (targetsByLevel is null) return false; - System.Text.StringBuilder sb = null; + System.Text.StringBuilder? sb = null; for (int i = 0; i <= LogLevel.MaxLevel.Ordinal; ++i) { if (sb != null) @@ -1119,7 +1065,7 @@ private static bool DumpTargetConfigurationForLogger(string loggerName, TargetWi sb.AppendFormat(CultureInfo.InvariantCulture, "Logger {0} [{1}] =>", loggerName, LogLevel.FromOrdinal(i)); } - for (TargetWithFilterChain afc = targetsByLevel[i]; afc != null; afc = afc.NextInChain) + for (TargetWithFilterChain? afc = targetsByLevel[i]; afc != null; afc = afc.NextInChain) { if (sb is null) { diff --git a/src/NLog/Config/LoggingConfigurationChangedEventArgs.cs b/src/NLog/Config/LoggingConfigurationChangedEventArgs.cs index 17eab604cd..042ad3fd18 100644 --- a/src/NLog/Config/LoggingConfigurationChangedEventArgs.cs +++ b/src/NLog/Config/LoggingConfigurationChangedEventArgs.cs @@ -45,7 +45,7 @@ public class LoggingConfigurationChangedEventArgs : EventArgs /// /// The new configuration. /// The old configuration. - public LoggingConfigurationChangedEventArgs(LoggingConfiguration activatedConfiguration, LoggingConfiguration deactivatedConfiguration) + public LoggingConfigurationChangedEventArgs(LoggingConfiguration? activatedConfiguration, LoggingConfiguration? deactivatedConfiguration) { ActivatedConfiguration = activatedConfiguration; DeactivatedConfiguration = deactivatedConfiguration; @@ -55,7 +55,7 @@ public LoggingConfigurationChangedEventArgs(LoggingConfiguration activatedConfig /// Gets the old configuration. /// /// The old configuration. - public LoggingConfiguration DeactivatedConfiguration { get; } + public LoggingConfiguration? DeactivatedConfiguration { get; } /// /// Gets the new configuration. @@ -64,6 +64,6 @@ public LoggingConfigurationChangedEventArgs(LoggingConfiguration activatedConfig /// New value can be null when unloading configuration during shutdown. /// /// The new configuration. - public LoggingConfiguration ActivatedConfiguration { get; } + public LoggingConfiguration? ActivatedConfiguration { get; } } } diff --git a/src/NLog/Config/LoggingConfigurationElementExtensions.cs b/src/NLog/Config/LoggingConfigurationElementExtensions.cs index 2e2ca9564d..0570ed7602 100644 --- a/src/NLog/Config/LoggingConfigurationElementExtensions.cs +++ b/src/NLog/Config/LoggingConfigurationElementExtensions.cs @@ -31,14 +31,15 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Globalization; -using System.Linq; -using NLog.Common; -using NLog.Internal; - namespace NLog.Config { + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using NLog.Common; + using NLog.Internal; + internal static class LoggingConfigurationElementExtensions { public static bool MatchesName(this ILoggingConfigurationElement section, string expectedName) @@ -60,7 +61,7 @@ public static void AssertName(this ILoggingConfigurationElement section, params public static string GetRequiredValue(this ILoggingConfigurationElement element, string attributeName, string section) { - string value = element.GetOptionalValue(attributeName, null); + var value = element.GetOptionalValue(attributeName, null); if (value is null) { throw new NLogConfigurationException($"Expected {attributeName} on {element.Name} in {section}"); @@ -75,7 +76,7 @@ public static string GetRequiredValue(this ILoggingConfigurationElement element, return value; } - public static string GetOptionalValue(this ILoggingConfigurationElement element, string attributeName, string defaultValue) + public static string? GetOptionalValue(this ILoggingConfigurationElement element, string attributeName, string? defaultValue) { return element.Values .Where(configItem => string.Equals(configItem.Key, attributeName, StringComparison.OrdinalIgnoreCase)) @@ -92,7 +93,7 @@ public static string GetOptionalValue(this ILoggingConfigurationElement element, public static bool GetOptionalBooleanValue(this ILoggingConfigurationElement element, string attributeName, bool defaultValue) { - string value = element.GetOptionalValue(attributeName, null); + var value = element.GetOptionalValue(attributeName, null)?.Trim() ?? string.Empty; if (string.IsNullOrEmpty(value)) { return defaultValue; @@ -100,7 +101,7 @@ public static bool GetOptionalBooleanValue(this ILoggingConfigurationElement ele try { - return Convert.ToBoolean(value.Trim(), CultureInfo.InvariantCulture); + return Convert.ToBoolean(value, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -114,10 +115,27 @@ public static bool GetOptionalBooleanValue(this ILoggingConfigurationElement ele } } - public static string GetConfigItemTypeAttribute(this ILoggingConfigurationElement element, string sectionNameForRequiredValue = null) + public static string GetConfigItemTypeAttribute(this ILoggingConfigurationElement element, string? sectionNameForRequiredValue = null) { var typeAttributeValue = sectionNameForRequiredValue != null ? element.GetRequiredValue("type", sectionNameForRequiredValue) : element.GetOptionalValue("type", null); - return StripOptionalNamespacePrefix(typeAttributeValue)?.Trim(); + return StripOptionalNamespacePrefix(typeAttributeValue ?? string.Empty).Trim(); + } + + /// + /// Returns children elements with the specified element name. + /// + public static IEnumerable FilterChildren(this ILoggingConfigurationElement element, string elementName) + { + if (elementName is null || element?.Children is null) + yield break; + + foreach (var childElement in element.Children) + { + if (childElement.Name.Equals(elementName, StringComparison.OrdinalIgnoreCase)) + { + yield return childElement; + } + } } /// @@ -131,15 +149,11 @@ public static string GetConfigItemTypeAttribute(this ILoggingConfigurationElemen private static string StripOptionalNamespacePrefix(string attributeValue) { if (attributeValue is null) - { - return null; - } + return string.Empty; int p = attributeValue.IndexOf(':'); if (p < 0) - { return attributeValue; - } return attributeValue.Substring(p + 1); } diff --git a/src/NLog/Config/LoggingConfigurationFileLoader.cs b/src/NLog/Config/LoggingConfigurationFileLoader.cs index 7dcb647d7a..672daff1d4 100644 --- a/src/NLog/Config/LoggingConfigurationFileLoader.cs +++ b/src/NLog/Config/LoggingConfigurationFileLoader.cs @@ -37,11 +37,9 @@ namespace NLog.Config using System.Collections.Generic; using System.IO; using System.Security; - using System.Xml; using NLog.Common; using NLog.Internal; using NLog.Internal.Fakeables; - using NLog.Targets; /// /// Enables loading of NLog configuration from a file @@ -55,9 +53,18 @@ public LoggingConfigurationFileLoader(IAppEnvironment appEnvironment) _appEnvironment = appEnvironment; } - public virtual LoggingConfiguration Load(LogFactory logFactory, string filename = null) + public LoggingConfiguration? Load(LogFactory logFactory, string? filename = null) { - if (string.IsNullOrEmpty(filename) || FilePathLayout.DetectFilePathKind(filename) == FilePathKind.Relative) +#if NETFRAMEWORK + if (string.IsNullOrEmpty(filename)) + { + var appConfig = ConfigSectionHandler.AppConfig; + if (appConfig != null) + return appConfig; + } +#endif + + if (filename is null || StringHelpers.IsNullOrWhiteSpace(filename) || FileInfoHelper.IsRelativeFilePath(filename)) { return TryLoadFromFilePaths(logFactory, filename); } @@ -69,12 +76,7 @@ public virtual LoggingConfiguration Load(LogFactory logFactory, string filename return null; } - public virtual void Activated(LogFactory logFactory, LoggingConfiguration config) - { - // Nothing to do - } - - private LoggingConfiguration TryLoadFromFilePaths(LogFactory logFactory, string filename) + private LoggingConfiguration? TryLoadFromFilePaths(LogFactory logFactory, string? filename) { #pragma warning disable CS0618 // Type or member is obsolete var configFileNames = logFactory.GetCandidateConfigFilePaths(filename); @@ -88,7 +90,7 @@ private LoggingConfiguration TryLoadFromFilePaths(LogFactory logFactory, string return null; } - private bool TryLoadLoggingConfiguration(LogFactory logFactory, string configFile, out LoggingConfiguration config) + private bool TryLoadLoggingConfiguration(LogFactory logFactory, string configFile, out LoggingConfiguration? config) { try { @@ -129,42 +131,23 @@ private LoggingConfiguration LoadXmlLoggingConfigurationFile(LogFactory logFacto { InternalLogger.Debug("Reading config from XML file: {0}", configFile); - using (var xmlReader = _appEnvironment.LoadXmlFile(configFile)) + using (var textReader = _appEnvironment.LoadTextFile(configFile)) { - return LoadXmlLoggingConfiguration(xmlReader, configFile, logFactory); - } - } - - private LoggingConfiguration LoadXmlLoggingConfiguration(XmlReader xmlReader, string configFile, LogFactory logFactory) - { - try - { - var newConfig = new XmlLoggingConfiguration(xmlReader, configFile, logFactory); - if (newConfig.InitializeSucceeded != true) + try { - if (ThrowXmlConfigExceptions(configFile, xmlReader, logFactory, out var autoReload)) - { - using (var xmlReaderRetry = _appEnvironment.LoadXmlFile(configFile)) - { - newConfig = new XmlLoggingConfiguration(xmlReaderRetry, configFile, logFactory); // Scan again after having updated LogFactory, to throw correct exception - } - } - else if (autoReload && !newConfig.AutoReload) - { - return CreateEmptyDefaultConfig(configFile, logFactory, autoReload); - } + return new XmlLoggingConfiguration(textReader, configFile, logFactory); } - return newConfig; - } - catch (Exception ex) - { - if (ex.MustBeRethrown() || (logFactory.ThrowConfigExceptions ?? logFactory.ThrowExceptions)) - throw; + catch (Exception ex) + { + if (ex.MustBeRethrown() || (logFactory.ThrowConfigExceptions ?? logFactory.ThrowExceptions)) + throw; - if (ThrowXmlConfigExceptions(configFile, xmlReader, logFactory, out var autoReload)) - throw; + var invalidXml = ex is XmlParserException || ex.InnerException is XmlParserException; + if (ThrowXmlConfigExceptions(configFile, invalidXml, logFactory, out var autoReload)) + throw; - return CreateEmptyDefaultConfig(configFile, logFactory, autoReload); + return CreateEmptyDefaultConfig(configFile, logFactory, autoReload); + } } } @@ -173,7 +156,7 @@ private static LoggingConfiguration CreateEmptyDefaultConfig(string configFile, return new XmlLoggingConfiguration($"", configFile, logFactory); // Empty default config, but monitors file } - private bool ThrowXmlConfigExceptions(string configFile, XmlReader xmlReader, LogFactory logFactory, out bool autoReload) + private static bool ThrowXmlConfigExceptions(string configFile, bool invalidXml, LogFactory logFactory, out bool autoReload) { autoReload = false; @@ -184,7 +167,7 @@ private bool ThrowXmlConfigExceptions(string configFile, XmlReader xmlReader, Lo var fileContent = File.ReadAllText(configFile); - if (xmlReader.ReadState == ReadState.Error) + if (invalidXml) { // Avoid reacting to throwExceptions="true" that only exists in comments, only check when invalid xml if (ScanForBooleanParameter(fileContent, "throwExceptions", true)) @@ -223,14 +206,11 @@ private static bool ScanForBooleanParameter(string fileContent, string parameter /// /// Get default file paths (including filename) for possible NLog config files. /// - public IEnumerable GetDefaultCandidateConfigFilePaths(string filename = null) + public IEnumerable GetDefaultCandidateConfigFilePaths(string? filename = null) { string baseDirectory = PathHelpers.TrimDirectorySeparators(_appEnvironment.AppDomainBaseDirectory); -#if !NETSTANDARD1_3 string entryAssemblyLocation = PathHelpers.TrimDirectorySeparators(_appEnvironment.EntryAssemblyLocation); -#else - string entryAssemblyLocation = string.Empty; -#endif + if (filename is null) { // Scan for process specific nlog-files @@ -265,17 +245,17 @@ public IEnumerable GetDefaultCandidateConfigFilePaths(string filename = foreach (var filePath in GetPrivateBinPathNLogLocations(baseDirectory, nlogConfigFile, platformFileSystemCaseInsensitive ? nLogConfigFileLowerCase : string.Empty)) yield return filePath; - string nlogAssemblyLocation = filename is null ? LookupNLogAssemblyLocation() : null; + var nlogAssemblyLocation = filename is null ? LookupNLogAssemblyLocation() : null; if (!string.IsNullOrEmpty(nlogAssemblyLocation)) yield return nlogAssemblyLocation + ".nlog"; } - private static string LookupNLogAssemblyLocation() + private static string? LookupNLogAssemblyLocation() { -#if !NETSTANDARD1_3 - var nlogAssembly = typeof(LogFactory).GetAssembly(); + var nlogAssembly = typeof(LogFactory).Assembly; // Get path to NLog.dll.nlog only if the assembly is not in the GAC - var nlogAssemblyLocation = nlogAssembly.Location; + // Notice NLog.dll can be loaded from nuget-cache using NTFS-hard-link, and return unexpected file-location. + var nlogAssemblyLocation = AssemblyHelpers.GetAssemblyFileLocation(nlogAssembly); if (!string.IsNullOrEmpty(nlogAssemblyLocation)) { #if NETFRAMEWORK @@ -286,7 +266,7 @@ private static string LookupNLogAssemblyLocation() #endif return nlogAssemblyLocation; } -#endif + return null; } @@ -308,7 +288,7 @@ public IEnumerable GetAppSpecificNLogLocations(string baseDirectory, str yield return Path.ChangeExtension(configurationFile.Replace(vshostSubStr, "."), ".nlog"); } } -#if NETSTANDARD && !NETSTANDARD1_3 +#if !NETFRAMEWORK else { if (string.IsNullOrEmpty(entryAssemblyLocation)) diff --git a/src/NLog/Config/LoggingConfigurationParser.cs b/src/NLog/Config/LoggingConfigurationParser.cs index bf31d35e98..24d549decd 100644 --- a/src/NLog/Config/LoggingConfigurationParser.cs +++ b/src/NLog/Config/LoggingConfigurationParser.cs @@ -31,30 +31,31 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using JetBrains.Annotations; -using NLog.Common; -using NLog.Filters; -using NLog.Internal; -using NLog.Layouts; -using NLog.Targets; -using NLog.Targets.Wrappers; -using NLog.Time; - namespace NLog.Config { + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Reflection; + using JetBrains.Annotations; + using NLog.Common; + using NLog.Filters; + using NLog.Internal; + using NLog.Layouts; + using NLog.Targets; + using NLog.Targets.Wrappers; + using NLog.Time; + /// /// Loads NLog configuration from /// + /// + /// Make sure to update official NLog.xsd schema, when adding new config-options outside targets/layouts + /// public abstract class LoggingConfigurationParser : LoggingConfiguration { - private readonly ServiceRepository _serviceRepository; - /// /// Constructor /// @@ -62,7 +63,6 @@ public abstract class LoggingConfigurationParser : LoggingConfiguration protected LoggingConfigurationParser(LogFactory logFactory) : base(logFactory) { - _serviceRepository = logFactory.ServiceRepository; } /// @@ -70,7 +70,7 @@ protected LoggingConfigurationParser(LogFactory logFactory) /// /// /// Directory where the NLog-config-file was loaded from - protected void LoadConfig(ILoggingConfigurationElement nlogConfig, string basePath) + protected void LoadConfig(ILoggingConfigurationElement nlogConfig, string? basePath) { InternalLogger.Trace("ParseNLogConfig"); nlogConfig.AssertName("nlog"); @@ -89,12 +89,19 @@ protected void LoadConfig(ILoggingConfigurationElement nlogConfig, string basePa } var rulesList = new List(); + var rulesInsertPosition = LoggingRules.Count; //parse all other direct elements foreach (var child in validatedConfig.ValidChildren) { if (child.MatchesName("rules")) { + if (rulesList.Count == 0) + { + // Give higher priority to LoggingRules from current config-element + // But until having read first LoggingRule, then allow new LoggingRules from include-files + rulesInsertPosition = LoggingRules.Count; + } //postpone parsing to the end rulesList.Add(child); } @@ -112,7 +119,7 @@ protected void LoadConfig(ILoggingConfigurationElement nlogConfig, string basePa foreach (var ruleChild in rulesList) { - ParseRulesElement(ruleChild, LoggingRules); + ParseRulesElement(ruleChild, LoggingRules, rulesInsertPosition); } } @@ -120,7 +127,7 @@ private void SetNLogElementSettings(ILoggingConfigurationElement nlogConfig) { var sortedList = CreateUniqueSortedListFromConfig(nlogConfig); - CultureInfo defaultCultureInfo = DefaultCultureInfo ?? LogFactory._defaultCultureInfo; + CultureInfo? defaultCultureInfo = DefaultCultureInfo ?? LogFactory._defaultCultureInfo; bool? parseMessageTemplates = null; bool internalLoggerEnabled = false; bool autoLoadExtensions = false; @@ -129,52 +136,47 @@ private void SetNLogElementSettings(ILoggingConfigurationElement nlogConfig) switch (configItem.Key.ToUpperInvariant()) { case "THROWEXCEPTIONS": - LogFactory.ThrowExceptions = ParseBooleanValue(configItem.Key, configItem.Value, LogFactory.ThrowExceptions); + LogFactory.ThrowExceptions = ParseBooleanValue(configItem.Key, configItem.Value ?? string.Empty, LogFactory.ThrowExceptions); break; case "THROWCONFIGEXCEPTIONS": - LogFactory.ThrowConfigExceptions = ParseNullableBooleanValue(configItem.Key, configItem.Value, false); + LogFactory.ThrowConfigExceptions = ParseNullableBooleanValue(configItem.Key, configItem.Value ?? string.Empty, false); break; case "INTERNALLOGLEVEL": - InternalLogger.LogLevel = ParseLogLevelSafe(configItem.Key, configItem.Value, InternalLogger.LogLevel); + InternalLogger.LogLevel = ParseLogLevelSafe(configItem.Key, configItem.Value ?? string.Empty, InternalLogger.LogLevel); internalLoggerEnabled = InternalLogger.LogLevel != LogLevel.Off; break; case "USEINVARIANTCULTURE": - if (ParseBooleanValue(configItem.Key, configItem.Value, false)) + if (ParseBooleanValue(configItem.Key, configItem.Value ?? string.Empty, false)) defaultCultureInfo = DefaultCultureInfo = CultureInfo.InvariantCulture; break; case "KEEPVARIABLESONRELOAD": - LogFactory.KeepVariablesOnReload = ParseBooleanValue(configItem.Key, configItem.Value, LogFactory.KeepVariablesOnReload); + LogFactory.KeepVariablesOnReload = ParseBooleanValue(configItem.Key, configItem.Value ?? string.Empty, LogFactory.KeepVariablesOnReload); break; case "INTERNALLOGTOCONSOLE": - InternalLogger.LogToConsole = ParseBooleanValue(configItem.Key, configItem.Value, InternalLogger.LogToConsole); + InternalLogger.LogToConsole = ParseBooleanValue(configItem.Key, configItem.Value ?? string.Empty, InternalLogger.LogToConsole); break; case "INTERNALLOGTOCONSOLEERROR": - InternalLogger.LogToConsoleError = ParseBooleanValue(configItem.Key, configItem.Value, InternalLogger.LogToConsoleError); + InternalLogger.LogToConsoleError = ParseBooleanValue(configItem.Key, configItem.Value ?? string.Empty, InternalLogger.LogToConsoleError); break; case "INTERNALLOGFILE": InternalLogger.LogFile = configItem.Value?.Trim(); break; - case "INTERNALLOGTOTRACE": -#pragma warning disable CS0618 // Type or member is obsolete - InternalLogger.LogToTrace = ParseBooleanValue(configItem.Key, configItem.Value, InternalLogger.LogToTrace); -#pragma warning restore CS0618 // Type or member is obsolete - break; case "INTERNALLOGINCLUDETIMESTAMP": - InternalLogger.IncludeTimestamp = ParseBooleanValue(configItem.Key, configItem.Value, InternalLogger.IncludeTimestamp); + InternalLogger.IncludeTimestamp = ParseBooleanValue(configItem.Key, configItem.Value ?? string.Empty, InternalLogger.IncludeTimestamp); break; case "GLOBALTHRESHOLD": - LogFactory.GlobalThreshold = ParseLogLevelSafe(configItem.Key, configItem.Value, LogFactory.GlobalThreshold); + LogFactory.GlobalThreshold = ParseLogLevelSafe(configItem.Key, configItem.Value ?? string.Empty, LogFactory.GlobalThreshold); break; // expanding variables not possible here, they are created later case "PARSEMESSAGETEMPLATES": - parseMessageTemplates = ParseNullableBooleanValue(configItem.Key, configItem.Value, true); + parseMessageTemplates = ParseNullableBooleanValue(configItem.Key, configItem.Value ?? string.Empty, true); break; case "AUTOSHUTDOWN": - LogFactory.AutoShutdown = ParseBooleanValue(configItem.Key, configItem.Value, true); + LogFactory.AutoShutdown = ParseBooleanValue(configItem.Key, configItem.Value ?? string.Empty, true); break; case "AUTORELOAD": break; // Ignore here, used by other logic case "AUTOLOADEXTENSIONS": - autoLoadExtensions = ParseBooleanValue(configItem.Key, configItem.Value, false); + autoLoadExtensions = ParseBooleanValue(configItem.Key, configItem.Value ?? string.Empty, false); break; default: var configException = new NLogConfigurationException($"Unrecognized value '{configItem.Key}'='{configItem.Value}' for element '{nlogConfig.Name}'"); @@ -196,10 +198,16 @@ private void SetNLogElementSettings(ILoggingConfigurationElement nlogConfig) if (autoLoadExtensions) { - ConfigurationItemFactory.Default.AssemblyLoader.ScanForAutoLoadExtensions(ConfigurationItemFactory.Default); + ScanForAutoLoadExtensions(); } - _serviceRepository.ParseMessageTemplates(parseMessageTemplates); + LogFactory.ServiceRepository.ParseMessageTemplates(LogFactory, parseMessageTemplates); + } + + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow extension loading from config", "IL2026")] + private static void ScanForAutoLoadExtensions() + { + ConfigurationItemFactory.Default.AssemblyLoader.ScanForAutoLoadExtensions(ConfigurationItemFactory.Default); } /// @@ -207,14 +215,15 @@ private void SetNLogElementSettings(ILoggingConfigurationElement nlogConfig) /// /// /// - private ICollection> CreateUniqueSortedListFromConfig(ILoggingConfigurationElement nlogConfig) + private ICollection> CreateUniqueSortedListFromConfig(ILoggingConfigurationElement nlogConfig) { - var dict = ValidatedConfigurationElement.Create(nlogConfig, LogFactory).ValueLookup; + var configurationElement = ValidatedConfigurationElement.Create(nlogConfig, LogFactory); + var dict = configurationElement.Values; if (dict.Count <= 1) return dict; - var sortedList = new List>(dict.Count); - var highPriorityList = new[] + var sortedList = new List>(dict.Count); + var highPriorityList = new HashSet(StringComparer.OrdinalIgnoreCase) { "ThrowExceptions", "ThrowConfigExceptions", @@ -224,15 +233,18 @@ private ICollection> CreateUniqueSortedListFromConf }; foreach (var highPrioritySetting in highPriorityList) { - if (dict.TryGetValue(highPrioritySetting, out var settingValue)) + var settingValue = configurationElement.GetOptionalValue(highPrioritySetting, null); + if (settingValue != null) { - sortedList.Add(new KeyValuePair(highPrioritySetting, settingValue)); - dict.Remove(highPrioritySetting); + sortedList.Add(new KeyValuePair(highPrioritySetting, settingValue)); } } - foreach (var configItem in dict) + foreach (var configItem in configurationElement.Values) { - sortedList.Add(configItem); + if (!highPriorityList.Contains(configItem.Key)) + { + sortedList.Add(configItem); + } } return sortedList; } @@ -248,8 +260,7 @@ private LogLevel ParseLogLevelSafe(string propertyName, string propertyValue, Lo { try { - var internalLogLevel = LogLevel.FromString(propertyValue?.Trim()); - return internalLogLevel; + return LogLevel.FromString(propertyValue?.Trim() ?? string.Empty); } catch (Exception exception) { @@ -294,22 +305,22 @@ protected virtual bool ParseNLogSection(ILoggingConfigurationElement configSecti return false; } - private void ParseExtensionsElement(ValidatedConfigurationElement extensionsElement, string baseDirectory) + private void ParseExtensionsElement(ValidatedConfigurationElement extensionsElement, string? baseDirectory) { extensionsElement.AssertName("extensions"); foreach (var childItem in extensionsElement.ValidChildren) { - string prefix = null; - string type = null; - string assemblyFile = null; - string assemblyName = null; + string itemNamePrefix = string.Empty; + string? type = null; + string? assemblyFile = null; + string? assemblyName = null; foreach (var childProperty in childItem.Values) { if (MatchesName(childProperty.Key, "prefix")) { - prefix = childProperty.Value + "."; + itemNamePrefix = childProperty.Value + "."; } else if (MatchesName(childProperty.Key, "type")) { @@ -331,30 +342,33 @@ private void ParseExtensionsElement(ValidatedConfigurationElement extensionsElem } } - if (!StringHelpers.IsNullOrWhiteSpace(type)) + if (type != null && !StringHelpers.IsNullOrWhiteSpace(type)) { - RegisterExtension(type, prefix); + RegisterExtension(type, itemNamePrefix); } -#if !NETSTANDARD1_3 - if (!StringHelpers.IsNullOrWhiteSpace(assemblyFile)) + if (assemblyFile != null && !StringHelpers.IsNullOrWhiteSpace(assemblyFile)) { - ParseExtensionWithAssemblyFile(baseDirectory, assemblyFile, prefix); + ParseExtensionWithAssemblyFile(assemblyFile, baseDirectory, itemNamePrefix); continue; } -#endif - if (!StringHelpers.IsNullOrWhiteSpace(assemblyName)) + + if (assemblyName != null && !StringHelpers.IsNullOrWhiteSpace(assemblyName)) { - ParseExtensionWithAssemblyName(assemblyName?.Trim(), prefix); + ParseExtensionWithAssemblyName(assemblyName.Trim(), itemNamePrefix); } } } + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow extension loading from config", "IL2072")] private void RegisterExtension(string typeName, string itemNamePrefix) { try { - ConfigurationItemFactory.Default.AssemblyLoader.LoadTypeFromName(ConfigurationItemFactory.Default, typeName, itemNamePrefix); + var configType = PropertyTypeConverter.ConvertToType(typeName, true); +#pragma warning disable CS0618 // Type or member is obsolete + ConfigurationItemFactory.Default.RegisterType(configType, itemNamePrefix); +#pragma warning restore CS0618 // Type or member is obsolete } catch (Exception exception) { @@ -368,8 +382,8 @@ private void RegisterExtension(string typeName, string itemNamePrefix) } } -#if !NETSTANDARD1_3 - private void ParseExtensionWithAssemblyFile(string baseDirectory, string assemblyFile, string prefix) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow extension loading from config", "IL2026")] + private void ParseExtensionWithAssemblyFile(string assemblyFile, string? baseDirectory, string prefix) { try { @@ -386,7 +400,6 @@ private void ParseExtensionWithAssemblyFile(string baseDirectory, string assembl throw configException; } } -#endif private bool RegisterExtensionFromAssemblyName(string assemblyName, string originalTypeName) { @@ -394,11 +407,12 @@ private bool RegisterExtensionFromAssemblyName(string assemblyName, string origi return ParseExtensionWithAssemblyName(assemblyName, string.Empty); } - private bool ParseExtensionWithAssemblyName(string assemblyName, string prefix) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow extension loading from config", "IL2026")] + private bool ParseExtensionWithAssemblyName(string assemblyName, string itemNamePrefix) { try { - ConfigurationItemFactory.Default.AssemblyLoader.LoadAssemblyFromName(ConfigurationItemFactory.Default, assemblyName, prefix); + ConfigurationItemFactory.Default.AssemblyLoader.LoadAssemblyFromName(ConfigurationItemFactory.Default, assemblyName, itemNamePrefix); return true; } catch (Exception exception) @@ -417,8 +431,8 @@ private bool ParseExtensionWithAssemblyName(string assemblyName, string prefix) private void ParseVariableElement(ValidatedConfigurationElement variableElement) { - string variableName = null; - string variableValue = null; + string? variableName = null; + string? variableValue = null; foreach (var childProperty in variableElement.Values) { if (MatchesName(childProperty.Key, "name")) @@ -433,20 +447,20 @@ private void ParseVariableElement(ValidatedConfigurationElement variableElement) } } - if (!AssertNonEmptyValue(variableName, "name", variableElement.Name, "variables")) + if (!AssertNonEmptyValue(variableName, "name", variableElement.Name, "variables") || variableName is null) return; - Layout variableLayout = variableValue is null + Layout? variableLayout = variableValue is null ? ParseVariableLayoutValue(variableElement) : CreateSimpleLayout(ExpandSimpleVariables(variableValue)); - if (!AssertNotNullValue(variableLayout, "value", variableElement.Name, "variables")) + if (!AssertNotNullValue(variableLayout, "value", variableElement.Name, "variables") || variableLayout is null) return; InsertParsedConfigVariable(variableName, variableLayout); } - private Layout ParseVariableLayoutValue(ValidatedConfigurationElement variableElement) + private Layout? ParseVariableLayoutValue(ValidatedConfigurationElement variableElement) { var childElement = variableElement.ValidChildren.FirstOrDefault(); if (childElement != null) @@ -471,7 +485,7 @@ private void ParseTimeElement(ValidatedConfigurationElement timeElement) { timeElement.AssertName("time"); - string timeSourceType = null; + string? timeSourceType = null; foreach (var childProperty in timeElement.Values) { if (MatchesName(childProperty.Key, "type")) @@ -486,10 +500,10 @@ private void ParseTimeElement(ValidatedConfigurationElement timeElement) } } - if (!AssertNonEmptyValue(timeSourceType, "type", timeElement.Name, string.Empty)) + if (!AssertNonEmptyValue(timeSourceType, "type", timeElement.Name, string.Empty) || timeSourceType is null) return; - TimeSource newTimeSource = FactoryCreateInstance(timeSourceType, ConfigurationItemFactory.Default.TimeSourceFactory); + var newTimeSource = FactoryCreateInstance(timeSourceType, ConfigurationItemFactory.Default.TimeSourceFactory); if (newTimeSource != null) { ConfigureFromAttributesAndElements(newTimeSource, timeElement); @@ -499,7 +513,7 @@ private void ParseTimeElement(ValidatedConfigurationElement timeElement) } [ContractAnnotation("value:notnull => true")] - private bool AssertNotNullValue(object value, string propertyName, string elementName, string sectionName) + private bool AssertNotNullValue(object? value, string propertyName, string elementName, string sectionName) { if (value is null) return AssertNonEmptyValue(string.Empty, propertyName, elementName, sectionName); @@ -508,7 +522,7 @@ private bool AssertNotNullValue(object value, string propertyName, string elemen } [ContractAnnotation("value:null => false")] - private bool AssertNonEmptyValue(string value, string propertyName, string elementName, string sectionName) + private bool AssertNonEmptyValue(string? value, string propertyName, string elementName, string sectionName) { if (!StringHelpers.IsNullOrWhiteSpace(value)) return true; @@ -523,48 +537,46 @@ private bool AssertNonEmptyValue(string value, string propertyName, string eleme /// /// Parse {Rules} xml element /// - /// - /// Rules are added to this parameter. - private void ParseRulesElement(ValidatedConfigurationElement rulesElement, IList rulesCollection) + private void ParseRulesElement(ValidatedConfigurationElement rulesElement, IList rulesCollection, int rulesInsertPosition) { InternalLogger.Trace("ParseRulesElement"); rulesElement.AssertName("rules"); + if (rulesInsertPosition > rulesCollection.Count) + { + rulesInsertPosition = rulesCollection.Count; + } + foreach (var childItem in rulesElement.ValidChildren) { - LoggingRule loggingRule = ParseRuleElement(childItem); + var loggingRule = ParseRuleElement(childItem); if (loggingRule != null) { lock (rulesCollection) { - rulesCollection.Add(loggingRule); + rulesCollection.Insert(rulesInsertPosition++, loggingRule); } } } } - private LogLevel LogLevelFromString(string text) - { - return LogLevel.FromString(ExpandSimpleVariables(text).Trim()); - } - /// /// Parse {Logger} xml element /// /// - private LoggingRule ParseRuleElement(ValidatedConfigurationElement loggerElement) + private LoggingRule? ParseRuleElement(ValidatedConfigurationElement loggerElement) { - string minLevel = null; - string maxLevel = null; - string finalMinLevel = null; - string enableLevels = null; + string? minLevel = null; + string? maxLevel = null; + string? finalMinLevel = null; + string? enableLevels = null; - string ruleName = null; - string namePattern = null; + string? ruleName = null; + string? namePattern = null; bool enabled = true; bool final = false; - string writeTargets = null; - string filterDefaultAction = null; + string? writeTargets = null; + string? filterDefaultAction = null; foreach (var childProperty in loggerElement.Values) { switch (childProperty.Key?.Trim().ToUpperInvariant()) @@ -582,7 +594,7 @@ private LoggingRule ParseRuleElement(ValidatedConfigurationElement loggerElement namePattern = childProperty.Value; break; case "ENABLED": - enabled = ParseBooleanValue(childProperty.Key, childProperty.Value, true); + enabled = ParseBooleanValue(childProperty.Key, childProperty.Value ?? string.Empty, true); break; case "APPENDTO": writeTargets = childProperty.Value; @@ -591,7 +603,7 @@ private LoggingRule ParseRuleElement(ValidatedConfigurationElement loggerElement writeTargets = childProperty.Value; break; case "FINAL": - final = ParseBooleanValue(childProperty.Key, childProperty.Value, false); + final = ParseBooleanValue(childProperty.Key, childProperty.Value ?? string.Empty, false); break; case "LEVEL": case "LEVELS": @@ -653,48 +665,50 @@ private LoggingRule ParseRuleElement(ValidatedConfigurationElement loggerElement private void EnableLevelsForRule( LoggingRule rule, - string enableLevels, - string minLevel, - string maxLevel, - string finalMinLevel) + string? enableLevels, + string? minLevel, + string? maxLevel, + string? finalMinLevel) { if (enableLevels != null) { - enableLevels = ExpandSimpleVariables(enableLevels); - finalMinLevel = finalMinLevel != null ? ExpandSimpleVariables(finalMinLevel) : finalMinLevel; + enableLevels = ExpandSimpleVariables(enableLevels).Trim(); + finalMinLevel = ExpandSimpleVariables(finalMinLevel).Trim(); + if (IsLevelLayout(enableLevels) || IsLevelLayout(finalMinLevel)) { - SimpleLayout simpleLayout = ParseLevelLayout(enableLevels); - SimpleLayout finalMinLevelLayout = ParseLevelLayout(finalMinLevel); + var simpleLayout = ParseLevelLayout(enableLevels); + var finalMinLevelLayout = ParseLevelLayout(finalMinLevel); rule.EnableLoggingForLevelLayout(simpleLayout, finalMinLevelLayout); } else { foreach (var logLevel in enableLevels.SplitAndTrimTokens(',')) { - rule.EnableLoggingForLevel(LogLevelFromString(logLevel)); + rule.EnableLoggingForLevel(LogLevel.FromString(logLevel)); } - if (finalMinLevel != null) - rule.FinalMinLevel = LogLevelFromString(finalMinLevel); + if (!string.IsNullOrEmpty(finalMinLevel)) + rule.FinalMinLevel = LogLevel.FromString(finalMinLevel); } } else { - minLevel = minLevel != null ? ExpandSimpleVariables(minLevel) : minLevel; - maxLevel = maxLevel != null ? ExpandSimpleVariables(maxLevel) : maxLevel; - finalMinLevel = finalMinLevel != null ? ExpandSimpleVariables(finalMinLevel) : finalMinLevel; + minLevel = ExpandSimpleVariables(minLevel).Trim(); + maxLevel = ExpandSimpleVariables(maxLevel).Trim(); + finalMinLevel = ExpandSimpleVariables(finalMinLevel).Trim(); + if (IsLevelLayout(minLevel) || IsLevelLayout(maxLevel) || IsLevelLayout(finalMinLevel)) { - SimpleLayout finalMinLevelLayout = ParseLevelLayout(finalMinLevel); - SimpleLayout minLevelLayout = ParseLevelLayout(minLevel) ?? finalMinLevelLayout; - SimpleLayout maxLevelLayout = ParseLevelLayout(maxLevel); + var finalMinLevelLayout = ParseLevelLayout(finalMinLevel); + var minLevelLayout = ParseLevelLayout(minLevel) ?? finalMinLevelLayout; + var maxLevelLayout = ParseLevelLayout(maxLevel); rule.EnableLoggingForLevelsLayout(minLevelLayout, maxLevelLayout, finalMinLevelLayout); } else { - LogLevel finalMinLogLevel = finalMinLevel != null ? LogLevelFromString(finalMinLevel) : null; - LogLevel minLogLevel = minLevel != null ? LogLevelFromString(minLevel) : (finalMinLogLevel ?? LogLevel.MinLevel); - LogLevel maxLogLevel = maxLevel != null ? LogLevelFromString(maxLevel) : LogLevel.MaxLevel; + var finalMinLogLevel = string.IsNullOrEmpty(finalMinLevel) ? null : LogLevel.FromString(finalMinLevel); + var minLogLevel = string.IsNullOrEmpty(minLevel) ? (finalMinLogLevel ?? LogLevel.MinLevel) : LogLevel.FromString(minLevel); + var maxLogLevel = string.IsNullOrEmpty(maxLevel) ? LogLevel.MaxLevel : LogLevel.FromString(maxLevel); rule.SetLoggingLevels(minLogLevel, maxLogLevel); if (finalMinLogLevel != null) rule.FinalMinLevel = finalMinLogLevel; @@ -702,27 +716,30 @@ private void EnableLevelsForRule( } } - private static bool IsLevelLayout(string level) + private static bool IsLevelLayout(string? level) { return level?.IndexOf('{') >= 0; } - private SimpleLayout ParseLevelLayout(string levelLayout) + private SimpleLayout? ParseLevelLayout(string levelLayout) { - SimpleLayout simpleLayout = !StringHelpers.IsNullOrWhiteSpace(levelLayout) ? CreateSimpleLayout(levelLayout) : null; - simpleLayout?.Initialize(this); + if (levelLayout is null || StringHelpers.IsNullOrWhiteSpace(levelLayout)) + return null; + + var simpleLayout = CreateSimpleLayout(levelLayout); + simpleLayout.Initialize(this); return simpleLayout; } - private void ParseLoggingRuleTargets(string writeTargets, LoggingRule rule) + private void ParseLoggingRuleTargets(string? writeTargets, LoggingRule rule) { - writeTargets = ExpandSimpleVariables(writeTargets); + writeTargets = ExpandSimpleVariables(writeTargets).Trim(); if (string.IsNullOrEmpty(writeTargets)) return; foreach (string targetName in writeTargets.SplitAndTrimTokens(',')) { - Target target = FindTargetByName(targetName); + var target = FindTargetByName(targetName); if (target != null) { rule.Targets.Add(target); @@ -738,11 +755,11 @@ private void ParseLoggingRuleTargets(string writeTargets, LoggingRule rule) } [Obsolete("Very exotic feature without any unit-tests, not sure if it works. Marked obsolete with NLog v5.3")] - private void ParseLoggingRuleChildren(ValidatedConfigurationElement loggerElement, LoggingRule rule, string filterDefaultAction = null) + private void ParseLoggingRuleChildren(ValidatedConfigurationElement loggerElement, LoggingRule rule, string? filterDefaultAction = null) { foreach (var child in loggerElement.ValidChildren) { - LoggingRule childRule = null; + LoggingRule? childRule = null; if (child.MatchesName("filters")) { ParseLoggingRuleFilters(rule, child, filterDefaultAction); @@ -774,14 +791,14 @@ private void ParseLoggingRuleChildren(ValidatedConfigurationElement loggerElemen } } - private void ParseLoggingRuleFilters(LoggingRule rule, ValidatedConfigurationElement filtersElement, string filterDefaultAction = null) + private void ParseLoggingRuleFilters(LoggingRule rule, ValidatedConfigurationElement filtersElement, string? filterDefaultAction = null) { filtersElement.AssertName("filters"); filterDefaultAction = filtersElement.GetOptionalValue("defaultAction", null) ?? filtersElement.GetOptionalValue(nameof(rule.FilterDefaultAction), null) ?? filterDefaultAction; if (filterDefaultAction != null) { - if (ConversionHelpers.TryParseEnum(filterDefaultAction, typeof(FilterResult), out var enumValue)) + if (ConversionHelpers.TryParseEnum(filterDefaultAction, typeof(FilterResult), out var enumValue) && enumValue != null) { rule.FilterDefaultAction = (FilterResult)enumValue; } @@ -796,9 +813,12 @@ private void ParseLoggingRuleFilters(LoggingRule rule, ValidatedConfigurationEle foreach (var filterElement in filtersElement.ValidChildren) { var filterType = filterElement.GetOptionalValue("type", null) ?? filterElement.Name; - Filter filter = FactoryCreateInstance(filterType, ConfigurationItemFactory.Default.FilterFactory); - ConfigureFromAttributesAndElements(filter, filterElement); - rule.Filters.Add(filter); + Filter? filter = FactoryCreateInstance(filterType, ConfigurationItemFactory.Default.FilterFactory); + if (filter != null) + { + ConfigureFromAttributesAndElements(filter, filterElement); + rule.Filters.Add(filter); + } } } @@ -822,16 +842,16 @@ private void ParseTargetsElement(ValidatedConfigurationElement targetsElement) { targetsElement.AssertName("targets", "appenders"); - bool asyncWrap = ParseBooleanValue("async", targetsElement.GetOptionalValue("async", "false"), false); + bool asyncWrap = ParseBooleanValue("async", targetsElement.GetOptionalValue("async", "false") ?? string.Empty, false); - ValidatedConfigurationElement defaultWrapperElement = null; - Dictionary typeNameToDefaultTargetParameters = null; + ValidatedConfigurationElement? defaultWrapperElement = null; + Dictionary? typeNameToDefaultTargetParameters = null; foreach (var targetElement in targetsElement.ValidChildren) { - string targetTypeName = targetElement.GetConfigItemTypeAttribute(); - string targetValueName = targetElement.GetOptionalValue("name", null); - targetValueName = string.IsNullOrEmpty(targetValueName) ? targetElement.Name : $"{targetElement.Name}(Name={targetValueName})"; + var targetTypeName = targetElement.GetConfigItemTypeAttribute(); + var targetValueName = targetElement.GetOptionalValue("name", null) ?? string.Empty; + targetValueName = string.IsNullOrEmpty(targetValueName) ? (targetElement.Name ?? string.Empty) : $"{targetElement.Name}(Name={targetValueName})"; switch (targetElement.Name?.Trim().ToUpperInvariant()) { @@ -871,7 +891,7 @@ private void ParseTargetsElement(ValidatedConfigurationElement targetsElement) } } - private static Dictionary RegisterNewTargetDefaultParameters(Dictionary typeNameToDefaultTargetParameters, ValidatedConfigurationElement targetElement, string targetTypeName) + private static Dictionary RegisterNewTargetDefaultParameters(Dictionary? typeNameToDefaultTargetParameters, ValidatedConfigurationElement targetElement, string targetTypeName) { if (typeNameToDefaultTargetParameters is null) typeNameToDefaultTargetParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -879,9 +899,9 @@ private static Dictionary RegisterNewTarg return typeNameToDefaultTargetParameters; } - private void AddNewTargetFromConfig(string targetTypeName, ValidatedConfigurationElement targetElement, bool asyncWrap, Dictionary typeNameToDefaultTargetParameters = null, ValidatedConfigurationElement defaultWrapperElement = null) + private void AddNewTargetFromConfig(string targetTypeName, ValidatedConfigurationElement targetElement, bool asyncWrap, Dictionary? typeNameToDefaultTargetParameters = null, ValidatedConfigurationElement? defaultWrapperElement = null) { - Target newTarget = null; + Target? newTarget = null; try { @@ -919,16 +939,16 @@ private void AddNewTargetFromConfig(string targetTypeName, ValidatedConfiguratio } } - private Target CreateTargetType(string targetTypeName) + private Target? CreateTargetType(string targetTypeName) { return FactoryCreateInstance(targetTypeName, ConfigurationItemFactory.Default.TargetFactory); } - private void ParseTargetElement(Target target, ValidatedConfigurationElement targetElement, Dictionary typeNameToDefaultTargetParameters = null) + private void ParseTargetElement(Target target, ValidatedConfigurationElement targetElement, Dictionary? typeNameToDefaultTargetParameters = null) { - string targetTypeName = targetElement.GetConfigItemTypeAttribute("targets"); - if (typeNameToDefaultTargetParameters != null && - typeNameToDefaultTargetParameters.TryGetValue(targetTypeName, out var defaults)) + var targetTypeName = targetElement.GetConfigItemTypeAttribute("targets"); + if ( typeNameToDefaultTargetParameters != null + && typeNameToDefaultTargetParameters.TryGetValue(targetTypeName, out var defaults)) { ParseTargetElement(target, defaults, null); } @@ -959,13 +979,13 @@ private void ParseTargetElement(Target target, ValidatedConfigurationElement tar private bool ParseTargetWrapper( WrapperTargetBase wrapper, ValidatedConfigurationElement childElement, - Dictionary typeNameToDefaultTargetParameters) + Dictionary? typeNameToDefaultTargetParameters) { if (IsTargetRefElement(childElement.Name)) { var targetName = childElement.GetRequiredValue("name", GetName(wrapper)); - Target newTarget = FindTargetByName(targetName); + var newTarget = FindTargetByName(targetName); if (newTarget is null) { var configException = new NLogConfigurationException($"Referenced target '{targetName}' not found."); @@ -979,9 +999,9 @@ private bool ParseTargetWrapper( if (IsTargetElement(childElement.Name)) { - string targetTypeName = childElement.GetConfigItemTypeAttribute(GetName(wrapper)); + var targetTypeName = childElement.GetConfigItemTypeAttribute(GetName(wrapper)); - Target newTarget = CreateTargetType(targetTypeName); + var newTarget = CreateTargetType(targetTypeName); if (newTarget != null) { ParseTargetElement(newTarget, childElement, typeNameToDefaultTargetParameters); @@ -1013,8 +1033,8 @@ private bool ParseTargetWrapper( private bool ParseCompoundTarget( CompoundTargetBase compound, ValidatedConfigurationElement childElement, - Dictionary typeNameToDefaultTargetParameters, - string targetName) + Dictionary? typeNameToDefaultTargetParameters, + string? targetName) { if (MatchesName(childElement.Name, "targets") || MatchesName(childElement.Name, "appenders")) { @@ -1030,7 +1050,7 @@ private bool ParseCompoundTarget( { targetName = childElement.GetRequiredValue("name", GetName(compound)); - Target newTarget = FindTargetByName(targetName); + var newTarget = FindTargetByName(targetName); if (newTarget is null) { throw new NLogConfigurationException("Referenced target '" + targetName + "' not found."); @@ -1042,9 +1062,9 @@ private bool ParseCompoundTarget( if (IsTargetElement(childElement.Name)) { - string targetTypeName = childElement.GetConfigItemTypeAttribute(GetName(compound)); + var targetTypeName = childElement.GetConfigItemTypeAttribute(GetName(compound)); - Target newTarget = CreateTargetType(targetTypeName); + var newTarget = CreateTargetType(targetTypeName); if (newTarget != null) { if (targetName != null) @@ -1068,10 +1088,10 @@ private bool ParseCompoundTarget( private void ConfigureObjectFromAttributes(T targetObject, ValidatedConfigurationElement element, bool ignoreType = true) where T : class { - foreach (var kvp in element.ValueLookup) + foreach (var kvp in element.Values) { - string childName = kvp.Key; - string childValue = kvp.Value; + var childName = kvp.Key; + var childValue = kvp.Value; if (ignoreType && MatchesName(childName, "type")) { @@ -1082,19 +1102,15 @@ private void ConfigureObjectFromAttributes(T targetObject, ValidatedConfigura } } - private void SetPropertyValueFromString(T targetObject, string propertyName, string propertyValue, ValidatedConfigurationElement element) where T : class + private void SetPropertyValueFromString(T targetObject, string propertyName, string? propertyValue, ValidatedConfigurationElement element) where T : class { try { if (targetObject is null) - { throw new NLogConfigurationException($"'{typeof(T).Name}' is null, and cannot assign property '{propertyName}'='{propertyValue}'"); - } - if (!PropertyHelper.TryGetPropertyInfo(ConfigurationItemFactory.Default, targetObject, propertyName, out var propertyInfo)) - { + if (!PropertyHelper.TryGetPropertyInfo(ConfigurationItemFactory.Default, targetObject, propertyName, out var propertyInfo) || propertyInfo is null) throw new NLogConfigurationException($"'{targetObject.GetType()?.Name}' cannot assign unknown property '{propertyName}'='{propertyValue}'"); - } var propertyValueExpanded = ExpandSimpleVariables(propertyValue, out var matchingVariableName); if (matchingVariableName != null && TryLookupDynamicVariable(matchingVariableName, out var matchingLayout)) @@ -1118,7 +1134,7 @@ private void SetPropertyValueFromString(T targetObject, string propertyName, if (ex.MustBeRethrownImmediately()) throw; - var configException = new NLogConfigurationException($"'{targetObject?.GetType()?.Name}' cannot assign property '{propertyName}'='{propertyValue}' in section '{element.Name}'. Error: {ex.Message}", ex); + var configException = new NLogConfigurationException($"'{targetObject.GetType()?.Name}' cannot assign property '{propertyName}'='{propertyValue}' in section '{element.Name}'. Error: {ex.Message}", ex); if (MustThrowConfigException(configException)) throw; } @@ -1135,7 +1151,7 @@ private void SetPropertyValuesFromElement(T targetObject, ValidatedConfigurat return; } - if (!PropertyHelper.TryGetPropertyInfo(ConfigurationItemFactory.Default, targetObject, childElement.Name, out var propInfo)) + if (!PropertyHelper.TryGetPropertyInfo(ConfigurationItemFactory.Default, targetObject, childElement.Name, out var propInfo) || propInfo is null) { var configException = new NLogConfigurationException($"'{targetObject.GetType()?.Name}' cannot assign unknown property '{childElement.Name}' in section '{parentElement.Name}'"); if (MustThrowConfigException(configException)) @@ -1159,44 +1175,70 @@ private void SetPropertyValuesFromElement(T targetObject, ValidatedConfigurat return; } - object propertyValue = propInfo.GetValue(targetObject, null); - ConfigureFromAttributesAndElements(propertyValue, childElement); + if (TryGetPropertyValue(targetObject, propInfo, out var propertyValue) && propertyValue is not null) + { + ConfigureFromAttributesAndElements(propertyValue, childElement); + } + } + + private bool TryGetPropertyValue(T targetObject, PropertyInfo propInfo, out object? propertyValue) where T : class + { + try + { + propertyValue = propInfo.GetValue(targetObject, null); + return true; + } + catch (Exception ex) + { + if (ex.MustBeRethrownImmediately()) + throw; + + var configException = new NLogConfigurationException($"Failed getting property {propInfo.Name} for type: {typeof(T).Name}", ex); + if (MustThrowConfigException(configException)) + throw configException; + + propertyValue = null; + return false; + } } private bool AddArrayItemFromElement(object o, PropertyInfo propInfo, ValidatedConfigurationElement element) { - Type elementType = PropertyHelper.GetArrayItemType(propInfo); + var elementType = PropertyHelper.GetArrayItemType(propInfo); if (elementType != null) { - IList propertyValue = (IList)propInfo.GetValue(o, null); - - if (string.Equals(propInfo.Name, element.Name, StringComparison.OrdinalIgnoreCase)) + if (TryGetPropertyValue(o, propInfo, out var propertyValue) && propertyValue != null) { - bool foundChild = false; - foreach (var child in element.ValidChildren) + IList listValue = (IList)propertyValue; + + if (string.Equals(propInfo.Name, element.Name, StringComparison.OrdinalIgnoreCase)) { - foundChild = true; - propertyValue.Add(ParseArrayItemFromElement(elementType, child)); + bool foundChild = false; + foreach (var child in element.ValidChildren) + { + foundChild = true; + listValue.Add(ParseArrayItemFromElement(elementType, child)); + } + if (foundChild) + return true; } - if (foundChild) - return true; - } - object arrayItem = ParseArrayItemFromElement(elementType, element); - propertyValue.Add(arrayItem); - return true; + var arrayItem = ParseArrayItemFromElement(elementType, element); + listValue.Add(arrayItem); + return true; + } } return false; } - private object ParseArrayItemFromElement(Type elementType, ValidatedConfigurationElement element) + private object? ParseArrayItemFromElement(Type elementType, ValidatedConfigurationElement element) { - object arrayItem = TryCreateLayoutInstance(element, elementType); + object? arrayItem = TryCreateLayoutInstance(element, elementType); // arrayItem is not a layout if (arrayItem is null) { - if (!ConfigurationItemFactory.Default.TryCreateInstance(elementType, out arrayItem)) + if (!ConfigurationItemFactory.Default.TryCreateInstance(elementType, out arrayItem) || arrayItem is null) { throw new NLogConfigurationException($"Factory returned null for {elementType}"); } @@ -1221,7 +1263,7 @@ private bool SetLayoutFromElement(object o, PropertyInfo propInfo, ValidatedConf private bool SetFilterFromElement(object o, PropertyInfo propInfo, ValidatedConfigurationElement element) { - Filter filter = TryCreateFilterInstance(element, propInfo.PropertyType); + var filter = TryCreateFilterInstance(element, propInfo.PropertyType); // and is a Filter and 'type' attribute has been specified if (filter != null) { @@ -1237,15 +1279,15 @@ private SimpleLayout CreateSimpleLayout(string layoutText) return new SimpleLayout(layoutText, ConfigurationItemFactory.Default, LogFactory.ThrowConfigExceptions); } - private Layout TryCreateLayoutInstance(ValidatedConfigurationElement element, Type type) + private Layout? TryCreateLayoutInstance(ValidatedConfigurationElement element, Type type) { // Check if Layout type if (!typeof(Layout).IsAssignableFrom(type)) return null; // Check if the 'type' attribute has been specified - string classType = element.GetConfigItemTypeAttribute(); - if (classType is null) + var classType = element.GetConfigItemTypeAttribute(); + if (string.IsNullOrEmpty(classType)) return null; var expandedClassType = ExpandSimpleVariables(classType, out var matchingVariableName); @@ -1257,6 +1299,11 @@ private Layout TryCreateLayoutInstance(ValidatedConfigurationElement element, Ty } } + if (nameof(SimpleLayout).Equals(expandedClassType, StringComparison.OrdinalIgnoreCase) && TryCreateSimpleLayoutInstance(element, out var simpleLayout)) + { + return simpleLayout; + } + var layoutInstance = FactoryCreateInstance(expandedClassType, ConfigurationItemFactory.Default.LayoutFactory); if (layoutInstance != null) { @@ -1267,7 +1314,29 @@ private Layout TryCreateLayoutInstance(ValidatedConfigurationElement element, Ty return null; } - private Filter TryCreateFilterInstance(ValidatedConfigurationElement element, Type type) + private bool TryCreateSimpleLayoutInstance(ValidatedConfigurationElement element, out SimpleLayout? simpleLayout) + { + if (!element.ValidChildren.Any()) + { + var valueLookup = element.Values; + if (valueLookup.Count == 2) + { + var simpleLayoutValue = (nameof(SimpleLayout.Text).Equals(valueLookup.First().Key, StringComparison.OrdinalIgnoreCase) ? (valueLookup.First().Value ?? string.Empty) : null) ?? + (nameof(SimpleLayout.Text).Equals(valueLookup.Last().Key, StringComparison.OrdinalIgnoreCase) ? (valueLookup.Last().Value ?? string.Empty) : null); + if (simpleLayoutValue != null) + { + var simpleLayoutText = ExpandSimpleVariables(simpleLayoutValue); + simpleLayout = string.IsNullOrEmpty(simpleLayoutValue) ? SimpleLayout.Default : new SimpleLayout(simpleLayoutText, ConfigurationItemFactory.Default); + return true; + } + } + } + + simpleLayout = null; + return false; + } + + private Filter? TryCreateFilterInstance(ValidatedConfigurationElement element, Type type) { var filter = TryCreateInstance(element, type, ConfigurationItemFactory.Default.FilterFactory); if (filter != null) @@ -1279,7 +1348,7 @@ private Filter TryCreateFilterInstance(ValidatedConfigurationElement element, Ty return null; } - private T TryCreateInstance(ValidatedConfigurationElement element, Type type, IFactory factory) + private T? TryCreateInstance(ValidatedConfigurationElement element, Type type, IFactory factory) where T : class { // Check if correct type @@ -1287,20 +1356,20 @@ private T TryCreateInstance(ValidatedConfigurationElement element, Type type, return null; // Check if the 'type' attribute has been specified - string classType = element.GetConfigItemTypeAttribute(); - if (classType is null) + var classType = element.GetConfigItemTypeAttribute(); + if (string.IsNullOrEmpty(classType)) return null; return FactoryCreateInstance(classType, factory); } - private T FactoryCreateInstance(string typeName, IFactory factory) where T : class + private T? FactoryCreateInstance(string typeName, IFactory factory) where T : class { - T newInstance = null; + T? newInstance = null; try { - typeName = ExpandSimpleVariables(typeName); + typeName = ExpandSimpleVariables(typeName).Trim(); if (typeName.Contains(',')) { // Possible specification of assembly-name detected @@ -1381,23 +1450,21 @@ private static Target WrapWithAsyncTargetWrapper(Target target) private Target WrapWithDefaultWrapper(Target target, ValidatedConfigurationElement defaultWrapperElement) { - string wrapperTypeName = defaultWrapperElement.GetConfigItemTypeAttribute("targets"); - Target wrapperTargetInstance = CreateTargetType(wrapperTypeName); - WrapperTargetBase wtb = wrapperTargetInstance as WrapperTargetBase; - if (wtb is null) + var wrapperTypeName = defaultWrapperElement.GetConfigItemTypeAttribute("targets"); + var wrapperTargetInstance = CreateTargetType(wrapperTypeName) as WrapperTargetBase; + if (wrapperTargetInstance is null) { - throw new NLogConfigurationException("Target type specified on is not a wrapper."); + throw new NLogConfigurationException($"Target type '{wrapperTypeName}' cannot be used as default target wrapper."); } + var wtb = wrapperTargetInstance; ParseTargetElement(wrapperTargetInstance, defaultWrapperElement); while (wtb.WrappedTarget != null) { - wtb = wtb.WrappedTarget as WrapperTargetBase; - if (wtb is null) - { - throw new NLogConfigurationException( - "Child target type specified on is not a wrapper."); - } + if (wtb.WrappedTarget is WrapperTargetBase nestedWrapper) + wtb = nestedWrapper; + else + throw new NLogConfigurationException($"Target type '{wrapperTypeName}' with nested {wtb.WrappedTarget.GetType()} cannot be used as default target wrapper."); } #if !NET35 @@ -1490,11 +1557,9 @@ private static string GetName(Target target) /// private sealed class ValidatedConfigurationElement : ILoggingConfigurationElement { - private static readonly IDictionary EmptyDefaultDictionary = new SortHelpers.ReadOnlySingleBucketDictionary(); - private readonly ILoggingConfigurationElement _element; private readonly bool _throwConfigExceptions; - private IList _validChildren; + private IList? _validChildren; public static ValidatedConfigurationElement Create(ILoggingConfigurationElement element, LogFactory logFactory) { @@ -1509,13 +1574,14 @@ public ValidatedConfigurationElement(ILoggingConfigurationElement element, bool { _throwConfigExceptions = throwConfigExceptions; Name = element.Name.Trim(); - ValueLookup = CreateValueLookup(element, throwConfigExceptions); + _valueLookup = CreateValueLookup(element, throwConfigExceptions); _element = element; } public string Name { get; } - public IDictionary ValueLookup { get; } + public ICollection> Values => _valueLookup ?? (ICollection>)ArrayHelper.Empty>(); + private readonly IDictionary? _valueLookup; public IEnumerable ValidChildren { @@ -1530,7 +1596,7 @@ public IEnumerable ValidChildren IEnumerable YieldAndCacheValidChildren() { - IList validChildren = null; + IList? validChildren = null; foreach (var child in _element.Children) { validChildren = validChildren ?? new List(); @@ -1541,16 +1607,16 @@ IEnumerable YieldAndCacheValidChildren() _validChildren = validChildren ?? ArrayHelper.Empty(); } - public IEnumerable> Values => ValueLookup; - /// /// Explicit cast because NET35 doesn't support covariance. /// IEnumerable ILoggingConfigurationElement.Children => ValidChildren.Cast(); + IEnumerable> ILoggingConfigurationElement.Values => Values; + public string GetRequiredValue(string attributeName, string section) { - string value = GetOptionalValue(attributeName, null); + var value = GetOptionalValue(attributeName, null); if (value is null) { throw new NLogConfigurationException($"Expected {attributeName} on {Name} in {section}"); @@ -1565,20 +1631,23 @@ public string GetRequiredValue(string attributeName, string section) return value; } - public string GetOptionalValue(string attributeName, string defaultValue) + public string? GetOptionalValue(string attributeName, string? defaultValue) { - ValueLookup.TryGetValue(attributeName, out string value); + if (_valueLookup is null) + return defaultValue; + + _valueLookup.TryGetValue(attributeName, out var value); return value ?? defaultValue; } - private static IDictionary CreateValueLookup(ILoggingConfigurationElement element, bool throwConfigExceptions) + private static IDictionary? CreateValueLookup(ILoggingConfigurationElement element, bool throwConfigExceptions) { - IDictionary valueLookup = null; - List warnings = null; + IDictionary? valueLookup = null; + List? warnings = null; foreach (var attribute in element.Values) { var attributeKey = attribute.Key?.Trim() ?? string.Empty; - valueLookup = valueLookup ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + valueLookup = valueLookup ?? new Dictionary(StringComparer.OrdinalIgnoreCase); if (!string.IsNullOrEmpty(attributeKey) && !valueLookup.ContainsKey(attributeKey)) { valueLookup[attributeKey] = attribute.Value; @@ -1595,11 +1664,13 @@ private static IDictionary CreateValueLookup(ILoggingConfigurati } } } + if (throwConfigExceptions && warnings?.Count > 0) { throw new NLogConfigurationException(StringHelpers.Join(Environment.NewLine, warnings)); } - return valueLookup ?? EmptyDefaultDictionary; + + return valueLookup; } public override string ToString() diff --git a/src/NLog/Config/LoggingConfigurationReloadedEventArgs.cs b/src/NLog/Config/LoggingConfigurationReloadedEventArgs.cs deleted file mode 100644 index bb0373f542..0000000000 --- a/src/NLog/Config/LoggingConfigurationReloadedEventArgs.cs +++ /dev/null @@ -1,82 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -#if !NETSTANDARD1_3 - -namespace NLog.Config -{ - using System; - using System.ComponentModel; - - /// - /// Obsolete and replaced by and with NLog v5.2. - /// - [Obsolete("Replaced by ConfigurationChanged, but check args.ActivatedConfiguration != null. Marked obsolete on NLog 5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class LoggingConfigurationReloadedEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// Whether configuration reload has succeeded. - public LoggingConfigurationReloadedEventArgs(bool succeeded) - { - Succeeded = succeeded; - } - - /// - /// Initializes a new instance of the class. - /// - /// Whether configuration reload has succeeded. - /// The exception during configuration reload. - public LoggingConfigurationReloadedEventArgs(bool succeeded, Exception exception) - { - Succeeded = succeeded; - Exception = exception; - } - - /// - /// Gets a value indicating whether configuration reload has succeeded. - /// - /// A value of true if succeeded; otherwise, false. - public bool Succeeded { get; } - - /// - /// Gets the exception which occurred during configuration reload. - /// - /// The exception. - public Exception Exception { get; } - } -} - -#endif diff --git a/src/NLog/Config/LoggingConfigurationWatchableFileLoader.cs b/src/NLog/Config/LoggingConfigurationWatchableFileLoader.cs deleted file mode 100644 index 42a01e6a4d..0000000000 --- a/src/NLog/Config/LoggingConfigurationWatchableFileLoader.cs +++ /dev/null @@ -1,308 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -#if !NETSTANDARD1_3 - -namespace NLog.Config -{ - using System; - using System.Linq; - using System.Threading; - using NLog.Common; - using NLog.Internal; - using NLog.Internal.Fakeables; - - /// - /// Enables FileWatcher for the currently loaded NLog Configuration File, - /// and supports automatic reload on file modification. - /// - internal sealed class LoggingConfigurationWatchableFileLoader : LoggingConfigurationFileLoader - { - private const int ReconfigAfterFileChangedTimeout = 1000; - private readonly object _lockObject = new object(); - private Timer _reloadTimer; - private MultiFileWatcher _watcher; - private bool _isDisposing; - private LogFactory _logFactory; - - public LoggingConfigurationWatchableFileLoader(IAppEnvironment appEnvironment) - : base(appEnvironment) - { - } - - public override LoggingConfiguration Load(LogFactory logFactory, string filename = null) - { -#if NETFRAMEWORK - if (string.IsNullOrEmpty(filename)) - { - var config = TryLoadFromAppConfig(); - if (config != null) - return config; - } -#endif - - return base.Load(logFactory, filename); - } - - public override void Activated(LogFactory logFactory, LoggingConfiguration config) - { - _logFactory = logFactory; - - TryUnwatchConfigFile(); - - if (config != null) - { - TryWachtingConfigFile(config); - } - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _isDisposing = true; - - if (_watcher != null) - { - // Disable startup of new reload-timers - _watcher.FileChanged -= ConfigFileChanged; - _watcher.StopWatching(); - } - - var currentTimer = _reloadTimer; - if (currentTimer != null) - { - _reloadTimer = null; - currentTimer.WaitForDispose(TimeSpan.Zero); - } - - // Dispose file-watcher after having dispose timer to avoid race - _watcher?.Dispose(); - } - - base.Dispose(disposing); - } - -#if NETFRAMEWORK - private LoggingConfiguration TryLoadFromAppConfig() - { - try - { - // Try to load default configuration. - return XmlLoggingConfiguration.AppConfig; - } - catch (Exception ex) - { - //loading could fail due to an invalid XML file (app.config) etc. - if (ex.MustBeRethrown()) - { - throw; - } - return null; - } - } -#endif - - internal void ReloadConfigOnTimer(object state) - { - if (_reloadTimer is null && _isDisposing) - { - return; //timer was disposed already. - } - - LoggingConfiguration oldConfig = (LoggingConfiguration)state; - - InternalLogger.Info("Reloading configuration..."); - lock (_lockObject) - { - if (_isDisposing) - { - return; //timer was disposed already. - } - - var currentTimer = _reloadTimer; - if (currentTimer != null) - { - _reloadTimer = null; - currentTimer.WaitForDispose(TimeSpan.Zero); - } - } - - lock (_logFactory._syncRoot) - { - LoggingConfiguration newConfig; - try - { - if (!ReferenceEquals(_logFactory._config, oldConfig)) - { - InternalLogger.Warn("NLog Config changed in between. Not reloading."); - return; - } - - newConfig = oldConfig.Reload(); - if (ReferenceEquals(newConfig, oldConfig)) - return; - - if (newConfig is IInitializeSucceeded config2 && config2.InitializeSucceeded != true) - { - InternalLogger.Warn("NLog Config Reload() failed. Invalid XML?"); - return; - } - } - catch (Exception exception) - { -#if DEBUG - if (exception.MustBeRethrownImmediately()) - { - throw; // Throwing exceptions here will crash the entire application (.NET 2.0 behavior) - } -#endif - InternalLogger.Warn(exception, "NLog configuration failed to reload"); -#pragma warning disable CS0618 // Type or member is obsolete - _logFactory?.NotifyConfigurationReloaded(new LoggingConfigurationReloadedEventArgs(false, exception)); -#pragma warning restore CS0618 // Type or member is obsolete - return; - } - - try - { - TryUnwatchConfigFile(); - - _logFactory.Configuration = newConfig; // Triggers LogFactory to call Activated(...) that adds file-watch again - -#pragma warning disable CS0618 // Type or member is obsolete - _logFactory?.NotifyConfigurationReloaded(new LoggingConfigurationReloadedEventArgs(true)); -#pragma warning restore CS0618 // Type or member is obsolete - } - catch (Exception exception) - { -#if DEBUG - if (exception.MustBeRethrownImmediately()) - { - throw; // Throwing exceptions here will crash the entire application (.NET 2.0 behavior) - } -#endif - InternalLogger.Warn(exception, "NLog configuration reloaded, failed to be assigned"); - _watcher.Watch(oldConfig.FileNamesToWatch); -#pragma warning disable CS0618 // Type or member is obsolete - _logFactory?.NotifyConfigurationReloaded(new LoggingConfigurationReloadedEventArgs(false, exception)); -#pragma warning restore CS0618 // Type or member is obsolete - } - } - } - - private void ConfigFileChanged(object sender, EventArgs args) - { - InternalLogger.Info("Configuration file change detected! Reloading in {0}ms...", ReconfigAfterFileChangedTimeout); - - // In the rare cases we may get multiple notifications here, - // but we need to reload config only once. - // - // The trick is to schedule the reload in one second after - // the last change notification comes in. - lock (_lockObject) - { - if (_isDisposing) - { - return; - } - - if (_reloadTimer is null) - { - var configuration = _logFactory._config; - if (configuration != null) - { - _reloadTimer = new Timer( - ReloadConfigOnTimer, - configuration, - ReconfigAfterFileChangedTimeout, - Timeout.Infinite); - } - } - else - { - _reloadTimer.Change( - ReconfigAfterFileChangedTimeout, - Timeout.Infinite); - } - } - } - - private void TryWachtingConfigFile(LoggingConfiguration config) - { - try - { - var fileNamesToWatch = config.FileNamesToWatch?.ToList(); - if (fileNamesToWatch?.Count > 0) - { - if (_watcher is null) - { - _watcher = new MultiFileWatcher(); - _watcher.FileChanged += ConfigFileChanged; - } - - _watcher.Watch(fileNamesToWatch); - } - } - catch (Exception exception) - { - if (exception.MustBeRethrown()) - { - throw; - } - - //ToArray needed for .Net 3.5 - InternalLogger.Warn(exception, "Cannot start file watching: {0}", string.Join(",", _logFactory._config.FileNamesToWatch.ToArray())); - } - } - - private void TryUnwatchConfigFile() - { - try - { - _watcher?.StopWatching(); - } - catch (Exception exception) - { - InternalLogger.Error(exception, "Cannot stop file watching."); - - if (exception.MustBeRethrown()) - { - throw; - } - } - } - } -} - -#endif diff --git a/src/NLog/Config/LoggingRule.cs b/src/NLog/Config/LoggingRule.cs index 9af48387b6..aaf38672bd 100644 --- a/src/NLog/Config/LoggingRule.cs +++ b/src/NLog/Config/LoggingRule.cs @@ -38,9 +38,9 @@ namespace NLog.Config using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; - using System.Linq; using System.Text; using NLog.Filters; + using NLog.Internal; using NLog.Targets; /// @@ -50,21 +50,20 @@ namespace NLog.Config public class LoggingRule { private ILoggingRuleLevelFilter _logLevelFilter = LoggingRuleLevelFilter.Off; - private LoggerNameMatcher _loggerNameMatcher = LoggerNameMatcher.Create(null); + private LoggerNameMatcher _loggerNameMatcher = LoggerNameMatcher.Off; private readonly List _targets = new List(); /// /// Create an empty . /// public LoggingRule() - : this(null) { } /// /// Create an empty . /// - public LoggingRule(string ruleName) + public LoggingRule(string? ruleName) { RuleName = ruleName; } @@ -77,8 +76,8 @@ public LoggingRule(string ruleName) /// Maximum log level needed to trigger this rule. /// Target to be written to when the rule matches. public LoggingRule(string loggerNamePattern, LogLevel minLevel, LogLevel maxLevel, Target target) - : this() { + Guard.ThrowIfNull(target); LoggerNamePattern = loggerNamePattern; _targets.Add(target); EnableLoggingForLevels(minLevel, maxLevel); @@ -91,8 +90,8 @@ public LoggingRule(string loggerNamePattern, LogLevel minLevel, LogLevel maxLeve /// Minimum log level needed to trigger this rule. /// Target to be written to when the rule matches. public LoggingRule(string loggerNamePattern, LogLevel minLevel, Target target) - : this() { + Guard.ThrowIfNull(target); LoggerNamePattern = loggerNamePattern; _targets.Add(target); EnableLoggingForLevels(minLevel, LogLevel.MaxLevel); @@ -104,8 +103,8 @@ public LoggingRule(string loggerNamePattern, LogLevel minLevel, Target target) /// Logger name pattern used for . It may include one or more '*' or '?' wildcards at any position. /// Target to be written to when the rule matches. public LoggingRule(string loggerNamePattern, Target target) - : this() { + Guard.ThrowIfNull(target); LoggerNamePattern = loggerNamePattern; _targets.Add(target); } @@ -113,7 +112,7 @@ public LoggingRule(string loggerNamePattern, Target target) /// /// Rule identifier to allow rule lookup /// - public string RuleName { get; set; } + public string? RuleName { get; set; } /// /// Gets a collection of targets that should be written to when this rule matches. @@ -127,10 +126,12 @@ public LoggingRule(string loggerNamePattern, Target target) /// [Obsolete("Very exotic feature without any unit-tests, not sure if it works. Marked obsolete with NLog v5.3")] [EditorBrowsable(EditorBrowsableState.Never)] - public IList ChildRules { get; } = new List(); + public IList ChildRules => _childRules; [Obsolete("Very exotic feature without any unit-tests, not sure if it works. Marked obsolete with NLog v5.3")] - internal List GetChildRulesThreadSafe() { lock (ChildRules) return ChildRules.ToList(); } - internal Target[] GetTargetsThreadSafe() { lock (_targets) return _targets.Count == 0 ? NLog.Internal.ArrayHelper.Empty() : _targets.ToArray(); } + private readonly List _childRules = new List(); + [Obsolete("Very exotic feature without any unit-tests, not sure if it works. Marked obsolete with NLog v5.3")] + internal LoggingRule[] GetChildRulesThreadSafe() { lock (_childRules) return _childRules.ToArray(); } + internal Target[] GetTargetsThreadSafe() { lock (_targets) return _targets.Count == 0 ? ArrayHelper.Empty() : _targets.ToArray(); } internal bool RemoveTargetThreadSafe(Target target) { lock (_targets) return _targets.Remove(target); } /// @@ -149,7 +150,7 @@ public LoggingRule(string loggerNamePattern, Target target) /// /// Loggers matching will be restricted to specified minimum level for following rules. /// - public LogLevel FinalMinLevel + public LogLevel? FinalMinLevel { get => _logLevelFilter.FinalMinLevel; set => _logLevelFilter = _logLevelFilter.GetSimpleFilterForUpdate().SetFinalMinLevel(value); @@ -241,12 +242,12 @@ public void EnableLoggingForLevels(LogLevel minLevel, LogLevel maxLevel) _logLevelFilter = _logLevelFilter.GetSimpleFilterForUpdate().SetLoggingLevels(minLevel, maxLevel, true); } - internal void EnableLoggingForLevelLayout(NLog.Layouts.SimpleLayout simpleLayout, NLog.Layouts.SimpleLayout finalMinLevel) + internal void EnableLoggingForLevelLayout(NLog.Layouts.SimpleLayout? simpleLayout, NLog.Layouts.SimpleLayout? finalMinLevel) { _logLevelFilter = new DynamicLogLevelFilter(this, simpleLayout, finalMinLevel); } - internal void EnableLoggingForLevelsLayout(Layouts.SimpleLayout minLevel, Layouts.SimpleLayout maxLevel, NLog.Layouts.SimpleLayout finalMinLevel) + internal void EnableLoggingForLevelsLayout(Layouts.SimpleLayout? minLevel, Layouts.SimpleLayout? maxLevel, NLog.Layouts.SimpleLayout? finalMinLevel) { _logLevelFilter = new DynamicRangeLevelFilter(this, minLevel, maxLevel, finalMinLevel); } @@ -272,6 +273,8 @@ public void DisableLoggingForLevel(LogLevel level) /// Maximum log level to be disabled. public void DisableLoggingForLevels(LogLevel minLevel, LogLevel maxLevel) { + Guard.ThrowIfNull(minLevel); + Guard.ThrowIfNull(maxLevel); _logLevelFilter = _logLevelFilter.GetSimpleFilterForUpdate().SetLoggingLevels(minLevel, maxLevel, false); } @@ -282,6 +285,8 @@ public void DisableLoggingForLevels(LogLevel minLevel, LogLevel maxLevel) /// Maximum log level needed to trigger this rule. public void SetLoggingLevels(LogLevel minLevel, LogLevel maxLevel) { + Guard.ThrowIfNull(minLevel); + Guard.ThrowIfNull(maxLevel); _logLevelFilter = _logLevelFilter.GetSimpleFilterForUpdate().SetLoggingLevels(LogLevel.MinLevel, LogLevel.MaxLevel, false).SetLoggingLevels(minLevel, maxLevel, true); } diff --git a/src/NLog/Config/LoggingRuleLevelFilter.cs b/src/NLog/Config/LoggingRuleLevelFilter.cs index b17c7b6346..a5b83ca853 100644 --- a/src/NLog/Config/LoggingRuleLevelFilter.cs +++ b/src/NLog/Config/LoggingRuleLevelFilter.cs @@ -42,9 +42,9 @@ internal sealed class LoggingRuleLevelFilter : ILoggingRuleLevelFilter { public static readonly ILoggingRuleLevelFilter Off = new LoggingRuleLevelFilter(); public bool[] LogLevels { get; } - public LogLevel FinalMinLevel { get; private set; } + public LogLevel? FinalMinLevel { get; private set; } - public LoggingRuleLevelFilter(bool[] logLevels = null, LogLevel finalMinLevel = null) + public LoggingRuleLevelFilter(bool[]? logLevels = null, LogLevel? finalMinLevel = null) { LogLevels = new bool[LogLevel.MaxLevel.Ordinal + 1]; if (logLevels != null) @@ -70,7 +70,7 @@ public LoggingRuleLevelFilter SetLoggingLevels(LogLevel minLevel, LogLevel maxLe return this; } - public LoggingRuleLevelFilter SetFinalMinLevel(LogLevel finalMinLevel) + public LoggingRuleLevelFilter SetFinalMinLevel(LogLevel? finalMinLevel) { FinalMinLevel = finalMinLevel; return this; diff --git a/src/NLog/Config/MethodFactory.cs b/src/NLog/Config/MethodFactory.cs index 92bfb66a2a..60520da6af 100644 --- a/src/NLog/Config/MethodFactory.cs +++ b/src/NLog/Config/MethodFactory.cs @@ -37,41 +37,39 @@ namespace NLog.Config using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; - using NLog.Common; using NLog.Conditions; using NLog.Internal; /// /// Factory for locating methods. /// - internal sealed class MethodFactory : -#pragma warning disable CS0618 // Type or member is obsolete - INamedItemFactory, -#pragma warning restore CS0618 // Type or member is obsolete - IFactory + internal sealed class MethodFactory : IFactory { - private readonly MethodFactory _globalDefaultFactory; private readonly Dictionary _nameToMethodDetails = new Dictionary(StringComparer.OrdinalIgnoreCase); + private +#if !NETFRAMEWORK + readonly +#endif struct MethodDetails { public readonly MethodInfo MethodInfo; - public readonly Func NoParameters; - public readonly Func OneParameter; - public readonly Func TwoParameters; - public readonly Func ThreeParameters; - public readonly Func ManyParameters; + public readonly Func NoParameters; + public readonly Func OneParameter; + public readonly Func TwoParameters; + public readonly Func ThreeParameters; + public readonly Func ManyParameters; public readonly int ManyParameterMinCount; public readonly int ManyParameterMaxCount; public readonly bool ManyParameterWithLogEvent; public MethodDetails( MethodInfo methodInfo, - Func noParameters, - Func oneParameter, - Func twoParameters, - Func threeParameters, - Func manyParameters, + Func noParameters, + Func oneParameter, + Func twoParameters, + Func threeParameters, + Func manyParameters, int manyParameterMinCount, int manyParameterMaxCount, bool manyParameterWithLogEvent) @@ -88,72 +86,46 @@ public MethodDetails( } } - /// - /// Initializes a new instance of the class. - /// - public MethodFactory(MethodFactory globalDefaultFactory) - { - _globalDefaultFactory = globalDefaultFactory; - } + public bool Initialized { get; private set; } - /// - /// Scans the assembly for classes marked with expected class - /// and methods marked with expected and adds them - /// to the factory. - /// - /// The types to scan. - /// The assembly name for the type. - /// The item name prefix. - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - [UnconditionalSuppressMessage("Trimming - Ignore since obsolete", "IL2072")] - [UnconditionalSuppressMessage("Trimming - Ignore since obsolete", "IL2062")] - public void ScanTypes(Type[] types, string assemblyName, string itemNamePrefix) + public void Initialize(Action itemRegistration) { - foreach (Type t in types) + lock (ConfigurationItemFactory.SyncRoot) { + if (Initialized) + return; + try { - if (t.IsClass()) - { - RegisterType(t, itemNamePrefix); - } + var skipCheckExists = _nameToMethodDetails.Count == 0; + itemRegistration.Invoke(skipCheckExists); } - catch (Exception exception) + finally { - InternalLogger.Error(exception, "Failed to add type '{0}'.", t.FullName); - - if (exception.MustBeRethrown()) - { - throw; - } + Initialized = true; } } } - /// - /// Registers the type. - /// - /// The type to register. - /// The item name prefix. - void IFactory.RegisterType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string itemNamePrefix) - { - RegisterType(type, itemNamePrefix); - } + public bool CheckTypeAliasExists(string typeAlias) => _nameToMethodDetails.ContainsKey(typeAlias); /// /// Registers the type. /// /// The type to register. /// The item name prefix. - private void RegisterType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string itemNamePrefix) + void IFactory.RegisterType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string itemNamePrefix) { - var extractedMethods = ExtractClassMethods(type); - if (extractedMethods?.Count > 0) + if (type.IsClass) { - for (int i = 0; i < extractedMethods.Count; ++i) + var extractedMethods = ExtractClassMethods(type); + if (extractedMethods?.Count > 0) { - string methodName = string.IsNullOrEmpty(itemNamePrefix) ? extractedMethods[i].Key : itemNamePrefix + extractedMethods[i].Key; - RegisterDefinition(methodName, extractedMethods[i].Value); + for (int i = 0; i < extractedMethods.Count; ++i) + { + string methodName = string.IsNullOrEmpty(itemNamePrefix) ? extractedMethods[i].Key : itemNamePrefix + extractedMethods[i].Key; + RegisterDefinition(methodName, extractedMethods[i].Value); + } } } } @@ -196,17 +168,6 @@ public void Clear() } } - /// - /// Registers the definition of a single method. - /// - /// The method name. - /// The method info. - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - void INamedItemFactory.RegisterDefinition(string itemName, MethodInfo itemDefinition) - { - RegisterDefinition(itemName, itemDefinition); - } - internal void RegisterDefinition(string methodName, MethodInfo methodInfo) { object[] defaultMethodParameters = ResolveDefaultMethodParameters(methodInfo, out var manyParameterMinCount, out var manyParameterMaxCount, out var includeLogEvent); @@ -244,7 +205,7 @@ internal void RegisterDefinition(string methodName, MethodInfo methodInfo) } } - private static object InvokeMethodInfo(MethodInfo methodInfo, object[] methodArgs) + private static object InvokeMethodInfo(MethodInfo methodInfo, object?[] methodArgs) { try { @@ -283,12 +244,12 @@ private static object[] ResolveDefaultMethodParameters(MethodInfo methodInfo, ou return defaultMethodParameters; } - private static object[] ResolveMethodParameters(object[] defaultMethodParameters, object[] inputParameters) + private static object?[] ResolveMethodParameters(object?[] defaultMethodParameters, object?[] inputParameters) { if (defaultMethodParameters.Length == inputParameters.Length) return inputParameters; - object[] methodParameters = new object[defaultMethodParameters.Length]; + object?[] methodParameters = new object[defaultMethodParameters.Length]; for (int i = 0; i < inputParameters.Length; ++i) methodParameters[i] = inputParameters[i]; for (int i = inputParameters.Length; i < defaultMethodParameters.Length; ++i) @@ -296,18 +257,18 @@ private static object[] ResolveMethodParameters(object[] defaultMethodParameters return methodParameters; } - private static object[] ResolveMethodParameters(object[] defaultMethodParameters, object inputParameterArg1) + private static object?[] ResolveMethodParameters(object?[] defaultMethodParameters, object? inputParameterArg1) { - object[] methodParameters = new object[defaultMethodParameters.Length]; + object?[] methodParameters = new object[defaultMethodParameters.Length]; methodParameters[0] = inputParameterArg1; for (int i = 1; i < defaultMethodParameters.Length; ++i) methodParameters[i] = defaultMethodParameters[i]; return methodParameters; } - private static object[] ResolveMethodParameters(object[] defaultMethodParameters, object inputParameterArg1, object inputParameterArg2) + private static object?[] ResolveMethodParameters(object?[] defaultMethodParameters, object? inputParameterArg1, object? inputParameterArg2) { - object[] methodParameters = new object[defaultMethodParameters.Length]; + object?[] methodParameters = new object[defaultMethodParameters.Length]; methodParameters[0] = inputParameterArg1; methodParameters[1] = inputParameterArg2; for (int i = 2; i < defaultMethodParameters.Length; ++i) @@ -315,9 +276,9 @@ private static object[] ResolveMethodParameters(object[] defaultMethodParameters return methodParameters; } - private static object[] ResolveMethodParameters(object[] defaultMethodParameters, object inputParameterArg1, object inputParameterArg2, object inputParameterArg3) + private static object?[] ResolveMethodParameters(object?[] defaultMethodParameters, object? inputParameterArg1, object? inputParameterArg2, object? inputParameterArg3) { - object[] methodParameters = new object[defaultMethodParameters.Length]; + object?[] methodParameters = new object[defaultMethodParameters.Length]; methodParameters[0] = inputParameterArg1; methodParameters[1] = inputParameterArg2; methodParameters[2] = inputParameterArg3; @@ -326,9 +287,9 @@ private static object[] ResolveMethodParameters(object[] defaultMethodParameters return methodParameters; } - private static object[] ResolveMethodParameters(object[] defaultMethodParameters, object inputParameterArg1, object inputParameterArg2, object inputParameterArg3, object inputParameterArg4) + private static object?[] ResolveMethodParameters(object?[] defaultMethodParameters, object? inputParameterArg1, object? inputParameterArg2, object? inputParameterArg3, object? inputParameterArg4) { - object[] methodParameters = new object[defaultMethodParameters.Length]; + object?[] methodParameters = new object[defaultMethodParameters.Length]; methodParameters[0] = inputParameterArg1; methodParameters[1] = inputParameterArg2; methodParameters[2] = inputParameterArg3; @@ -338,107 +299,57 @@ private static object[] ResolveMethodParameters(object[] defaultMethodParameters return methodParameters; } - /// - /// Tries to retrieve method by name. - /// - /// The method name. - /// The result. - /// A value of true if the method was found, false otherwise. - [Obsolete("Use TryCreateInstance instead. Marked obsolete with NLog v5.2")] - bool INamedItemFactory.TryCreateInstance(string itemName, out MethodInfo result) - { - return TryGetDefinition(itemName, out result); - } - - /// - /// Retrieves method by name. - /// - /// Method name. - /// MethodInfo object. - [Obsolete("Use TryCreateInstance instead. Marked obsolete with NLog v5.2")] - MethodInfo INamedItemFactory.CreateInstance(string itemName) - { - if (TryGetDefinition(itemName, out MethodInfo result)) - { - return result; - } - - throw new NLogConfigurationException($"Unknown function: '{itemName}'"); - } - - /// - /// Tries to get method definition. - /// - /// The method name. - /// The result. - /// A value of true if the method was found, false otherwise. - [Obsolete("Use TryCreateInstance instead. Marked obsolete with NLog v5.2")] - public bool TryGetDefinition(string itemName, out MethodInfo result) - { - lock (_nameToMethodDetails) - { - if (_nameToMethodDetails.TryGetValue(itemName, out var methodDetails)) - { - result = methodDetails.MethodInfo; - return result != null; - } - } - - result = null; - return _globalDefaultFactory?.TryGetDefinition(itemName, out result) ?? false; - } - - public void RegisterNoParameters(string methodName, Func noParameters, MethodInfo legacyMethodInfo = null) + public void RegisterNoParameters(string methodName, Func noParameters, MethodInfo? legacyMethodInfo = null) { lock (_nameToMethodDetails) { _nameToMethodDetails.TryGetValue(methodName, out var methodDetails); - legacyMethodInfo = legacyMethodInfo ?? methodDetails.MethodInfo ?? noParameters.GetDelegateInfo(); + legacyMethodInfo = legacyMethodInfo ?? methodDetails.MethodInfo ?? noParameters.Method; _nameToMethodDetails[methodName] = new MethodDetails(legacyMethodInfo, noParameters, methodDetails.OneParameter, methodDetails.TwoParameters, methodDetails.ThreeParameters, methodDetails.ManyParameters, methodDetails.ManyParameterMinCount, methodDetails.ManyParameterMaxCount, methodDetails.ManyParameterWithLogEvent); } } - public void RegisterOneParameter(string methodName, Func oneParameter, MethodInfo legacyMethodInfo = null) + public void RegisterOneParameter(string methodName, Func oneParameter, MethodInfo? legacyMethodInfo = null) { lock (_nameToMethodDetails) { _nameToMethodDetails.TryGetValue(methodName, out var methodDetails); - legacyMethodInfo = legacyMethodInfo ?? methodDetails.MethodInfo ?? oneParameter.GetDelegateInfo(); + legacyMethodInfo = legacyMethodInfo ?? methodDetails.MethodInfo ?? oneParameter.Method; _nameToMethodDetails[methodName] = new MethodDetails(legacyMethodInfo, methodDetails.NoParameters, oneParameter, methodDetails.TwoParameters, methodDetails.ThreeParameters, methodDetails.ManyParameters, methodDetails.ManyParameterMinCount, methodDetails.ManyParameterMaxCount, methodDetails.ManyParameterWithLogEvent); } } - public void RegisterTwoParameters(string methodName, Func twoParameters, MethodInfo legacyMethodInfo = null) + public void RegisterTwoParameters(string methodName, Func twoParameters, MethodInfo? legacyMethodInfo = null) { lock (_nameToMethodDetails) { _nameToMethodDetails.TryGetValue(methodName, out var methodDetails); - legacyMethodInfo = legacyMethodInfo ?? methodDetails.MethodInfo ?? twoParameters.GetDelegateInfo(); + legacyMethodInfo = legacyMethodInfo ?? methodDetails.MethodInfo ?? twoParameters.Method; _nameToMethodDetails[methodName] = new MethodDetails(legacyMethodInfo, methodDetails.NoParameters, methodDetails.OneParameter, twoParameters, methodDetails.ThreeParameters, methodDetails.ManyParameters, methodDetails.ManyParameterMinCount, methodDetails.ManyParameterMaxCount, methodDetails.ManyParameterWithLogEvent); } } - public void RegisterThreeParameters(string methodName, Func threeParameters, MethodInfo legacyMethodInfo = null) + public void RegisterThreeParameters(string methodName, Func threeParameters, MethodInfo? legacyMethodInfo = null) { lock (_nameToMethodDetails) { _nameToMethodDetails.TryGetValue(methodName, out var methodDetails); - legacyMethodInfo = legacyMethodInfo ?? methodDetails.MethodInfo ?? threeParameters.GetDelegateInfo(); + legacyMethodInfo = legacyMethodInfo ?? methodDetails.MethodInfo ?? threeParameters.Method; _nameToMethodDetails[methodName] = new MethodDetails(legacyMethodInfo, methodDetails.NoParameters, methodDetails.OneParameter, methodDetails.TwoParameters, threeParameters, methodDetails.ManyParameters, methodDetails.ManyParameterMinCount, methodDetails.ManyParameterMaxCount, methodDetails.ManyParameterWithLogEvent); } } - public void RegisterManyParameters(string methodName, Func manyParameters, int manyParameterMinCount, int manyParameterMaxCount, bool manyParameterWithLogEvent, MethodInfo legacyMethodInfo = null) + public void RegisterManyParameters(string methodName, Func manyParameters, int manyParameterMinCount, int manyParameterMaxCount, bool manyParameterWithLogEvent, MethodInfo? legacyMethodInfo = null) { lock (_nameToMethodDetails) { _nameToMethodDetails.TryGetValue(methodName, out var methodDetails); - legacyMethodInfo = legacyMethodInfo ?? methodDetails.MethodInfo ?? manyParameters.GetDelegateInfo(); + legacyMethodInfo = legacyMethodInfo ?? methodDetails.MethodInfo ?? manyParameters.Method; _nameToMethodDetails[methodName] = new MethodDetails(legacyMethodInfo, methodDetails.NoParameters, methodDetails.OneParameter, methodDetails.TwoParameters, methodDetails.ThreeParameters, manyParameters, manyParameterMinCount, manyParameterMaxCount, manyParameterWithLogEvent); } } - public Func TryCreateInstanceWithNoParameters(string methodName) + public Func? TryCreateInstanceWithNoParameters(string methodName) { lock (_nameToMethodDetails) { @@ -449,7 +360,7 @@ public Func TryCreateInstanceWithNoParameters(string metho } } - public Func TryCreateInstanceWithOneParameter(string methodName) + public Func? TryCreateInstanceWithOneParameter(string methodName) { lock (_nameToMethodDetails) { @@ -460,7 +371,7 @@ public Func TryCreateInstanceWithOneParameter(stri } } - public Func TryCreateInstanceWithTwoParameters(string methodName) + public Func? TryCreateInstanceWithTwoParameters(string methodName) { lock (_nameToMethodDetails) { @@ -471,7 +382,7 @@ public Func TryCreateInstanceWithTwoParame } } - public Func TryCreateInstanceWithThreeParameters(string methodName) + public Func? TryCreateInstanceWithThreeParameters(string methodName) { lock (_nameToMethodDetails) { @@ -482,7 +393,7 @@ public Func TryCreateInstanceWithT } } - public Func TryCreateInstanceWithManyParameters(string methodName, out int manyParameterMinCount, out int manyParameterMaxCount, out bool manyParameterWithLogEvent) + public Func? TryCreateInstanceWithManyParameters(string methodName, out int manyParameterMinCount, out int manyParameterMaxCount, out bool manyParameterWithLogEvent) { lock (_nameToMethodDetails) { @@ -500,28 +411,28 @@ public Func TryCreateInstanceWithManyParameters(string methodN manyParameterMaxCount = 3; manyParameterMinCount = methodDetails.TwoParameters is null ? 3 : 2; manyParameterWithLogEvent = true; - return new Func(args => methodDetails.ThreeParameters((LogEventInfo)args[0], args[1], args[2], args[3])); + return new Func(args => methodDetails.ThreeParameters((LogEventInfo)(args[0] ?? throw new ArgumentNullException(nameof(LogEventInfo))), args[1], args[2], args[3])); } else if (methodDetails.TwoParameters != null) { manyParameterMaxCount = 2; manyParameterMinCount = methodDetails.OneParameter is null ? 2 : 1; manyParameterWithLogEvent = true; - return new Func(args => methodDetails.TwoParameters((LogEventInfo)args[0], args[1], args[2])); + return new Func(args => methodDetails.TwoParameters((LogEventInfo)(args[0] ?? throw new ArgumentNullException(nameof(LogEventInfo))), args[1], args[2])); } else if (methodDetails.OneParameter != null) { manyParameterMaxCount = 1; manyParameterMinCount = methodDetails.NoParameters is null ? 1 : 0; manyParameterWithLogEvent = true; - return new Func(args => methodDetails.OneParameter((LogEventInfo)args[0], args[1])); + return new Func(args => methodDetails.OneParameter((LogEventInfo)(args[0] ?? throw new ArgumentNullException(nameof(LogEventInfo))), args[1])); } else if (methodDetails.NoParameters != null) { manyParameterMaxCount = 0; manyParameterMinCount = 0; manyParameterWithLogEvent = true; - return new Func(args => methodDetails.NoParameters((LogEventInfo)args[0])); + return new Func(args => methodDetails.NoParameters((LogEventInfo)(args[0] ?? throw new ArgumentNullException(nameof(LogEventInfo))))); } } manyParameterMinCount = 0; diff --git a/src/NLog/Config/NLogConfigurationItemAttribute.cs b/src/NLog/Config/NLogConfigurationItemAttribute.cs index b7b8efb5a4..e4c1db0dca 100644 --- a/src/NLog/Config/NLogConfigurationItemAttribute.cs +++ b/src/NLog/Config/NLogConfigurationItemAttribute.cs @@ -31,11 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using JetBrains.Annotations; - namespace NLog.Config { using System; + using JetBrains.Annotations; /// /// Marks the object as configuration item for NLog. diff --git a/src/NLog/Config/NLogDependencyResolveException.cs b/src/NLog/Config/NLogDependencyResolveException.cs index 78542e2c6f..a5fd2eeaed 100644 --- a/src/NLog/Config/NLogDependencyResolveException.cs +++ b/src/NLog/Config/NLogDependencyResolveException.cs @@ -31,12 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using JetBrains.Annotations; -using NLog.Internal; - namespace NLog.Config { + using System; + using NLog.Internal; + /// /// Failed to resolve the interface of service type /// @@ -45,12 +44,12 @@ public sealed class NLogDependencyResolveException : Exception /// /// Typed we tried to resolve /// - [NotNull] public Type ServiceType { get; } + public Type ServiceType { get; } /// /// Initializes a new instance of the class. /// - public NLogDependencyResolveException(string message, [NotNull] Type serviceType) : base(CreateFullMessage(serviceType, message)) + public NLogDependencyResolveException(string message, Type serviceType) : base(CreateFullMessage(serviceType, message)) { ServiceType = Guard.ThrowIfNull(serviceType); } @@ -58,7 +57,7 @@ public NLogDependencyResolveException(string message, [NotNull] Type serviceType /// /// Initializes a new instance of the class. /// - public NLogDependencyResolveException(string message, Exception innerException, [NotNull] Type serviceType) : base(CreateFullMessage(serviceType, message), innerException) + public NLogDependencyResolveException(string message, Exception innerException, Type serviceType) : base(CreateFullMessage(serviceType, message), innerException) { ServiceType = Guard.ThrowIfNull(serviceType); } diff --git a/src/NLog/Config/NameBaseAttribute.cs b/src/NLog/Config/NameBaseAttribute.cs index d71aa7fc90..848d3880aa 100644 --- a/src/NLog/Config/NameBaseAttribute.cs +++ b/src/NLog/Config/NameBaseAttribute.cs @@ -31,14 +31,13 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using JetBrains.Annotations; - namespace NLog.Config { using System; - using LayoutRenderers; - using Layouts; - using Targets; + using NLog.LayoutRenderers; + using NLog.Layouts; + using NLog.Targets; + using JetBrains.Annotations; /// /// Attaches a type-alias for an item (such as , diff --git a/src/NLog/Config/PropertyTypeConverter.cs b/src/NLog/Config/PropertyTypeConverter.cs index a20b746b34..d30d830585 100644 --- a/src/NLog/Config/PropertyTypeConverter.cs +++ b/src/NLog/Config/PropertyTypeConverter.cs @@ -36,7 +36,7 @@ namespace NLog.Config using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using System.Reflection; + using System.Globalization; using NLog.Internal; /// @@ -49,15 +49,15 @@ internal sealed class PropertyTypeConverter : IPropertyTypeConverter /// public static PropertyTypeConverter Instance { get; } = new PropertyTypeConverter(); - private static Dictionary> StringConverterLookup => _stringConverters ?? (_stringConverters = BuildStringConverterLookup()); - private static Dictionary> _stringConverters; + private static Dictionary> StringConverterLookup => _stringConverters ?? (_stringConverters = BuildStringConverterLookup()); + private static Dictionary>? _stringConverters; - private static Dictionary> BuildStringConverterLookup() + private static Dictionary> BuildStringConverterLookup() { - return new Dictionary>() + return new Dictionary>() { { typeof(System.Text.Encoding), (stringvalue, format, formatProvider) => ConvertToEncoding(stringvalue) }, - { typeof(System.Globalization.CultureInfo), (stringvalue, format, formatProvider) => new System.Globalization.CultureInfo(stringvalue) }, + { typeof(System.Globalization.CultureInfo), (stringvalue, format, formatProvider) => ConvertToCultureInfo(stringvalue) }, { typeof(Type), (stringvalue, format, formatProvider) => ConvertToType(stringvalue, true) }, { typeof(NLog.Targets.LineEndingMode), (stringvalue, format, formatProvider) => NLog.Targets.LineEndingMode.FromString(stringvalue) }, { typeof(LogLevel), (stringvalue, format, formatProvider) => LogLevel.FromString(stringvalue) }, @@ -77,11 +77,11 @@ internal static Type ConvertToType(string stringvalue, bool throwOnError) internal static bool IsComplexType(Type type) { - return !type.IsValueType() && !typeof(IConvertible).IsAssignableFrom(type) && !StringConverterLookup.ContainsKey(type) && type.GetFirstCustomAttribute() is null; + return !type.IsValueType && !typeof(IConvertible).IsAssignableFrom(type) && !StringConverterLookup.ContainsKey(type) && type.GetFirstCustomAttribute() is null; } /// - public object Convert(object propertyValue, Type propertyType, string format, IFormatProvider formatProvider) + public object? Convert(object? propertyValue, Type propertyType, string? format, IFormatProvider? formatProvider) { if (propertyValue is null || propertyType is null || propertyType.Equals(typeof(object))) { @@ -113,7 +113,7 @@ public object Convert(object propertyValue, Type propertyType, string format, IF return ChangeObjectType(propertyValue, propertyType, format, formatProvider); } - private static bool TryConvertFromString(string propertyString, Type propertyType, string format, IFormatProvider formatProvider, out object propertyValue) + private static bool TryConvertFromString(string propertyString, Type propertyType, string? format, IFormatProvider? formatProvider, out object? propertyValue) { propertyValue = propertyString = propertyString.Trim(); @@ -123,7 +123,7 @@ private static bool TryConvertFromString(string propertyString, Type propertyTyp return true; } - if (propertyType.IsEnum()) + if (propertyType.IsEnum) { return NLog.Common.ConversionHelpers.TryParseEnum(propertyString, propertyType, out propertyValue); } @@ -137,20 +137,18 @@ private static bool TryConvertFromString(string propertyString, Type propertyTyp return false; } - private static object ChangeObjectType(object propertyValue, Type propertyType, string format, IFormatProvider formatProvider) + private static object? ChangeObjectType(object propertyValue, Type propertyType, string? format, IFormatProvider? formatProvider) { - if (propertyValue is string propertyString && TryConvertFromString(propertyString, propertyType, format, formatProvider, out propertyValue)) + if (propertyValue is string propertyString && TryConvertFromString(propertyString, propertyType, format, formatProvider, out var fromStringValue)) { - return propertyValue; + return fromStringValue; } if (propertyValue is IConvertible convertibleValue) { var typeCode = convertibleValue.GetTypeCode(); -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 if (typeCode == TypeCode.DBNull) return convertibleValue; -#endif if (typeCode == TypeCode.Empty) return null; } @@ -170,7 +168,7 @@ private static object ChangeObjectType(object propertyValue, Type propertyType, [UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL2026")] [UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL2067")] [UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL2072")] - private static bool TryConvertToType(object propertyValue, Type propertyType, out object convertedValue) + private static bool TryConvertToType(object propertyValue, Type propertyType, out object? convertedValue) { if (propertyValue is null || propertyType.IsAssignableFrom(propertyValue.GetType())) { @@ -189,7 +187,7 @@ private static bool TryConvertToType(object propertyValue, Type propertyType, ou return false; } - private static object ConvertGuid(string format, string propertyString) + private static object ConvertGuid(string? format, string propertyString) { #if !NET35 return string.IsNullOrEmpty(format) ? Guid.Parse(propertyString) : Guid.ParseExact(propertyString, format); @@ -198,7 +196,18 @@ private static object ConvertGuid(string format, string propertyString) #endif } - private static object ConvertToEncoding(string stringValue) + internal static object? ConvertToCultureInfo(string? stringValue) + { + if (StringHelpers.IsNullOrWhiteSpace(stringValue)) + return null; + if (nameof(CultureInfo.InvariantCulture).Equals(stringValue, StringComparison.OrdinalIgnoreCase)) + return CultureInfo.InvariantCulture; + if (nameof(CultureInfo.CurrentCulture).Equals(stringValue, StringComparison.CurrentCulture)) + return CultureInfo.CurrentCulture; + return new CultureInfo(stringValue); + } + + internal static object ConvertToEncoding(string stringValue) { stringValue = stringValue.Trim(); if (string.Equals(stringValue, nameof(System.Text.Encoding.UTF8), StringComparison.OrdinalIgnoreCase)) @@ -206,7 +215,7 @@ private static object ConvertToEncoding(string stringValue) return System.Text.Encoding.GetEncoding(stringValue); } - private static object ConvertToTimeSpan(string format, IFormatProvider formatProvider, string propertyString) + private static object ConvertToTimeSpan(string? format, IFormatProvider? formatProvider, string propertyString) { #if !NET35 if (!string.IsNullOrEmpty(format)) @@ -217,14 +226,14 @@ private static object ConvertToTimeSpan(string format, IFormatProvider formatPro #endif } - private static object ConvertToDateTimeOffset(string format, IFormatProvider formatProvider, string propertyString) + private static object ConvertToDateTimeOffset(string? format, IFormatProvider? formatProvider, string propertyString) { if (!string.IsNullOrEmpty(format)) return DateTimeOffset.ParseExact(propertyString, format, formatProvider); return DateTimeOffset.Parse(propertyString, formatProvider); } - private static object ConvertToDateTime(string format, IFormatProvider formatProvider, string propertyString) + private static object ConvertToDateTime(string? format, IFormatProvider? formatProvider, string propertyString) { if (!string.IsNullOrEmpty(format)) return DateTime.ParseExact(propertyString, format, formatProvider); diff --git a/src/NLog/Config/RequiredParameterAttribute.cs b/src/NLog/Config/RequiredParameterAttribute.cs index c7c03a2f35..73c2e24a67 100644 --- a/src/NLog/Config/RequiredParameterAttribute.cs +++ b/src/NLog/Config/RequiredParameterAttribute.cs @@ -31,11 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using JetBrains.Annotations; - namespace NLog.Config { using System; + using JetBrains.Annotations; /// /// Attribute used to mark the required parameters for targets, @@ -43,6 +42,7 @@ namespace NLog.Config /// [AttributeUsage(AttributeTargets.Property)] [MeansImplicitUse] + [Obsolete("Instead perform relevant config validation in InitializeTarget / InitializeLayout. Marked obsolete with NLog v6.0")] public sealed class RequiredParameterAttribute : Attribute { } diff --git a/src/NLog/Config/ServiceRepository.cs b/src/NLog/Config/ServiceRepository.cs index ea7e6ef6b3..470ad8bb6d 100644 --- a/src/NLog/Config/ServiceRepository.cs +++ b/src/NLog/Config/ServiceRepository.cs @@ -53,7 +53,7 @@ public abstract class ServiceRepository : IServiceProvider /// Avoid calling this while handling a LogEvent, since random deadlocks can occur. public abstract object GetService(Type serviceType); - internal abstract bool TryGetService(out T serviceInstance) where T : class; + internal abstract bool TryGetService(out T? serviceInstance) where T : class; internal ServiceRepository() { diff --git a/src/NLog/Config/ServiceRepositoryExtensions.cs b/src/NLog/Config/ServiceRepositoryExtensions.cs index 715d449097..6787be879b 100644 --- a/src/NLog/Config/ServiceRepositoryExtensions.cs +++ b/src/NLog/Config/ServiceRepositoryExtensions.cs @@ -34,13 +34,12 @@ namespace NLog.Config { using System; - using JetBrains.Annotations; using NLog.Internal; using NLog.Targets; internal static class ServiceRepositoryExtensions { - internal static ServiceRepository GetServiceProvider([CanBeNull] this LoggingConfiguration loggingConfiguration) + internal static ServiceRepository GetServiceProvider(this LoggingConfiguration? loggingConfiguration) { return loggingConfiguration?.LogFactory?.ServiceRepository ?? LogManager.LogFactory.ServiceRepository; } @@ -57,7 +56,7 @@ internal static T ResolveService(this ServiceRepository serviceProvider, bool try { - if (serviceProvider.TryGetService(out var service)) + if (serviceProvider.TryGetService(out var service) && service != null) { return service; } @@ -74,15 +73,15 @@ internal static T ResolveService(this ServiceRepository serviceProvider, bool } catch (Exception ex) { - if (ex.MustBeRethrown()) + if (ex.MustBeRethrownImmediately()) throw; - throw new NLogDependencyResolveException(ex.Message, ex, typeof(T)); + throw new NLogDependencyResolveException($"Service Provider failed with exception - {ex.Message}", ex, typeof(T)); } if (ReferenceEquals(externalServiceProvider, serviceProvider)) { - throw new NLogDependencyResolveException("Instance of class must be registered", typeof(T)); + throw new NLogDependencyResolveException("Type not registered in Service Provider", typeof(T)); } // External IServiceProvider can be dangerous to use from Logging-library and can lead to deadlock or stackoverflow @@ -100,7 +99,7 @@ internal static T GetService(this IServiceProvider serviceProvider) where T : { var service = (serviceProvider ?? LogManager.LogFactory.ServiceRepository).GetService(typeof(T)) as T; if (service is null) - throw new NLogDependencyResolveException("Instance of class is unavailable", typeof(T)); + throw new NLogDependencyResolveException("Type not registered in Service Provider", typeof(T)); return service; } @@ -113,10 +112,10 @@ internal static T GetService(this IServiceProvider serviceProvider) where T : } catch (Exception ex) { - if (ex.MustBeRethrown()) + if (ex.MustBeRethrownImmediately()) throw; - throw new NLogDependencyResolveException(ex.Message, ex, typeof(T)); + throw new NLogDependencyResolveException($"Service Provider failed with exception - {ex.Message}", ex, typeof(T)); } } @@ -138,7 +137,7 @@ internal static ServiceRepository RegisterSingleton(this ServiceRepository se /// /// Registers the string serializer to use with /// - internal static ServiceRepository RegisterValueFormatter(this ServiceRepository serviceRepository, [NotNull] IValueFormatter valueFormatter) + internal static ServiceRepository RegisterValueFormatter(this ServiceRepository serviceRepository, IValueFormatter valueFormatter) { Guard.ThrowIfNull(valueFormatter); @@ -146,7 +145,7 @@ internal static ServiceRepository RegisterValueFormatter(this ServiceRepository return serviceRepository; } - internal static ServiceRepository RegisterJsonConverter(this ServiceRepository serviceRepository, [NotNull] IJsonConverter jsonConverter) + internal static ServiceRepository RegisterJsonConverter(this ServiceRepository serviceRepository, IJsonConverter jsonConverter) { Guard.ThrowIfNull(jsonConverter); @@ -154,7 +153,7 @@ internal static ServiceRepository RegisterJsonConverter(this ServiceRepository s return serviceRepository; } - internal static ServiceRepository RegisterPropertyTypeConverter(this ServiceRepository serviceRepository, [NotNull] IPropertyTypeConverter converter) + internal static ServiceRepository RegisterPropertyTypeConverter(this ServiceRepository serviceRepository, IPropertyTypeConverter converter) { Guard.ThrowIfNull(converter); @@ -162,7 +161,7 @@ internal static ServiceRepository RegisterPropertyTypeConverter(this ServiceRepo return serviceRepository; } - internal static ServiceRepository RegisterObjectTypeTransformer(this ServiceRepository serviceRepository, [NotNull] IObjectTypeTransformer transformer) + internal static ServiceRepository RegisterObjectTypeTransformer(this ServiceRepository serviceRepository, IObjectTypeTransformer transformer) { Guard.ThrowIfNull(transformer); @@ -170,12 +169,12 @@ internal static ServiceRepository RegisterObjectTypeTransformer(this ServiceRepo return serviceRepository; } - internal static ServiceRepository ParseMessageTemplates(this ServiceRepository serviceRepository, bool? enable) + internal static ServiceRepository ParseMessageTemplates(this ServiceRepository serviceRepository, LogFactory logFactory, bool? enable) { if (enable == true) { NLog.Common.InternalLogger.Debug("Message Template Format always enabled"); - serviceRepository.RegisterSingleton(new LogMessageTemplateFormatter(serviceRepository, true, false)); + serviceRepository.RegisterSingleton(new LogMessageTemplateFormatter(logFactory, true, false)); } else if (enable == false) { @@ -186,7 +185,7 @@ internal static ServiceRepository ParseMessageTemplates(this ServiceRepository s { //null = auto NLog.Common.InternalLogger.Debug("Message Template Auto Format enabled"); - serviceRepository.RegisterSingleton(new LogMessageTemplateFormatter(serviceRepository, false, false)); + serviceRepository.RegisterSingleton(new LogMessageTemplateFormatter(logFactory, false, false)); } return serviceRepository; } @@ -197,12 +196,13 @@ internal static ServiceRepository ParseMessageTemplates(this ServiceRepository s return messageFormatter?.EnableMessageTemplateParser; } - internal static ServiceRepository RegisterDefaults(this ServiceRepository serviceRepository) + internal static ServiceRepository RegisterDefaults(this ServiceRepository serviceRepository, LogFactory logFactory) { + // Maybe also include active TimeSource ? Could also be done with LogFactory extension-methods serviceRepository.RegisterSingleton(serviceRepository); - serviceRepository.RegisterSingleton(new LogMessageTemplateFormatter(serviceRepository, false, false)); + serviceRepository.RegisterSingleton(new LogMessageTemplateFormatter(logFactory, false, false)); serviceRepository.RegisterJsonConverter(new DefaultJsonSerializer(serviceRepository)); - serviceRepository.RegisterValueFormatter(new MessageTemplates.ValueFormatter(serviceRepository)); + serviceRepository.RegisterValueFormatter(new MessageTemplates.ValueFormatter(serviceRepository, legacyStringQuotes: false)); serviceRepository.RegisterPropertyTypeConverter(PropertyTypeConverter.Instance); serviceRepository.RegisterObjectTypeTransformer(new ObjectReflectionCache(serviceRepository)); return serviceRepository; diff --git a/src/NLog/Config/ServiceRepositoryInternal.cs b/src/NLog/Config/ServiceRepositoryInternal.cs index 2a87e19d54..b64b882cac 100644 --- a/src/NLog/Config/ServiceRepositoryInternal.cs +++ b/src/NLog/Config/ServiceRepositoryInternal.cs @@ -35,9 +35,6 @@ namespace NLog.Config { using System; using System.Collections.Generic; - using System.Reflection; - using JetBrains.Annotations; - using NLog.Common; using NLog.Internal; /// @@ -46,20 +43,15 @@ namespace NLog.Config internal sealed class ServiceRepositoryInternal : ServiceRepository { private readonly Dictionary> _creatorMap = new Dictionary>(); - private readonly Dictionary _lateBoundMap = new Dictionary(); private readonly object _lockObject = new object(); - public event EventHandler TypeRegistered; + public event EventHandler? TypeRegistered; /// /// Initializes a new instance of the class. /// - internal ServiceRepositoryInternal(bool resetGlobalCache = false) + internal ServiceRepositoryInternal(LogFactory logFactory) { - if (resetGlobalCache) - ConfigurationItemFactory.Default = null; //build new global factory - - this.RegisterDefaults(); - // Maybe also include active TimeSource ? Could also be done with LogFactory extension-methods + this.RegisterDefaults(logFactory); } public override void RegisterService(Type type, object instance) @@ -77,151 +69,36 @@ public override void RegisterService(Type type, object instance) public override object GetService(Type serviceType) { - var serviceInstance = DefaultResolveInstance(serviceType, null); - if (serviceInstance is null && serviceType.IsAbstract()) - { - throw new NLogDependencyResolveException("Instance of class must be registered", serviceType); - } - return serviceInstance; - } + object? serviceInstance = TryGetService(serviceType); + if (serviceInstance is null) + throw new NLogDependencyResolveException("Type not registered in Service Provider", serviceType); - internal override bool TryGetService(out T serviceInstance) - { - serviceInstance = DefaultResolveInstance(typeof(T), null) as T; - return !(serviceInstance is null); + return serviceInstance; } - private object DefaultResolveInstance(Type itemType, HashSet seenTypes) + private object? TryGetService(Type serviceType) { - Guard.ThrowIfNull(itemType); - - Func objectResolver = null; - CompiledConstructor compiledConstructor = null; + Guard.ThrowIfNull(serviceType); + Func? objectResolver = null; lock (_lockObject) { - if (!_creatorMap.TryGetValue(itemType, out objectResolver)) - { - _lateBoundMap.TryGetValue(itemType, out compiledConstructor); - } - } - - if (objectResolver is null && compiledConstructor is null) - { - if (itemType.IsAbstract()) - { - if (seenTypes is null) - return null; - else - throw new NLogDependencyResolveException("Instance of class must be registered", itemType); - } - - // Do not hold lock while resolving types to avoid deadlock on initialization of type static members -#pragma warning disable CS0618 // Type or member is obsolete - var newCompiledConstructor = CreateCompiledConstructor(itemType); -#pragma warning restore CS0618 // Type or member is obsolete - - lock (_lockObject) - { - if (!_lateBoundMap.TryGetValue(itemType, out compiledConstructor)) - { - _lateBoundMap.Add(itemType, newCompiledConstructor); - compiledConstructor = newCompiledConstructor; - } - } + _creatorMap.TryGetValue(serviceType, out objectResolver); } - // Do not hold lock while calling constructor (or resolving parameter values) to avoid deadlock - var constructorParameters = compiledConstructor?.Parameters; - if (constructorParameters is null) - { - return objectResolver?.Invoke() ?? compiledConstructor?.Ctor(null); - } - else - { - seenTypes = seenTypes ?? new HashSet(); - var parameterValues = CreateCtorParameterValues(constructorParameters, seenTypes); - return compiledConstructor?.Ctor(parameterValues); - } + return objectResolver?.Invoke(); } - [Obsolete("Instead use NLog.LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Ignore since obsolete", "IL2070")] - private CompiledConstructor CreateCompiledConstructor(Type itemType) + internal override bool TryGetService(out T? serviceInstance) where T : class { - try + if (TryGetService(typeof(T)) is T service) { - InternalLogger.Debug("Object reflection needed to create instance of type: {0}", itemType); - - var defaultConstructor = itemType.GetConstructor(Type.EmptyTypes); - if (defaultConstructor is null) - { - InternalLogger.Trace("Resolves parameterized constructor for {0}", itemType); - var ctors = itemType.GetConstructors(); - if (ctors.Length == 0) - { - throw new NLogDependencyResolveException("No public constructor", itemType); - } - - if (ctors.Length > 1) - { - throw new NLogDependencyResolveException("Multiple public constructor are not supported if there isn't a default constructor'", itemType); - } - - var ctor = ctors[0]; - var constructorMethod = ReflectionHelpers.CreateLateBoundConstructor(ctor); - return new CompiledConstructor(constructorMethod, ctor.GetParameters()); - } - else - { - InternalLogger.Trace("Resolves default constructor for {0}", itemType); - var constructorMethod = ReflectionHelpers.CreateLateBoundConstructor(defaultConstructor); - return new CompiledConstructor(constructorMethod); - } + serviceInstance = service; + return true; } - catch (MissingMethodException exception) - { - throw new NLogDependencyResolveException("Is the required permission granted?", exception, itemType); - } - finally - { - InternalLogger.Trace("Resolve {0} done", itemType.FullName); - } - } - - private object[] CreateCtorParameterValues(ParameterInfo[] parameterInfos, HashSet seenTypes) - { - var parameterValues = new object[parameterInfos.Length]; - - for (var i = 0; i < parameterInfos.Length; i++) - { - var param = parameterInfos[i]; - - var parameterType = param.ParameterType; - if (seenTypes.Contains(parameterType)) - { - throw new NLogDependencyResolveException("There is a cycle", parameterType); - } - seenTypes.Add(parameterType); - - var paramValue = DefaultResolveInstance(parameterType, seenTypes); - parameterValues[i] = paramValue; - } - - return parameterValues; - } - - private sealed class CompiledConstructor - { - [NotNull] public ReflectionHelpers.LateBoundConstructor Ctor { get; } - [CanBeNull] public ParameterInfo[] Parameters { get; } - - public CompiledConstructor([NotNull] ReflectionHelpers.LateBoundConstructor ctor, ParameterInfo[] parameters = null) - { - Ctor = Guard.ThrowIfNull(ctor); - Parameters = parameters; - } + serviceInstance = default(T); + return false; } } } diff --git a/src/NLog/Config/ServiceRepositoryUpdateEventArgs.cs b/src/NLog/Config/ServiceRepositoryUpdateEventArgs.cs index a86e16d197..b6d37666f7 100644 --- a/src/NLog/Config/ServiceRepositoryUpdateEventArgs.cs +++ b/src/NLog/Config/ServiceRepositoryUpdateEventArgs.cs @@ -31,10 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; - namespace NLog.Config { + using System; + using NLog.Internal; + /// /// Registered service type in the service repository /// @@ -46,7 +47,7 @@ public class ServiceRepositoryUpdateEventArgs : EventArgs /// Type of service that have been registered public ServiceRepositoryUpdateEventArgs(Type serviceType) { - ServiceType = serviceType; + ServiceType = Guard.ThrowIfNull(serviceType); } /// diff --git a/src/NLog/Config/SimpleConfigurator.cs b/src/NLog/Config/SimpleConfigurator.cs index 4b54652fba..4f17ee8bc7 100644 --- a/src/NLog/Config/SimpleConfigurator.cs +++ b/src/NLog/Config/SimpleConfigurator.cs @@ -49,7 +49,6 @@ namespace NLog.Config [EditorBrowsable(EditorBrowsableState.Never)] public static class SimpleConfigurator { -#if !NETSTANDARD1_3 /// /// Obsolete and replaced by and with NLog v5.2. /// @@ -80,7 +79,6 @@ public static void ConfigureForConsoleLogging(LogLevel minLevel) config.AddRule(minLevel, LogLevel.MaxLevel, consoleTarget, "*"); LogManager.Configuration = config; } -#endif /// /// Obsolete and replaced by with NLog v5.2. diff --git a/src/NLog/Config/XmlLoggingConfiguration.cs b/src/NLog/Config/XmlLoggingConfiguration.cs index e9226fabb4..7faacd78b3 100644 --- a/src/NLog/Config/XmlLoggingConfiguration.cs +++ b/src/NLog/Config/XmlLoggingConfiguration.cs @@ -38,27 +38,23 @@ namespace NLog.Config using System.ComponentModel; using System.IO; using System.Linq; - using System.Xml; - using JetBrains.Annotations; + using System.Threading; using NLog.Common; using NLog.Internal; using NLog.Layouts; /// - /// A class for configuring NLog through an XML configuration file - /// (App.config style or App.nlog style). - /// - /// Parsing of the XML file is also implemented in this class. + /// Loads NLog LoggingConfiguration from xml-file /// - /// - /// - This class is thread-safe..ToList() is used for that purpose. - /// - Update TemplateXSD.xml for changes outside targets + /// + /// Make sure to update official NLog.xsd schema, when adding new config-options outside targets/layouts /// - public class XmlLoggingConfiguration : LoggingConfigurationParser, IInitializeSucceeded + public class XmlLoggingConfiguration : LoggingConfigurationParser { + private static readonly Dictionary _watchers = new Dictionary(); private readonly Dictionary _fileMustAutoReloadLookup = new Dictionary(StringComparer.OrdinalIgnoreCase); - private string _originalFileName; + private string? _originalFileName; private readonly Stack _currentFilePath = new Stack(); @@ -70,123 +66,100 @@ internal XmlLoggingConfiguration(LogFactory logFactory) /// /// Initializes a new instance of the class. /// - /// Configuration file to be read. - public XmlLoggingConfiguration([NotNull] string fileName) + /// Path to the config-file to read. + public XmlLoggingConfiguration(string fileName) : this(fileName, LogManager.LogFactory) { } /// /// Initializes a new instance of the class. /// - /// Configuration file to be read. + /// Path to the config-file to read. /// The to which to apply any applicable configuration values. - public XmlLoggingConfiguration([NotNull] string fileName, LogFactory logFactory) + public XmlLoggingConfiguration(string fileName, LogFactory logFactory) : base(logFactory) { + Guard.ThrowIfNullOrEmpty(fileName); LoadFromXmlFile(fileName); } /// - /// Obsolete and replaced by with NLog v4.7. - /// /// Initializes a new instance of the class. /// - /// Configuration file to be read. - /// Ignore any errors during configuration. - [Obsolete("Constructor with parameter ignoreErrors has limited effect. Instead use LogManager.ThrowConfigExceptions. Marked obsolete in NLog 4.7")] - [EditorBrowsable(EditorBrowsableState.Never)] - public XmlLoggingConfiguration([NotNull] string fileName, bool ignoreErrors) - : this(fileName, ignoreErrors, LogManager.LogFactory) - { } + /// Configuration file to be read. + public XmlLoggingConfiguration(TextReader xmlSource) + : this(xmlSource, null) + { + } /// - /// Obsolete and replaced by with NLog v4.7. - /// /// Initializes a new instance of the class. /// - /// Configuration file to be read. - /// Ignore any errors during configuration. + /// Configuration file to be read. + /// Path to the config-file that contains the element (to be used as a base for including other files). null is allowed. + public XmlLoggingConfiguration(TextReader xmlSource, string? filePath) + : this(xmlSource, filePath, LogManager.LogFactory) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Configuration file to be read. + /// Path to the config-file that contains the element (to be used as a base for including other files). null is allowed. /// The to which to apply any applicable configuration values. - [Obsolete("Constructor with parameter ignoreErrors has limited effect. Instead use LogManager.ThrowConfigExceptions. Marked obsolete in NLog 4.7")] - [EditorBrowsable(EditorBrowsableState.Never)] - public XmlLoggingConfiguration([NotNull] string fileName, bool ignoreErrors, LogFactory logFactory) + public XmlLoggingConfiguration(TextReader xmlSource, string? filePath, LogFactory logFactory) : base(logFactory) { - using (XmlReader reader = CreateFileReader(fileName)) - { - Initialize(reader, fileName, ignoreErrors); - } + Guard.ThrowIfNull(xmlSource); + ParseFromTextReader(xmlSource, filePath); } +#if NETFRAMEWORK /// /// Initializes a new instance of the class. /// /// XML reader to read from. - public XmlLoggingConfiguration([NotNull] XmlReader reader) + [Obsolete("Instead use TextReader as input. Marked obsolete with NLog 6.0")] + public XmlLoggingConfiguration(System.Xml.XmlReader reader) : this(reader, null) { } /// /// Initializes a new instance of the class. /// - /// containing the configuration section. - /// Name of the file that contains the element (to be used as a base for including other files). null is allowed. - public XmlLoggingConfiguration([NotNull] XmlReader reader, [CanBeNull] string fileName) + /// XmlReader containing the configuration section. + /// Path to the config-file that contains the element (to be used as a base for including other files). null is allowed. + [Obsolete("Instead use TextReader as input. Marked obsolete with NLog 6.0")] + public XmlLoggingConfiguration(System.Xml.XmlReader reader, string? fileName) : this(reader, fileName, LogManager.LogFactory) { } /// /// Initializes a new instance of the class. /// - /// containing the configuration section. - /// Name of the file that contains the element (to be used as a base for including other files). null is allowed. + /// XmlReader containing the configuration section. + /// Path to the config-file that contains the element (to be used as a base for including other files). null is allowed. /// The to which to apply any applicable configuration values. - public XmlLoggingConfiguration([NotNull] XmlReader reader, [CanBeNull] string fileName, LogFactory logFactory) + [Obsolete("Instead use TextReader as input. Marked obsolete with NLog 6.0")] + public XmlLoggingConfiguration(System.Xml.XmlReader reader, string? fileName, LogFactory logFactory) : base(logFactory) { - Initialize(reader, fileName); - } - - /// - /// Obsolete and replaced by with NLog v4.7. - /// - /// Initializes a new instance of the class. - /// - /// containing the configuration section. - /// Name of the file that contains the element (to be used as a base for including other files). null is allowed. - /// Ignore any errors during configuration. - [Obsolete("Constructor with parameter ignoreErrors has limited effect. Instead use LogManager.ThrowConfigExceptions. Marked obsolete in NLog 4.7")] - [EditorBrowsable(EditorBrowsableState.Never)] - public XmlLoggingConfiguration([NotNull] XmlReader reader, [CanBeNull] string fileName, bool ignoreErrors) - : this(reader, fileName, ignoreErrors, LogManager.LogFactory) - { } - - /// - /// Obsolete and replaced by with NLog v4.7. - /// - /// Initializes a new instance of the class. - /// - /// containing the configuration section. - /// Name of the file that contains the element (to be used as a base for including other files). null is allowed. - /// Ignore any errors during configuration. - /// The to which to apply any applicable configuration values. - [Obsolete("Constructor with parameter ignoreErrors has limited effect. Instead use LogManager.ThrowConfigExceptions. Marked obsolete in NLog 4.7")] - [EditorBrowsable(EditorBrowsableState.Never)] - public XmlLoggingConfiguration([NotNull] XmlReader reader, [CanBeNull] string fileName, bool ignoreErrors, LogFactory logFactory) - : base(logFactory) - { - Initialize(reader, fileName, ignoreErrors); + Guard.ThrowIfNull(reader); + ParseFromXmlReader(reader, fileName); } +#endif /// /// Initializes a new instance of the class. /// /// NLog configuration as XML string. - /// Name of the XML file. + /// Path to the config-file that contains the element (to be used as a base for including other files). null is allowed. /// The to which to apply any applicable configuration values. - internal XmlLoggingConfiguration([NotNull] string xmlContents, [CanBeNull] string fileName, LogFactory logFactory) + internal XmlLoggingConfiguration(string xmlContents, string filePath, LogFactory logFactory) : base(logFactory) { - LoadFromXmlContent(xmlContents, fileName); + Guard.ThrowIfNullOrEmpty(xmlContents); + LoadFromXmlContent(xmlContents, filePath); } /// @@ -208,39 +181,13 @@ public static XmlLoggingConfiguration CreateFromXmlString(string xml, LogFactory return new XmlLoggingConfiguration(xml, string.Empty, logFactory); } -#if NETFRAMEWORK - /// - /// Gets the default object by parsing - /// the application configuration file (app.exe.config). - /// - public static LoggingConfiguration AppConfig - { - get - { - object o = System.Configuration.ConfigurationManager.GetSection("nlog"); - return o as LoggingConfiguration; - } - } -#endif - - /// - /// Did the Succeeded? true= success, false= error, null = initialize not started yet. - /// - public bool? InitializeSucceeded { get; private set; } - /// - /// Gets or sets a value indicating whether all of the configuration files + /// Gets or sets a value indicating whether any of the configuration files /// should be watched for changes and reloaded automatically when changed. /// public bool AutoReload { - get - { - if (_fileMustAutoReloadLookup.Count == 0) - return false; - else - return _fileMustAutoReloadLookup.Values.All(mustAutoReload => mustAutoReload); - } + get => AutoReloadFileNames.Any(); set { var autoReloadFiles = _fileMustAutoReloadLookup.Keys.ToList(); @@ -254,32 +201,86 @@ public bool AutoReload /// This is the list of configuration files processed. /// If the autoReload attribute is not set it returns empty collection. /// - public override IEnumerable FileNamesToWatch + public IEnumerable AutoReloadFileNames { get { - return _fileMustAutoReloadLookup.Where(entry => entry.Value).Select(entry => entry.Key); + if (_fileMustAutoReloadLookup.Count == 0) + return ArrayHelper.Empty(); + else + return _fileMustAutoReloadLookup.Where(entry => entry.Value).Select(entry => entry.Key); } } + /// + [Obsolete("Replaced by AutoReloadFileNames. Marked obsolete with NLog v6")] + [EditorBrowsable(EditorBrowsableState.Never)] + public override IEnumerable FileNamesToWatch => AutoReloadFileNames; + /// - /// Re-reads the original configuration file and returns the new object. + /// Loads the NLog LoggingConfiguration from its original configuration file and returns the new object. /// /// The newly loaded instance. /// Must assign the returned object to LogManager.Configuration to activate it public override LoggingConfiguration Reload() { - if (!string.IsNullOrEmpty(_originalFileName)) + var originalFileName = _originalFileName ?? string.Empty; + if (!string.IsNullOrEmpty(originalFileName)) { var newConfig = new XmlLoggingConfiguration(LogFactory); newConfig.PrepareForReload(this); - newConfig.LoadFromXmlFile(_originalFileName); + newConfig.LoadFromXmlFile(originalFileName); return newConfig; } return base.Reload(); } + /// + protected internal override void OnConfigurationAssigned(LogFactory? logFactory) + { + base.OnConfigurationAssigned(logFactory); + + try + { + var configFactory = logFactory ?? LogFactory ?? NLog.LogManager.LogFactory; + + AutoReloadConfigFileWatcher? fileWatcher = null; + lock (_watchers) + { + _watchers.TryGetValue(configFactory, out fileWatcher); + } + + if (logFactory is null || !AutoReload) + { + if (fileWatcher != null) + { + InternalLogger.Info("AutoReload Config File Monitor stopping, since no active configuration"); + fileWatcher.Dispose(); + } + } + else + { + InternalLogger.Debug("AutoReload Config File Monitor refreshing after configuration changed"); + if (fileWatcher is null || fileWatcher.IsDisposed) + { + InternalLogger.Info("AutoReload Config File Monitor starting"); + fileWatcher = new AutoReloadConfigFileWatcher(configFactory); + lock (_watchers) + { + _watchers[configFactory] = fileWatcher; + } + } + + fileWatcher.RefreshFileWatcher(AutoReloadFileNames); + } + } + catch (Exception ex) + { + InternalLogger.Error(ex, "AutoReload Config File Monitor failed to refresh after configuration changed."); + } + } + /// /// Obsolete and replaced by and with NLog v5.2. /// @@ -318,94 +319,86 @@ public static void ResetCandidateConfigFilePath() LogManager.LogFactory.ResetCandidateConfigFilePath(); } - private void LoadFromXmlFile(string fileName) - { - using (XmlReader reader = CreateFileReader(fileName)) - { - Initialize(reader, fileName); - } - } - - internal void LoadFromXmlContent(string xmlContent, string fileName) + private void LoadFromXmlFile(string filePath) { - using (var stringReader = new StringReader(xmlContent)) + using (var textReader = LogFactory.CurrentAppEnvironment.LoadTextFile(filePath)) { - using (XmlReader reader = XmlReader.Create(stringReader)) - { - Initialize(reader, fileName); - } + ParseFromTextReader(textReader, filePath); } } - /// - /// Create XML reader for (xml config) file. - /// - /// filepath - /// reader or null if filename is empty. - private XmlReader CreateFileReader(string fileName) + internal void LoadFromXmlContent(string xmlContents, string filePath) { - if (!string.IsNullOrEmpty(fileName)) + using (var stringReader = new StringReader(xmlContents)) { - fileName = fileName.Trim(); - return LogFactory.CurrentAppEnvironment.LoadXmlFile(fileName); + ParseFromTextReader(stringReader, filePath); } - return null; } - /// - /// Initializes the configuration. - /// - /// containing the configuration section. - /// Name of the file that contains the element (to be used as a base for including other files). null is allowed. - /// Ignore any errors during configuration. - private void Initialize([NotNull] XmlReader reader, [CanBeNull] string fileName, bool ignoreErrors = false) +#if NETFRAMEWORK + [Obsolete("Instead use TextReader as input. Marked obsolete with NLog 6.0")] + private void ParseFromXmlReader(System.Xml.XmlReader reader, string? filePath) { try { - InitializeSucceeded = null; - _originalFileName = string.IsNullOrEmpty(fileName) ? fileName : GetFileLookupKey(fileName); + _originalFileName = (filePath is null || StringHelpers.IsNullOrWhiteSpace(filePath)) ? null : GetFileLookupKey(filePath); reader.MoveToContent(); var content = new XmlLoggingConfigurationElement(reader); if (!string.IsNullOrEmpty(_originalFileName)) { InternalLogger.Info("Loading NLog config from XML file: {0}", _originalFileName); - ParseTopLevel(content, fileName, autoReloadDefault: false); + ParseTopLevel(content, filePath, autoReloadDefault: false); } else { ParseTopLevel(content, null, autoReloadDefault: false); } - InitializeSucceeded = true; } catch (Exception exception) { - InitializeSucceeded = false; + var configurationException = new NLogConfigurationException($"Exception when loading configuration {filePath}", exception); + InternalLogger.Error(exception, configurationException.Message); + throw configurationException; + } + } +#endif - if (exception.MustBeRethrownImmediately()) + private void ParseFromTextReader(TextReader textReader, string? filePath) + { + try + { + _originalFileName = (filePath is null || StringHelpers.IsNullOrWhiteSpace(filePath)) ? null : GetFileLookupKey(filePath); + var content = new XmlParserConfigurationElement(new XmlParser(textReader).LoadDocument(out var _)); + if (!string.IsNullOrEmpty(_originalFileName)) { - throw; + InternalLogger.Info("Loading NLog config from XML file: {0}", _originalFileName); + ParseTopLevel(content, filePath, autoReloadDefault: false); } - - var configurationException = new NLogConfigurationException($"Exception when loading configuration {fileName}", exception); - InternalLogger.Error(exception, configurationException.Message); - if (!ignoreErrors && (LogFactory.ThrowConfigExceptions ?? LogFactory.ThrowExceptions || configurationException.MustBeRethrown())) - throw configurationException; + else + { + ParseTopLevel(content, null, autoReloadDefault: false); + } + } + catch (Exception exception) + { + var filePathValue = string.IsNullOrEmpty(filePath) ? "" : $" FilePath: {filePath}"; + var configurationException = new NLogConfigurationException($"Failed loading NLog configuration. {exception.Message} {filePathValue}", exception); + InternalLogger.Error(configurationException, configurationException.Message); + throw configurationException; } } /// - /// Add a file with configuration. Check if not already included. + /// Include new file into the configuration. Check if not already included. /// - /// - /// - private void ConfigureFromFile([NotNull] string fileName, bool autoReloadDefault) + private void IncludeNewConfigFile(string filePath, bool autoReloadDefault) { - if (!_fileMustAutoReloadLookup.ContainsKey(GetFileLookupKey(fileName))) + if (!_fileMustAutoReloadLookup.ContainsKey(GetFileLookupKey(filePath))) { - using (var reader = LogFactory.CurrentAppEnvironment.LoadXmlFile(fileName)) + using (var textReader = LogFactory.CurrentAppEnvironment.LoadTextFile(filePath)) { - reader.MoveToContent(); - ParseTopLevel(new XmlLoggingConfigurationElement(reader, false), fileName, autoReloadDefault); + var configElement = new XmlParserConfigurationElement(new XmlParser(textReader).LoadDocument(out var _), false); + ParseTopLevel(configElement, filePath, autoReloadDefault); } } } @@ -414,13 +407,13 @@ private void ConfigureFromFile([NotNull] string fileName, bool autoReloadDefault /// Parse the root /// /// - /// path to config file. + /// Path to the config-file that contains the element (to be used as a base for including other files). null is allowed. /// The default value for the autoReload option. - private void ParseTopLevel(XmlLoggingConfigurationElement content, [CanBeNull] string filePath, bool autoReloadDefault) + private void ParseTopLevel(ILoggingConfigurationElement content, string? filePath, bool autoReloadDefault) { content.AssertName("nlog", "configuration"); - switch (content.LocalName.ToUpperInvariant()) + switch (content.Name.ToUpperInvariant()) { case "CONFIGURATION": ParseConfigurationElement(content, filePath, autoReloadDefault); @@ -436,9 +429,9 @@ private void ParseTopLevel(XmlLoggingConfigurationElement content, [CanBeNull] s /// Parse {configuration} xml element. /// /// - /// path to config file. + /// Path to the config-file that contains the element (to be used as a base for including other files). null is allowed. /// The default value for the autoReload option. - private void ParseConfigurationElement(XmlLoggingConfigurationElement configurationElement, [CanBeNull] string filePath, bool autoReloadDefault) + private void ParseConfigurationElement(ILoggingConfigurationElement configurationElement, string? filePath, bool autoReloadDefault) { InternalLogger.Trace("ParseConfigurationElement"); configurationElement.AssertName("configuration"); @@ -454,9 +447,9 @@ private void ParseConfigurationElement(XmlLoggingConfigurationElement configurat /// Parse {NLog} xml element. /// /// - /// path to config file. + /// Path to the config-file that contains the element (to be used as a base for including other files). null is allowed. /// The default value for the autoReload option. - private void ParseNLogElement(ILoggingConfigurationElement nlogElement, [CanBeNull] string filePath, bool autoReloadDefault) + private void ParseNLogElement(ILoggingConfigurationElement nlogElement, string? filePath, bool autoReloadDefault) { InternalLogger.Trace("ParseNLogElement"); nlogElement.AssertName("nlog"); @@ -465,8 +458,8 @@ private void ParseNLogElement(ILoggingConfigurationElement nlogElement, [CanBeNu try { - string baseDirectory = null; - if (!string.IsNullOrEmpty(filePath)) + string? baseDirectory = null; + if (filePath != null && !StringHelpers.IsNullOrWhiteSpace(filePath)) { _fileMustAutoReloadLookup[GetFileLookupKey(filePath)] = autoReload; _currentFilePath.Push(filePath); @@ -490,7 +483,7 @@ protected override bool ParseNLogSection(ILoggingConfigurationElement configSect { if (configSection.MatchesName("include")) { - string filePath = _currentFilePath.Peek(); + var filePath = _currentFilePath.Peek(); bool autoLoad = !string.IsNullOrEmpty(filePath) && _fileMustAutoReloadLookup[GetFileLookupKey(filePath)]; ParseIncludeElement(configSection, !string.IsNullOrEmpty(filePath) ? Path.GetDirectoryName(filePath) : null, autoLoad); return true; @@ -501,7 +494,7 @@ protected override bool ParseNLogSection(ILoggingConfigurationElement configSect } } - private void ParseIncludeElement(ILoggingConfigurationElement includeElement, string baseDirectory, bool autoReloadDefault) + private void ParseIncludeElement(ILoggingConfigurationElement includeElement, string? baseDirectory, bool autoReloadDefault) { includeElement.AssertName("include"); @@ -512,36 +505,35 @@ private void ParseIncludeElement(ILoggingConfigurationElement includeElement, st try { newFileName = ExpandSimpleVariables(newFileName); - newFileName = SimpleLayout.Evaluate(newFileName); - var fullNewFileName = newFileName; + newFileName = SimpleLayout.Evaluate(newFileName, this); + var filePath = newFileName; if (baseDirectory != null) { - fullNewFileName = Path.Combine(baseDirectory, newFileName); + filePath = Path.Combine(baseDirectory, newFileName); } - if (File.Exists(fullNewFileName)) + if (File.Exists(filePath)) { - InternalLogger.Debug("Including file '{0}'", fullNewFileName); - ConfigureFromFile(fullNewFileName, autoReloadDefault); + InternalLogger.Debug("Including file '{0}'", filePath); + IncludeNewConfigFile(filePath, autoReloadDefault); } else { //is mask? - if (newFileName.IndexOf('*') >= 0) { - ConfigureFromFilesByMask(baseDirectory, newFileName, autoReloadDefault); + IncludeConfigFilesByMask(baseDirectory ?? ".", newFileName, autoReloadDefault); } else { if (ignoreErrors) { //quick stop for performances - InternalLogger.Debug("Skipping included file '{0}' as it can't be found", fullNewFileName); + InternalLogger.Debug("Skipping included file '{0}' as it can't be found", filePath); return; } - throw new FileNotFoundException("Included file not found: " + fullNewFileName); + throw new FileNotFoundException("Included file not found: " + filePath); } } } @@ -565,7 +557,7 @@ private void ParseIncludeElement(ILoggingConfigurationElement includeElement, st /// base directory in case if is relative /// relative or absolute fileMask /// - private void ConfigureFromFilesByMask(string baseDirectory, string fileMask, bool autoReloadDefault) + private void IncludeConfigFilesByMask(string baseDirectory, string fileMask, bool autoReloadDefault) { var directory = baseDirectory; @@ -580,24 +572,24 @@ private void ConfigureFromFilesByMask(string baseDirectory, string fileMask, boo } var filename = Path.GetFileName(fileMask); - if (filename is null) { InternalLogger.Warn("filename is empty for include of '{0}'", fileMask); return; } + fileMask = filename; } var files = Directory.GetFiles(directory, fileMask); - foreach (var file in files) + foreach (var filePath in files) { //note we exclude our self in ConfigureFromFile - ConfigureFromFile(file, autoReloadDefault); + IncludeNewConfigFile(filePath, autoReloadDefault); } } - private static string GetFileLookupKey([NotNull] string fileName) + private static string GetFileLookupKey(string fileName) { return Path.GetFullPath(fileName); } @@ -607,5 +599,150 @@ public override string ToString() { return $"{base.ToString()}, FilePath={_originalFileName}"; } + + private sealed class AutoReloadConfigFileWatcher : IDisposable + { + private readonly LogFactory _logFactory; + private readonly MultiFileWatcher _fileWatcher = new MultiFileWatcher(); + private readonly object _lockObject = new object(); + private Timer? _reloadTimer; + private bool _isDisposing; + + internal bool IsDisposed => _isDisposing; + + public AutoReloadConfigFileWatcher(LogFactory logFactory) + { + _logFactory = logFactory; + _fileWatcher.FileChanged += FileWatcher_FileChanged; + } + + private void FileWatcher_FileChanged(object sender, System.IO.FileSystemEventArgs e) + { + lock (_lockObject) + { + if (_isDisposing) + return; + + var reloadTimer = _reloadTimer; + if (reloadTimer is null) + { + var currentConfig = _logFactory.Configuration; + if (currentConfig is null) + return; + + _reloadTimer = new Timer((s) => ReloadTimer(s), currentConfig, 1000, Timeout.Infinite); + } + else + { + reloadTimer.Change(1000, Timeout.Infinite); + } + } + } + + private void ReloadTimer(object state) + { + if (_isDisposing) + { + return; //timer was disposed already. + } + + LoggingConfiguration oldConfig = (LoggingConfiguration)state; + + InternalLogger.Info("AutoReload Config File Monitor reloading configuration..."); + + lock (_lockObject) + { + if (_isDisposing) + { + return; //timer was disposed already. + } + + var currentTimer = _reloadTimer; + if (currentTimer != null) + { + _reloadTimer = null; + currentTimer.Dispose(); + } + } + + LoggingConfiguration? newConfig = null; + + try + { + var currentConfig = _logFactory.Configuration; + if (!ReferenceEquals(currentConfig, oldConfig)) + { + InternalLogger.Debug("AutoReload Config File Monitor skipping reload, since existing NLog config has changed."); + return; + } + + newConfig = oldConfig.Reload(); + if (newConfig is null || ReferenceEquals(newConfig, oldConfig)) + { + InternalLogger.Debug("AutoReload Config File Monitor skipping reload, since new configuration has not changed."); + return; + } + + currentConfig = _logFactory.Configuration; + if (!ReferenceEquals(currentConfig, oldConfig)) + { + InternalLogger.Debug("AutoReload Config File Monitor skipping reload, since existing NLog config has changed."); + return; + } + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "AutoReload Config File Monitor failed to reload NLog LoggingConfiguration."); + return; + } + + try + { + TryUnwatchConfigFile(); + _logFactory.Configuration = newConfig; + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "AutoReload Config File Monitor failed to activate new NLog LoggingConfiguration."); + _fileWatcher.Watch((oldConfig as XmlLoggingConfiguration)?.AutoReloadFileNames ?? ArrayHelper.Empty()); + + } + } + + public void RefreshFileWatcher(IEnumerable fileNamesToWatch) + { + _fileWatcher.Watch(fileNamesToWatch); + } + + public void Dispose() + { + _isDisposing = true; + _fileWatcher.FileChanged -= FileWatcher_FileChanged; + lock (_lockObject) + { + var reloadTimer = _reloadTimer; + _reloadTimer = null; + reloadTimer?.Dispose(); + } + _fileWatcher.Dispose(); + } + + private void TryUnwatchConfigFile() + { + try + { + _fileWatcher?.StopWatching(); + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "AutoReload Config File Monitor failed to stop file watcher."); + + if (LogManager.ThrowExceptions || _logFactory.ThrowExceptions) + { + throw; + } + } + } + } } } diff --git a/src/NLog/Config/XmlLoggingConfigurationElement.cs b/src/NLog/Config/XmlLoggingConfigurationElement.cs index 7d3f267ef0..6a8ef08154 100644 --- a/src/NLog/Config/XmlLoggingConfigurationElement.cs +++ b/src/NLog/Config/XmlLoggingConfigurationElement.cs @@ -31,6 +31,8 @@ // THE POSSIBILITY OF SUCH DAMAGE. // +#if NETFRAMEWORK + namespace NLog.Config { using System; @@ -42,6 +44,7 @@ namespace NLog.Config /// /// Represents simple XML element with case-insensitive attribute semantics. /// + [Obsolete("Instead use TextReader as input. Marked obsolete with NLog 6.0")] internal sealed class XmlLoggingConfigurationElement : ILoggingConfigurationElement { /// @@ -55,20 +58,21 @@ public XmlLoggingConfigurationElement(XmlReader reader) public XmlLoggingConfigurationElement(XmlReader reader, bool nestedElement) { - Parse(reader, nestedElement, out var attributes, out var children); - AttributeValues = attributes ?? ArrayHelper.Empty>(); - Children = children ?? ArrayHelper.Empty(); + AttributeValues = ParseAttributes(reader, nestedElement); + LocalName = reader.LocalName; + Children = ParseChildren(reader, nestedElement, out var innerText); + Value = innerText; } /// /// Gets the element name. /// - public string LocalName { get; private set; } + public string LocalName { get; } /// /// Gets the dictionary of attribute values. /// - public IList> AttributeValues { get; } + public IList> AttributeValues { get; } /// /// Gets the collection of child elements. @@ -78,11 +82,11 @@ public XmlLoggingConfigurationElement(XmlReader reader, bool nestedElement) /// /// Gets the value of the element. /// - public string Value { get; private set; } + public string? Value { get; } public string Name => LocalName; - public IEnumerable> Values + public IEnumerable> Values { get { @@ -92,7 +96,7 @@ public IEnumerable> Values if (SingleValueElement(child)) { // Values assigned using nested node-elements. Maybe in combination with attributes - return AttributeValues.Concat(Children.Where(item => SingleValueElement(item)).Select(item => new KeyValuePair(item.Name, item.Value))); + return AttributeValues.Concat(Children.Where(item => SingleValueElement(item)).Select(item => new KeyValuePair(item.Name, item.Value ?? string.Empty))); } } return AttributeValues; @@ -120,22 +124,6 @@ IEnumerable ILoggingConfigurationElement.Children } } - /// - /// Returns children elements with the specified element name. - /// - /// Name of the element. - /// Children elements with the specified element name. - public IEnumerable FilterChildren(string elementName) - { - foreach (var childElement in Children) - { - if (childElement.LocalName.Equals(elementName, StringComparison.OrdinalIgnoreCase)) - { - yield return childElement; - } - } - } - /// /// Asserts that the name of the element is among specified element names. /// @@ -153,13 +141,11 @@ public void AssertName(params string[] allowedNames) throw new InvalidOperationException($"Assertion failed. Expected element name '{string.Join("|", allowedNames)}', actual: '{LocalName}'."); } - private void Parse(XmlReader reader, bool nestedElement, out IList> attributes, out IList children) + private static IList ParseChildren(XmlReader reader, bool nestedElement, out string? innerText) { - ParseAttributes(reader, nestedElement, out attributes); - - LocalName = reader.LocalName; + IList? children = null; - children = null; + innerText = null; if (!reader.IsEmptyElement) { @@ -172,7 +158,7 @@ private void Parse(XmlReader reader, bool nestedElement, out IList(); } - private static void ParseAttributes(XmlReader reader, bool nestedElement, out IList> attributes) + private static IList> ParseAttributes(XmlReader reader, bool nestedElement) { - attributes = null; + IList>? attributes = null; if (reader.MoveToFirstAttribute()) { do @@ -198,12 +186,14 @@ private static void ParseAttributes(XmlReader reader, bool nestedElement, out IL continue; } - attributes = attributes ?? new List>(); - attributes.Add(new KeyValuePair(reader.LocalName, reader.Value)); + attributes = attributes ?? new List>(); + attributes.Add(new KeyValuePair(reader.LocalName, reader.Value)); } while (reader.MoveToNextAttribute()); reader.MoveToElement(); } + + return attributes ?? ArrayHelper.Empty>(); } /// @@ -228,3 +218,5 @@ public override string ToString() } } } + +#endif diff --git a/src/NLog/Config/XmlParserConfigurationElement.cs b/src/NLog/Config/XmlParserConfigurationElement.cs new file mode 100644 index 0000000000..1f69e0a9b8 --- /dev/null +++ b/src/NLog/Config/XmlParserConfigurationElement.cs @@ -0,0 +1,179 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Config +{ + using System; + using System.Collections.Generic; + using System.Linq; + using NLog.Internal; + + internal sealed class XmlParserConfigurationElement : ILoggingConfigurationElement + { + /// + /// Gets the element name. + /// + public string Name { get; private set; } + + /// + /// Gets the value of the element. + /// + public string? Value { get; private set; } + + /// + /// Gets the dictionary of attribute values. + /// + public IList> AttributeValues { get; } + + /// + /// Gets the collection of child elements. + /// + public IList Children { get; } + + public IEnumerable> Values + { + get + { + for (int i = 0; i < Children.Count; ++i) + { + var child = Children[i]; + if (SingleValueElement(child)) + { + // Values assigned using nested node-elements. Maybe in combination with attributes + return AttributeValues.Concat(Children.Where(item => SingleValueElement(item)).Select(item => new KeyValuePair(item.Name, item.Value ?? string.Empty))); + } + } + return AttributeValues; + } + } + + IEnumerable ILoggingConfigurationElement.Children + { + get + { + for (int i = 0; i < Children.Count; ++i) + { + var child = Children[i]; + if (!SingleValueElement(child)) + return Children.Where(item => !SingleValueElement(item)).Cast(); + } + + return ArrayHelper.Empty(); + } + } + + public XmlParserConfigurationElement(XmlParser.XmlParserElement xmlElement) + : this(xmlElement, false) + { + } + + public XmlParserConfigurationElement(XmlParser.XmlParserElement xmlElement, bool nestedElement) + { + var namePrefixIndex = xmlElement.Name.IndexOf(':'); + Name = namePrefixIndex >= 0 ? xmlElement.Name.Substring(namePrefixIndex + 1) : xmlElement.Name; + Value = xmlElement.InnerText; +#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type. + AttributeValues = ParseAttributes(xmlElement, nestedElement); +#pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type. + Children = ParseChildren(xmlElement, nestedElement); + } + + private static bool SingleValueElement(XmlParserConfigurationElement child) + { + // Node-element that works like an attribute + return child.Children.Count == 0 && child.AttributeValues.Count == 0 && child.Value != null; + } + + private static IList> ParseAttributes(XmlParser.XmlParserElement xmlElement, bool nestedElement) + { + var attributes = xmlElement.Attributes; + if (attributes?.Count > 0) + { + if (!nestedElement) + { + for (int i = attributes.Count - 1; i >= 0; --i) + { + var attributeName = attributes[i].Key; + if (IsSpecialXmlRootAttribute(attributeName)) + { + attributes.RemoveAt(i); + } + } + } + + for (int j = 0; j < attributes.Count; ++j) + { + var attributePrefixIndex = attributes[j].Key.IndexOf(':'); + if (attributePrefixIndex >= 0) + attributes[j] = new KeyValuePair(attributes[j].Key.Substring(attributePrefixIndex + 1), attributes[j].Value); + } + } + + return attributes ?? ArrayHelper.Empty>(); + } + + private static IList ParseChildren(XmlParser.XmlParserElement xmlElement, bool nestedElement) + { + var childElements = xmlElement.Children; + if (childElements is null || childElements.Count == 0) + return ArrayHelper.Empty(); + + var children = new List(); + for (int i = 0; i < childElements.Count; ++i) + { + var child = childElements[i]; + var nestedChild = nestedElement || !string.Equals(child.Name, "nlog", StringComparison.OrdinalIgnoreCase); + children.Add(new XmlParserConfigurationElement(child, nestedChild)); + } + return children; + } + + /// + /// Special attribute we could ignore + /// + private static bool IsSpecialXmlRootAttribute(string attributeName) + { + if (attributeName?.StartsWith("xmlns", StringComparison.OrdinalIgnoreCase) == true) + return true; + if (attributeName?.IndexOf(":xmlns", StringComparison.OrdinalIgnoreCase) >= 0) + return true; + if (attributeName?.StartsWith("schemaLocation", StringComparison.OrdinalIgnoreCase) == true) + return true; + if (attributeName?.IndexOf(":schemaLocation", StringComparison.OrdinalIgnoreCase) >= 0) + return true; + if (attributeName?.StartsWith("xsi:", StringComparison.OrdinalIgnoreCase) == true) + return true; + return false; + } + } +} diff --git a/src/NLog/Config/AssemblyLoadingEventArgs.cs b/src/NLog/Config/XmlParserException.cs similarity index 66% rename from src/NLog/Config/AssemblyLoadingEventArgs.cs rename to src/NLog/Config/XmlParserException.cs index e181e573d4..10c2384542 100644 --- a/src/NLog/Config/AssemblyLoadingEventArgs.cs +++ b/src/NLog/Config/XmlParserException.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen // // All rights reserved. @@ -31,31 +31,39 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.ComponentModel; -using System.Reflection; - namespace NLog.Config { + using System; + /// - /// Obsolete since dynamic assembly loading is not compatible with publish as trimmed application. - /// Event notification about trying to load assembly with NLog extensions. + /// Exception thrown during XML parsing /// - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - public class AssemblyLoadingEventArgs : CancelEventArgs + public sealed class XmlParserException : NLogConfigurationException { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. + /// + public XmlParserException() + { + } + + /// + /// Initializes a new instance of the class. /// - /// Assembly that have been loaded - public AssemblyLoadingEventArgs(Assembly assembly) + /// The message. + public XmlParserException(string message) + : base(message) { - Assembly = assembly; } /// - /// The assembly that is trying to load. + /// Initializes a new instance of the class. /// - public Assembly Assembly { get; } + /// The message. + /// The inner exception. + public XmlParserException(string message, Exception? innerException) + : base(message, innerException) + { + } } } diff --git a/src/NLog/Filters/ConditionBasedFilter.cs b/src/NLog/Filters/ConditionBasedFilter.cs index 655037d169..1a04215001 100644 --- a/src/NLog/Filters/ConditionBasedFilter.cs +++ b/src/NLog/Filters/ConditionBasedFilter.cs @@ -34,7 +34,6 @@ namespace NLog.Filters { using NLog.Conditions; - using NLog.Config; /// /// Matches when the specified condition is met. @@ -46,19 +45,20 @@ namespace NLog.Filters [Filter("when")] public class ConditionBasedFilter : Filter { + internal static readonly ConditionBasedFilter Empty = new ConditionBasedFilter(); + /// /// Gets or sets the condition expression. /// /// - [RequiredParameter] - public ConditionExpression Condition { get; set; } + public ConditionExpression Condition { get; set; } = ConditionLiteralExpression.Null; internal FilterResult FilterDefaultAction { get; set; } = FilterResult.Neutral; /// protected override FilterResult Check(LogEventInfo logEvent) { - object val = Condition.Evaluate(logEvent); + var val = Condition?.Evaluate(logEvent); return ConditionExpression.BoxedTrue.Equals(val) ? Action : FilterDefaultAction; } } diff --git a/src/NLog/Filters/FilterAttribute.cs b/src/NLog/Filters/FilterAttribute.cs index 3f119c606a..c0e3b74e1e 100644 --- a/src/NLog/Filters/FilterAttribute.cs +++ b/src/NLog/Filters/FilterAttribute.cs @@ -34,7 +34,7 @@ namespace NLog.Filters { using System; - using Config; + using NLog.Config; /// /// Marks class as a layout renderer and assigns a name to it. diff --git a/src/NLog/Filters/LayoutBasedFilter.cs b/src/NLog/Filters/LayoutBasedFilter.cs index 85aaeada8c..bc047f5952 100644 --- a/src/NLog/Filters/LayoutBasedFilter.cs +++ b/src/NLog/Filters/LayoutBasedFilter.cs @@ -33,8 +33,7 @@ namespace NLog.Filters { - using Config; - using Layouts; + using NLog.Layouts; /// /// A base class for filters that are based on comparing a value to a layout. @@ -53,7 +52,6 @@ protected LayoutBasedFilter() /// /// The layout. /// - [RequiredParameter] - public Layout Layout { get; set; } + public Layout Layout { get; set; } = Layout.Empty; } } diff --git a/src/NLog/Filters/WhenContainsFilter.cs b/src/NLog/Filters/WhenContainsFilter.cs index 0f6fb69bdc..e5135c6a66 100644 --- a/src/NLog/Filters/WhenContainsFilter.cs +++ b/src/NLog/Filters/WhenContainsFilter.cs @@ -34,7 +34,6 @@ namespace NLog.Filters { using System; - using NLog.Config; /// /// Matches when the calculated layout contains the specified substring. @@ -53,8 +52,7 @@ public class WhenContainsFilter : LayoutBasedFilter /// Gets or sets the substring to be matched. /// /// - [RequiredParameter] - public string Substring { get; set; } + public string Substring { get; set; } = string.Empty; /// protected override FilterResult Check(LogEventInfo logEvent) @@ -62,8 +60,8 @@ protected override FilterResult Check(LogEventInfo logEvent) StringComparison comparisonType = IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - if (Layout.Render(logEvent).IndexOf(Substring, comparisonType) >= 0) + string result = Layout.Render(logEvent); + if (!string.IsNullOrEmpty(Substring) && result.IndexOf(Substring, comparisonType) >= 0) { return Action; } diff --git a/src/NLog/Filters/WhenEqualFilter.cs b/src/NLog/Filters/WhenEqualFilter.cs index e78eeabfbf..e8a66cb08c 100644 --- a/src/NLog/Filters/WhenEqualFilter.cs +++ b/src/NLog/Filters/WhenEqualFilter.cs @@ -34,7 +34,6 @@ namespace NLog.Filters { using System; - using NLog.Config; /// /// Matches when the calculated layout is equal to the specified substring. @@ -53,8 +52,7 @@ public class WhenEqualFilter : LayoutBasedFilter /// Gets or sets a string to compare the layout to. /// /// - [RequiredParameter] - public string CompareTo { get; set; } + public string CompareTo { get; set; } = string.Empty; /// protected override FilterResult Check(LogEventInfo logEvent) @@ -62,8 +60,8 @@ protected override FilterResult Check(LogEventInfo logEvent) StringComparison comparisonType = IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - if (Layout.Render(logEvent).Equals(CompareTo, comparisonType)) + string result = Layout.Render(logEvent); + if (result.Equals(CompareTo, comparisonType)) { return Action; } diff --git a/src/NLog/Filters/WhenNotContainsFilter.cs b/src/NLog/Filters/WhenNotContainsFilter.cs index ac60f613b0..53a4d54d85 100644 --- a/src/NLog/Filters/WhenNotContainsFilter.cs +++ b/src/NLog/Filters/WhenNotContainsFilter.cs @@ -34,7 +34,6 @@ namespace NLog.Filters { using System; - using NLog.Config; /// /// Matches when the calculated layout does NOT contain the specified substring. @@ -47,8 +46,7 @@ public class WhenNotContainsFilter : LayoutBasedFilter /// Gets or sets the substring to be matched. /// /// - [RequiredParameter] - public string Substring { get; set; } + public string Substring { get; set; } = string.Empty; /// /// Gets or sets a value indicating whether to ignore case when comparing strings. @@ -63,8 +61,7 @@ protected override FilterResult Check(LogEventInfo logEvent) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; string result = Layout.Render(logEvent); - - if (result.IndexOf(Substring, comparison) < 0) + if (!string.IsNullOrEmpty(Substring) && result.IndexOf(Substring, comparison) < 0) { return Action; } diff --git a/src/NLog/Filters/WhenNotEqualFilter.cs b/src/NLog/Filters/WhenNotEqualFilter.cs index c237f16c65..3abd7818dc 100644 --- a/src/NLog/Filters/WhenNotEqualFilter.cs +++ b/src/NLog/Filters/WhenNotEqualFilter.cs @@ -34,7 +34,6 @@ namespace NLog.Filters { using System; - using NLog.Config; /// /// Matches when the calculated layout is NOT equal to the specified substring. @@ -54,8 +53,7 @@ public WhenNotEqualFilter() /// Gets or sets a string to compare the layout to. /// /// - [RequiredParameter] - public string CompareTo { get; set; } + public string CompareTo { get; set; } = string.Empty; /// /// Gets or sets a value indicating whether to ignore case when comparing strings. @@ -69,8 +67,8 @@ protected override FilterResult Check(LogEventInfo logEvent) StringComparison comparisonType = IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - if (!Layout.Render(logEvent).Equals(CompareTo, comparisonType)) + string result = Layout.Render(logEvent); + if (!result.Equals(CompareTo, comparisonType)) { return Action; } diff --git a/src/NLog/Filters/WhenRepeatedFilter.cs b/src/NLog/Filters/WhenRepeatedFilter.cs index ffc25cef4f..7247a1d3f3 100644 --- a/src/NLog/Filters/WhenRepeatedFilter.cs +++ b/src/NLog/Filters/WhenRepeatedFilter.cs @@ -86,13 +86,13 @@ public class WhenRepeatedFilter : LayoutBasedFilter /// Insert FilterCount value into when an event is no longer filtered /// /// - public string FilterCountPropertyName { get; set; } + public string FilterCountPropertyName { get; set; } = string.Empty; /// /// Append FilterCount to the when an event is no longer filtered /// /// - public string FilterCountMessageAppendFormat { get; set; } + public string FilterCountMessageAppendFormat { get; set; } = string.Empty; /// /// Reuse internal buffers, and doesn't have to constantly allocate new buffers @@ -266,7 +266,7 @@ private FilterInfoKey RenderFilterInfoKey(LogEventInfo logEvent, StringBuilder t /// private FilterResult RefreshFilterInfo(LogEventInfo logEvent, FilterInfo filterInfo) { - if (filterInfo.HasExpired(logEvent.TimeStamp, TimeoutSeconds) || logEvent.Level.Ordinal > filterInfo.LogLevel.Ordinal) + if (filterInfo.HasExpired(logEvent.TimeStamp, TimeoutSeconds) || logEvent.Level > filterInfo.LogLevel) { int filterCount = filterInfo.FilterCount; if (filterCount > 0 && filterInfo.IsObsolete(logEvent.TimeStamp, TimeoutSeconds)) @@ -342,7 +342,7 @@ public bool HasExpired(DateTime logEventTime, int timeoutSeconds) } public StringBuilder StringBuffer { get; } - public LogLevel LogLevel { get; private set; } + public LogLevel? LogLevel { get; private set; } private DateTime LastLogTime { get; set; } private DateTime LastFilterTime { get; set; } public int FilterCount { get; private set; } @@ -351,13 +351,17 @@ public bool HasExpired(DateTime logEventTime, int timeoutSeconds) /// /// Filter Lookup Key (immutable) /// - private struct FilterInfoKey : IEquatable + private +#if !NETFRAMEWORK + readonly +#endif + struct FilterInfoKey : IEquatable { - private readonly StringBuilder _stringBuffer; - public readonly string StringValue; + private readonly StringBuilder? _stringBuffer; + public readonly string? StringValue; public readonly int StringHashCode; - public FilterInfoKey(StringBuilder stringBuffer, string stringValue, int? stringHashCode = null) + public FilterInfoKey(StringBuilder? stringBuffer, string? stringValue, int? stringHashCode = null) { _stringBuffer = stringBuffer; StringValue = stringValue; diff --git a/src/NLog/Fluent/Log.cs b/src/NLog/Fluent/Log.cs deleted file mode 100644 index 07e2a14450..0000000000 --- a/src/NLog/Fluent/Log.cs +++ /dev/null @@ -1,179 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -#if !NET35 && !NET40 - -namespace NLog.Fluent -{ - using System; - using System.ComponentModel; - using System.IO; - using System.Runtime.CompilerServices; - using NLog.Common; - - /// - /// A global logging class using caller info to find the logger. - /// - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.3")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static class Log - { - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - private static readonly ILogger _logger = LogManager.GetCurrentClassLogger(); - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event with the specified . - /// - /// The log level. - /// The full path of the source file that contains the caller. This is the file path at the time of compile. - /// An instance of the fluent . - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Level(LogLevel logLevel, [CallerFilePath] string callerFilePath = null) - { - return Create(logLevel, callerFilePath); - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Trace level. - /// - /// The full path of the source file that contains the caller. This is the file path at the time of compile. - /// An instance of the fluent . - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Trace([CallerFilePath] string callerFilePath = null) - { - return Create(LogLevel.Trace, callerFilePath); - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Debug level. - /// - /// The full path of the source file that contains the caller. This is the file path at the time of compile. - /// An instance of the fluent . - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Debug([CallerFilePath] string callerFilePath = null) - { - return Create(LogLevel.Debug, callerFilePath); - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Info level. - /// - /// The full path of the source file that contains the caller. This is the file path at the time of compile. - /// An instance of the fluent . - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Info([CallerFilePath] string callerFilePath = null) - { - return Create(LogLevel.Info, callerFilePath); - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Warn level. - /// - /// The full path of the source file that contains the caller. This is the file path at the time of compile. - /// An instance of the fluent . - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Warn([CallerFilePath] string callerFilePath = null) - { - return Create(LogLevel.Warn, callerFilePath); - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Error level. - /// - /// The full path of the source file that contains the caller. This is the file path at the time of compile. - /// An instance of the fluent . - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Error([CallerFilePath] string callerFilePath = null) - { - return Create(LogLevel.Error, callerFilePath); - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Fatal level. - /// - /// The full path of the source file that contains the caller. This is the file path at the time of compile. - /// An instance of the fluent . - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Fatal([CallerFilePath] string callerFilePath = null) - { - return Create(LogLevel.Fatal, callerFilePath); - } - - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - private static LogBuilder Create(LogLevel logLevel, string callerFilePath) - { - var logger = GetLogger(callerFilePath); - var builder = new LogBuilder(logger, logLevel); - return builder; - } - - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - private static ILogger GetLogger(string callerFilePath) - { - try - { - string name = !string.IsNullOrWhiteSpace(callerFilePath) ? Path.GetFileNameWithoutExtension(callerFilePath) : null; - return string.IsNullOrWhiteSpace(name) ? _logger : LogManager.GetLogger(name); - } - catch (Exception ex) - { - InternalLogger.Warn(ex, "Error when converting CallerFilePath to logger name."); - return _logger; - } - } - } -} - -#endif diff --git a/src/NLog/Fluent/LogBuilder.cs b/src/NLog/Fluent/LogBuilder.cs deleted file mode 100644 index a6bbe8dcb6..0000000000 --- a/src/NLog/Fluent/LogBuilder.cs +++ /dev/null @@ -1,389 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -using System; -using System.Collections; -using System.ComponentModel; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using JetBrains.Annotations; -using NLog.Internal; - -namespace NLog.Fluent -{ - /// - /// Obsolete and replaced by with NLog v5. - /// - /// A fluent class to build log events for NLog. - /// - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class LogBuilder - { - private readonly LogEventInfo _logEvent; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// The to send the log event. - [CLSCompliant(false)] - public LogBuilder(ILogger logger) - : this(logger, LogLevel.Debug) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The to send the log event. - /// The for the log event. - [CLSCompliant(false)] - public LogBuilder(ILogger logger, LogLevel logLevel) - { - Guard.ThrowIfNull(logger); - Guard.ThrowIfNull(logLevel); - - _logger = logger; - _logEvent = new LogEventInfo() { LoggerName = logger.Name, Level = logLevel }; - } - - /// - /// Gets the created by the builder. - /// - public LogEventInfo LogEventInfo => _logEvent; - - /// - /// Sets the information of the logging event. - /// - /// The exception information of the logging event. - /// current for chaining calls. - public LogBuilder Exception(Exception exception) - { - _logEvent.Exception = exception; - return this; - } - - /// - /// Sets the level of the logging event. - /// - /// The level of the logging event. - /// current for chaining calls. - public LogBuilder Level(LogLevel logLevel) - { - Guard.ThrowIfNull(logLevel); - - _logEvent.Level = logLevel; - return this; - } - - /// - /// Sets the logger name of the logging event. - /// - /// The logger name of the logging event. - /// current for chaining calls. - public LogBuilder LoggerName([Localizable(false)] string loggerName) - { - _logEvent.LoggerName = loggerName; - return this; - } - - /// - /// Sets the log message on the logging event. - /// - /// The log message for the logging event. - /// current for chaining calls. - public LogBuilder Message([Localizable(false)] string message) - { - _logEvent.Message = message; - - return this; - } - - /// - /// Sets the log message and parameters for formatting on the logging event. - /// - /// A composite format string. - /// The object to format. - /// current for chaining calls. - [MessageTemplateFormatMethod("format")] - public LogBuilder Message([Localizable(false)][StructuredMessageTemplate] string format, object arg0) - { - _logEvent.Message = format; - _logEvent.Parameters = new[] { arg0 }; - - return this; - } - - /// - /// Sets the log message and parameters for formatting on the logging event. - /// - /// A composite format string. - /// The first object to format. - /// The second object to format. - /// current for chaining calls. - [MessageTemplateFormatMethod("format")] - public LogBuilder Message([Localizable(false)][StructuredMessageTemplate] string format, object arg0, object arg1) - { - _logEvent.Message = format; - _logEvent.Parameters = new[] { arg0, arg1 }; - - return this; - } - - /// - /// Sets the log message and parameters for formatting on the logging event. - /// - /// A composite format string. - /// The first object to format. - /// The second object to format. - /// The third object to format. - /// current for chaining calls. - [MessageTemplateFormatMethod("format")] - public LogBuilder Message([Localizable(false)][StructuredMessageTemplate] string format, object arg0, object arg1, object arg2) - { - _logEvent.Message = format; - _logEvent.Parameters = new[] { arg0, arg1, arg2 }; - - return this; - } - - /// - /// Sets the log message and parameters for formatting on the logging event. - /// - /// A composite format string. - /// The first object to format. - /// The second object to format. - /// The third object to format. - /// The fourth object to format. - /// current for chaining calls. - [MessageTemplateFormatMethod("format")] - public LogBuilder Message([Localizable(false)][StructuredMessageTemplate] string format, object arg0, object arg1, object arg2, object arg3) - { - _logEvent.Message = format; - _logEvent.Parameters = new[] { arg0, arg1, arg2, arg3 }; - - return this; - } - - /// - /// Sets the log message and parameters for formatting on the logging event. - /// - /// A composite format string. - /// An object array that contains zero or more objects to format. - /// current for chaining calls. - [MessageTemplateFormatMethod("format")] - public LogBuilder Message([Localizable(false)][StructuredMessageTemplate] string format, params object[] args) - { - _logEvent.Message = format; - _logEvent.Parameters = args; - - return this; - } - - /// - /// Sets the log message and parameters for formatting on the logging event. - /// - /// An object that supplies culture-specific formatting information. - /// A composite format string. - /// An object array that contains zero or more objects to format. - /// current for chaining calls. - [MessageTemplateFormatMethod("format")] - public LogBuilder Message(IFormatProvider provider, [Localizable(false)][StructuredMessageTemplate] string format, params object[] args) - { - _logEvent.FormatProvider = provider; - _logEvent.Message = format; - _logEvent.Parameters = args; - - return this; - } - - /// - /// Sets a per-event context property on the logging event. - /// - /// The name of the context property. - /// The value of the context property. - /// current for chaining calls. - public LogBuilder Property(object name, object value) - { - Guard.ThrowIfNull(name); - - _logEvent.Properties[name] = value; - return this; - } - - /// - /// Sets multiple per-event context properties on the logging event. - /// - /// The properties to set. - /// current for chaining calls. - public LogBuilder Properties(IDictionary properties) - { - Guard.ThrowIfNull(properties); - - foreach (var key in properties.Keys) - { - _logEvent.Properties[key] = properties[key]; - } - return this; - } - - /// - /// Sets the timestamp of the logging event. - /// - /// The timestamp of the logging event. - /// current for chaining calls. - public LogBuilder TimeStamp(DateTime timeStamp) - { - _logEvent.TimeStamp = timeStamp; - return this; - } - - /// - /// Sets the stack trace for the event info. - /// - /// The stack trace. - /// Index of the first user stack frame within the stack trace. - /// current for chaining calls. - public LogBuilder StackTrace(StackTrace stackTrace, int userStackFrame) - { - _logEvent.SetStackTrace(stackTrace, userStackFrame); - return this; - } - - /// - /// Writes the log event to the underlying logger. - /// - /// The method or property name of the caller to the method. This is set at by the compiler. - /// The full path of the source file that contains the caller. This is set at by the compiler. - /// The line number in the source file at which the method is called. This is set at by the compiler. -#if !NET35 && !NET40 - public void Write( - [CallerMemberName] string callerMemberName = null, - [CallerFilePath] string callerFilePath = null, - [CallerLineNumber] int callerLineNumber = 0) - { - if (!_logger.IsEnabled(_logEvent.Level)) - return; - - SetCallerInfo(callerMemberName, callerFilePath, callerLineNumber); - - _logger.Log(_logEvent); - } -#else - public void Write( - string callerMemberName = null, - string callerFilePath = null, - int callerLineNumber = 0) - { - _logger.Log(_logEvent); - } -#endif - - /// - /// Writes the log event to the underlying logger if the condition delegate is true. - /// - /// If condition is true, write log event; otherwise ignore event. - /// The method or property name of the caller to the method. This is set at by the compiler. - /// The full path of the source file that contains the caller. This is set at by the compiler. - /// The line number in the source file at which the method is called. This is set at by the compiler. -#if !NET35 && !NET40 - public void WriteIf( - Func condition, - [CallerMemberName] string callerMemberName = null, - [CallerFilePath] string callerFilePath = null, - [CallerLineNumber] int callerLineNumber = 0) - { - if (condition is null || !condition() || !_logger.IsEnabled(_logEvent.Level)) - return; - - SetCallerInfo(callerMemberName, callerFilePath, callerLineNumber); - - _logger.Log(_logEvent); - } -#else - public void WriteIf( - Func condition, - string callerMemberName = null, - string callerFilePath = null, - int callerLineNumber = 0) - { - if (condition is null || !condition() || !_logger.IsEnabled(_logEvent.Level)) - return; - - _logger.Log(_logEvent); - } -#endif - - /// - /// Writes the log event to the underlying logger if the condition is true. - /// - /// If condition is true, write log event; otherwise ignore event. - /// The method or property name of the caller to the method. This is set at by the compiler. - /// The full path of the source file that contains the caller. This is set at by the compiler. - /// The line number in the source file at which the method is called. This is set at by the compiler. -#if !NET35 && !NET40 - public void WriteIf( - bool condition, - [CallerMemberName] string callerMemberName = null, - [CallerFilePath] string callerFilePath = null, - [CallerLineNumber] int callerLineNumber = 0) - { - if (!condition || !_logger.IsEnabled(_logEvent.Level)) - return; - - SetCallerInfo(callerMemberName, callerFilePath, callerLineNumber); - - _logger.Log(_logEvent); - } -#else - public void WriteIf( - bool condition, - string callerMemberName = null, - string callerFilePath = null, - int callerLineNumber = 0) - { - if (!condition || !_logger.IsEnabled(_logEvent.Level)) - return; - - _logger.Log(_logEvent); - } -#endif - - private void SetCallerInfo(string callerMethodName, string callerFilePath, int callerLineNumber) - { - if (callerMethodName != null || callerFilePath != null || callerLineNumber != 0) - _logEvent.SetCallerInfo(null, callerMethodName, callerFilePath, callerLineNumber); - } - } -} diff --git a/src/NLog/Fluent/LoggerExtensions.cs b/src/NLog/Fluent/LoggerExtensions.cs deleted file mode 100644 index 61faad7bdd..0000000000 --- a/src/NLog/Fluent/LoggerExtensions.cs +++ /dev/null @@ -1,156 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// -using System; -using System.ComponentModel; - -namespace NLog.Fluent -{ - /// - /// Extension methods for NLog . - /// - public static class LoggerExtensions - { - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event with the specified . - /// - /// The logger to write the log event to. - /// The log level. - /// current for chaining calls. - [CLSCompliant(false)] - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Log(this ILogger logger, LogLevel logLevel) - { - var builder = new LogBuilder(logger, logLevel); - return builder; - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Trace level. - /// - /// The logger to write the log event to. - /// current for chaining calls. - [CLSCompliant(false)] - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForTraceEvent() and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Trace(this ILogger logger) - { - var builder = new LogBuilder(logger, LogLevel.Trace); - return builder; - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Debug level. - /// - /// The logger to write the log event to. - /// current for chaining calls. - [CLSCompliant(false)] - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForDebugEvent() and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Debug(this ILogger logger) - { - var builder = new LogBuilder(logger, LogLevel.Debug); - return builder; - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Info level. - /// - /// The logger to write the log event to. - /// current for chaining calls. - [CLSCompliant(false)] - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForInfoEvent() and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Info(this ILogger logger) - { - var builder = new LogBuilder(logger, LogLevel.Info); - return builder; - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Warn level. - /// - /// The logger to write the log event to. - /// current for chaining calls. - [CLSCompliant(false)] - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForWarnEvent() and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Warn(this ILogger logger) - { - var builder = new LogBuilder(logger, LogLevel.Warn); - return builder; - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Error level. - /// - /// The logger to write the log event to. - /// current for chaining calls. - [CLSCompliant(false)] - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForErrorEvent() and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Error(this ILogger logger) - { - var builder = new LogBuilder(logger, LogLevel.Error); - return builder; - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Starts building a log event at the Fatal level. - /// - /// The logger to write the log event to. - /// current for chaining calls. - [CLSCompliant(false)] - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForFatalEvent() and LogEventBuilder. Obsoleted in NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static LogBuilder Fatal(this ILogger logger) - { - var builder = new LogBuilder(logger, LogLevel.Fatal); - return builder; - } - } -} diff --git a/src/NLog/GlobalDiagnosticsContext.cs b/src/NLog/GlobalDiagnosticsContext.cs index c6027dd250..db3e3db4ed 100644 --- a/src/NLog/GlobalDiagnosticsContext.cs +++ b/src/NLog/GlobalDiagnosticsContext.cs @@ -44,17 +44,17 @@ namespace NLog public static class GlobalDiagnosticsContext { private static readonly object _lockObject = new object(); - private static Dictionary _dict = new Dictionary(StringComparer.OrdinalIgnoreCase); - private static Dictionary _dictReadOnly; // Reset cache on change + private static Dictionary _dict = new Dictionary(StringComparer.OrdinalIgnoreCase); + private static Dictionary? _dictReadOnly; // Reset cache on change /// /// Sets the value with the specified key in the Global Diagnostics Context (GDC) dictionary /// /// Item name. /// Item value. - public static void Set(string item, string value) + public static void Set(string item, string? value) { - Set(item, (object)value); + Set(item, (object?)value); } /// @@ -62,8 +62,9 @@ public static void Set(string item, string value) /// /// Item name. /// Item value. - public static void Set(string item, object value) + public static void Set(string item, object? value) { + Guard.ThrowIfNull(item); lock (_lockObject) { GetWritableDict()[item] = value; @@ -78,6 +79,7 @@ public static void Set(string item, object value) /// If the value isn't a already, this call locks the for reading the needed for converting to . public static string Get(string item) { + Guard.ThrowIfNull(item); return Get(item, null); } @@ -88,8 +90,9 @@ public static string Get(string item) /// to use when converting the item's value to a string. /// The value of as a string, if defined; otherwise . /// If is null and the value isn't a already, this call locks the for reading the needed for converting to . - public static string Get(string item, IFormatProvider formatProvider) + public static string Get(string item, IFormatProvider? formatProvider) { + Guard.ThrowIfNull(item); return FormatHelper.ConvertToString(GetObject(item), formatProvider); } @@ -98,8 +101,9 @@ public static string Get(string item, IFormatProvider formatProvider) /// /// Item name. /// The item value, if defined; otherwise null. - public static object GetObject(string item) + public static object? GetObject(string item) { + Guard.ThrowIfNull(item); GetReadOnlyDict().TryGetValue(item, out var o); return o; } @@ -120,6 +124,7 @@ public static ICollection GetNames() /// A boolean indicating whether the specified item exists in current thread GDC. public static bool Contains(string item) { + Guard.ThrowIfNull(item); return GetReadOnlyDict().ContainsKey(item); } @@ -129,6 +134,7 @@ public static bool Contains(string item) /// Item name. public static void Remove(string item) { + Guard.ThrowIfNull(item); lock (_lockObject) { if (_dict.ContainsKey(item)) @@ -152,7 +158,7 @@ public static void Clear() } } - internal static Dictionary GetReadOnlyDict() + internal static Dictionary GetReadOnlyDict() { var readOnly = _dictReadOnly; if (readOnly is null) @@ -165,20 +171,20 @@ internal static Dictionary GetReadOnlyDict() return readOnly; } - private static Dictionary GetWritableDict(bool clearDictionary = false) + private static Dictionary GetWritableDict(bool clearDictionary = false) { if (_dictReadOnly != null) { - Dictionary newDict = CopyDictionaryOnWrite(clearDictionary); + Dictionary newDict = CopyDictionaryOnWrite(clearDictionary); _dict = newDict; _dictReadOnly = null; } return _dict; } - private static Dictionary CopyDictionaryOnWrite(bool clearDictionary) + private static Dictionary CopyDictionaryOnWrite(bool clearDictionary) { - var newDict = new Dictionary(clearDictionary ? 0 : _dict.Count + 1, _dict.Comparer); + var newDict = new Dictionary(clearDictionary ? 0 : _dict.Count + 1, _dict.Comparer); if (!clearDictionary) { // Less allocation with enumerator than Dictionary-constructor diff --git a/src/NLog/IJsonConverter.cs b/src/NLog/IJsonConverter.cs index e797da985f..5d80f2ce30 100644 --- a/src/NLog/IJsonConverter.cs +++ b/src/NLog/IJsonConverter.cs @@ -44,6 +44,6 @@ public interface IJsonConverter /// The object to serialize to JSON. /// Output destination. /// Serialize succeeded (true/false) - bool SerializeObject(object value, System.Text.StringBuilder builder); + bool SerializeObject(object? value, System.Text.StringBuilder builder); } } diff --git a/src/NLog/ILLink.Descriptors.xml b/src/NLog/ILLink.Descriptors.xml deleted file mode 100644 index 9ebc2155b1..0000000000 --- a/src/NLog/ILLink.Descriptors.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/NLog/ILogger-V1.cs b/src/NLog/ILogger-V1.cs index 111f99a0de..37d53ae708 100644 --- a/src/NLog/ILogger-V1.cs +++ b/src/NLog/ILogger-V1.cs @@ -35,6 +35,7 @@ namespace NLog { using System; using System.ComponentModel; + using System.Runtime.CompilerServices; using JetBrains.Annotations; /// @@ -50,7 +51,10 @@ public partial interface ILogger /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Trace(object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(object? value); /// /// Writes the diagnostic message at the Trace level. @@ -58,7 +62,10 @@ public partial interface ILogger /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Trace(IFormatProvider formatProvider, object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, object? value); /// /// Writes the diagnostic message at the Trace level using the specified parameters. @@ -68,7 +75,10 @@ public partial interface ILogger /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2); /// /// Writes the diagnostic message at the Trace level using the specified parameters. @@ -79,7 +89,10 @@ public partial interface ILogger /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter and formatting it with the supplied format provider. @@ -89,7 +102,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -98,6 +114,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Trace([Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// @@ -108,7 +127,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -117,6 +139,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Trace([Localizable(false)][StructuredMessageTemplate] string message, char argument); /// @@ -127,7 +152,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -136,6 +164,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Trace([Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// @@ -146,7 +177,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -155,7 +189,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace([Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace([Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter and formatting it with the supplied format provider. @@ -165,7 +202,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -174,6 +214,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Trace([Localizable(false)][StructuredMessageTemplate] string message, int argument); /// @@ -184,7 +227,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -193,6 +239,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Trace([Localizable(false)][StructuredMessageTemplate] string message, long argument); /// @@ -203,7 +252,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -212,6 +264,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Trace([Localizable(false)][StructuredMessageTemplate] string message, float argument); /// @@ -222,7 +277,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -231,6 +289,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Trace([Localizable(false)][StructuredMessageTemplate] string message, double argument); /// @@ -241,7 +302,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -250,6 +314,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Trace([Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// @@ -260,7 +327,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -269,7 +339,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace([Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace([Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter and formatting it with the supplied format provider. @@ -279,7 +352,10 @@ public partial interface ILogger /// The argument to format.s [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -288,6 +364,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Trace([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// @@ -298,7 +377,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -307,6 +389,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Trace([Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// @@ -317,7 +402,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); /// /// Writes the diagnostic message at the Trace level using the specified value as a parameter. @@ -326,6 +414,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Trace([Localizable(false)][StructuredMessageTemplate] string message, ulong argument); #endregion @@ -337,7 +428,10 @@ public partial interface ILogger /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Debug(object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(object? value); /// /// Writes the diagnostic message at the Debug level. @@ -345,7 +439,10 @@ public partial interface ILogger /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Debug(IFormatProvider formatProvider, object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, object? value); /// /// Writes the diagnostic message at the Debug level using the specified parameters. @@ -355,7 +452,10 @@ public partial interface ILogger /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2); /// /// Writes the diagnostic message at the Debug level using the specified parameters. @@ -366,7 +466,10 @@ public partial interface ILogger /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter and formatting it with the supplied format provider. @@ -376,7 +479,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -385,6 +491,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Debug([Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// @@ -395,7 +504,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -404,6 +516,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Debug([Localizable(false)][StructuredMessageTemplate] string message, char argument); /// @@ -414,7 +529,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -423,6 +541,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Debug([Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// @@ -433,7 +554,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -442,7 +566,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug([Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug([Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter and formatting it with the supplied format provider. @@ -452,7 +579,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -461,6 +591,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Debug([Localizable(false)][StructuredMessageTemplate] string message, int argument); /// @@ -471,7 +604,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -480,6 +616,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Debug([Localizable(false)][StructuredMessageTemplate] string message, long argument); /// @@ -490,7 +629,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -499,6 +641,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Debug([Localizable(false)][StructuredMessageTemplate] string message, float argument); /// @@ -509,7 +654,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -518,6 +666,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Debug([Localizable(false)][StructuredMessageTemplate] string message, double argument); /// @@ -528,7 +679,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -537,6 +691,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Debug([Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// @@ -547,7 +704,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -556,7 +716,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug([Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug([Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter and formatting it with the supplied format provider. @@ -566,7 +729,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -575,6 +741,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Debug([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// @@ -585,7 +754,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -594,6 +766,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Debug([Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// @@ -604,7 +779,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); /// /// Writes the diagnostic message at the Debug level using the specified value as a parameter. @@ -613,6 +791,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Debug([Localizable(false)][StructuredMessageTemplate] string message, ulong argument); #endregion @@ -624,7 +805,10 @@ public partial interface ILogger /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Info(object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(object? value); /// /// Writes the diagnostic message at the Info level. @@ -632,7 +816,10 @@ public partial interface ILogger /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Info(IFormatProvider formatProvider, object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, object? value); /// /// Writes the diagnostic message at the Info level using the specified parameters. @@ -642,7 +829,10 @@ public partial interface ILogger /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2); /// /// Writes the diagnostic message at the Info level using the specified parameters. @@ -653,7 +843,10 @@ public partial interface ILogger /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter and formatting it with the supplied format provider. @@ -663,7 +856,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -672,6 +868,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Info([Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// @@ -682,7 +881,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -691,6 +893,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Info([Localizable(false)][StructuredMessageTemplate] string message, char argument); /// @@ -701,7 +906,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -710,6 +918,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Info([Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// @@ -720,7 +931,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -729,7 +943,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info([Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info([Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter and formatting it with the supplied format provider. @@ -739,7 +956,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -748,6 +968,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Info([Localizable(false)][StructuredMessageTemplate] string message, int argument); /// @@ -758,7 +981,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -767,6 +993,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Info([Localizable(false)][StructuredMessageTemplate] string message, long argument); /// @@ -777,7 +1006,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -786,6 +1018,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Info([Localizable(false)][StructuredMessageTemplate] string message, float argument); /// @@ -796,7 +1031,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -805,6 +1043,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Info([Localizable(false)][StructuredMessageTemplate] string message, double argument); /// @@ -815,7 +1056,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -824,6 +1068,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Info([Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// @@ -834,7 +1081,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -843,7 +1093,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info([Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info([Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter and formatting it with the supplied format provider. @@ -853,7 +1106,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -862,6 +1118,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Info([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// @@ -872,7 +1131,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -881,6 +1143,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Info([Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// @@ -891,7 +1156,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); /// /// Writes the diagnostic message at the Info level using the specified value as a parameter. @@ -900,6 +1168,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Info([Localizable(false)][StructuredMessageTemplate] string message, ulong argument); #endregion @@ -911,7 +1182,10 @@ public partial interface ILogger /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Warn(object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(object? value); /// /// Writes the diagnostic message at the Warn level. @@ -919,7 +1193,10 @@ public partial interface ILogger /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Warn(IFormatProvider formatProvider, object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, object? value); /// /// Writes the diagnostic message at the Warn level using the specified parameters. @@ -929,7 +1206,10 @@ public partial interface ILogger /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2); /// /// Writes the diagnostic message at the Warn level using the specified parameters. @@ -940,7 +1220,10 @@ public partial interface ILogger /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter and formatting it with the supplied format provider. @@ -950,7 +1233,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -959,6 +1245,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Warn([Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// @@ -969,7 +1258,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -978,6 +1270,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Warn([Localizable(false)][StructuredMessageTemplate] string message, char argument); /// @@ -988,7 +1283,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -997,6 +1295,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Warn([Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// @@ -1007,7 +1308,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -1016,7 +1320,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn([Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn([Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter and formatting it with the supplied format provider. @@ -1026,7 +1333,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -1035,6 +1345,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Warn([Localizable(false)][StructuredMessageTemplate] string message, int argument); /// @@ -1045,7 +1358,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -1054,6 +1370,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Warn([Localizable(false)][StructuredMessageTemplate] string message, long argument); /// @@ -1064,7 +1383,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -1073,6 +1395,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Warn([Localizable(false)][StructuredMessageTemplate] string message, float argument); /// @@ -1083,7 +1408,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -1092,6 +1420,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Warn([Localizable(false)][StructuredMessageTemplate] string message, double argument); /// @@ -1102,7 +1433,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -1111,6 +1445,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Warn([Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// @@ -1121,7 +1458,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -1130,7 +1470,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn([Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn([Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter and formatting it with the supplied format provider. @@ -1140,7 +1483,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -1149,6 +1495,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Warn([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// @@ -1159,7 +1508,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -1168,6 +1520,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Warn([Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// @@ -1178,7 +1533,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); /// /// Writes the diagnostic message at the Warn level using the specified value as a parameter. @@ -1187,6 +1545,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Warn([Localizable(false)][StructuredMessageTemplate] string message, ulong argument); #endregion @@ -1198,7 +1559,10 @@ public partial interface ILogger /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Error(object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(object? value); /// /// Writes the diagnostic message at the Error level. @@ -1206,7 +1570,10 @@ public partial interface ILogger /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Error(IFormatProvider formatProvider, object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, object? value); /// /// Writes the diagnostic message at the Error level using the specified parameters. @@ -1216,7 +1583,10 @@ public partial interface ILogger /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2); /// /// Writes the diagnostic message at the Error level using the specified parameters. @@ -1227,7 +1597,10 @@ public partial interface ILogger /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter and formatting it with the supplied format provider. @@ -1237,7 +1610,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1245,6 +1621,9 @@ public partial interface ILogger /// A containing one format item. /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// @@ -1255,7 +1634,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1264,6 +1646,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, char argument); /// @@ -1274,7 +1659,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1282,6 +1670,9 @@ public partial interface ILogger /// A containing one format item. /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// @@ -1292,7 +1683,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1301,7 +1695,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error([Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error([Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter and formatting it with the supplied format provider. @@ -1311,7 +1708,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1320,6 +1720,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, int argument); /// @@ -1330,7 +1733,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1339,6 +1745,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, long argument); /// @@ -1349,7 +1758,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1357,6 +1769,9 @@ public partial interface ILogger /// A containing one format item. /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, float argument); /// @@ -1367,7 +1782,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1376,6 +1794,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, double argument); /// @@ -1386,7 +1807,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1395,6 +1819,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// @@ -1405,7 +1832,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1414,6 +1844,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, object argument); /// @@ -1424,7 +1857,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1433,6 +1869,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// @@ -1443,7 +1882,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1452,6 +1894,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// @@ -1462,7 +1907,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); /// /// Writes the diagnostic message at the Error level using the specified value as a parameter. @@ -1471,6 +1919,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Error([Localizable(false)][StructuredMessageTemplate] string message, ulong argument); #endregion @@ -1482,7 +1933,10 @@ public partial interface ILogger /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Fatal(object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(object? value); /// /// Writes the diagnostic message at the Fatal level. @@ -1490,7 +1944,10 @@ public partial interface ILogger /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Fatal(IFormatProvider formatProvider, object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, object? value); /// /// Writes the diagnostic message at the Fatal level using the specified parameters. @@ -1500,7 +1957,10 @@ public partial interface ILogger /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2); /// /// Writes the diagnostic message at the Fatal level using the specified parameters. @@ -1511,7 +1971,10 @@ public partial interface ILogger /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter and formatting it with the supplied format provider. @@ -1521,7 +1984,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1530,6 +1996,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Fatal([Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// @@ -1540,7 +2009,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1549,6 +2021,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Fatal([Localizable(false)][StructuredMessageTemplate] string message, char argument); /// @@ -1559,7 +2034,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1568,6 +2046,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Fatal([Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// @@ -1578,7 +2059,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1587,7 +2071,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal([Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal([Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter and formatting it with the supplied format provider. @@ -1597,7 +2084,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1606,6 +2096,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Fatal([Localizable(false)][StructuredMessageTemplate] string message, int argument); /// @@ -1616,7 +2109,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1625,6 +2121,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Fatal([Localizable(false)][StructuredMessageTemplate] string message, long argument); /// @@ -1635,7 +2134,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1644,6 +2146,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Fatal([Localizable(false)][StructuredMessageTemplate] string message, float argument); /// @@ -1654,7 +2159,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1663,6 +2171,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Fatal([Localizable(false)][StructuredMessageTemplate] string message, double argument); /// @@ -1673,7 +2184,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1681,6 +2195,9 @@ public partial interface ILogger /// A containing one format item. /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Fatal([Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// @@ -1691,7 +2208,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1700,7 +2220,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter and formatting it with the supplied format provider. @@ -1710,7 +2233,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1719,6 +2245,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Fatal([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// @@ -1729,7 +2258,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1738,6 +2270,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Fatal([Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// @@ -1748,7 +2283,10 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); /// /// Writes the diagnostic message at the Fatal level using the specified value as a parameter. @@ -1757,6 +2295,9 @@ public partial interface ILogger /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Fatal([Localizable(false)][StructuredMessageTemplate] string message, ulong argument); #endregion diff --git a/src/NLog/ILogger.cs b/src/NLog/ILogger.cs index 0b5f2bfd86..123047642c 100644 --- a/src/NLog/ILogger.cs +++ b/src/NLog/ILogger.cs @@ -110,7 +110,7 @@ bool IsFatalEnabled /// /// Type of the value. /// The value to be written. - void Trace(T value); + void Trace(T? value); /// /// Writes the diagnostic message at the Trace level. @@ -118,7 +118,7 @@ bool IsFatalEnabled /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - void Trace(IFormatProvider formatProvider, T value); + void Trace(IFormatProvider? formatProvider, T? value); /// /// Writes the diagnostic message at the Trace level. @@ -126,22 +126,12 @@ bool IsFatalEnabled /// A function returning message to be written. Function is not evaluated if logging is not enabled. void Trace(LogMessageGenerator messageFunc); - /// - /// Obsolete and replaced by - Writes the diagnostic message and exception at the Trace level. - /// - /// A to be written. - /// An exception to be logged. - /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. - [Obsolete("Use Trace(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] - [EditorBrowsable(EditorBrowsableState.Never)] - void TraceException([Localizable(false)] string message, Exception exception); - /// /// Writes the diagnostic message and exception at the Trace level. /// /// A to be written. /// An exception to be logged. - void Trace(Exception exception, [Localizable(false)] string message); + void Trace(Exception? exception, [Localizable(false)] string message); /// /// Writes the diagnostic message and exception at the Trace level. @@ -150,7 +140,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Trace(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Trace(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message and exception at the Trace level. @@ -160,7 +150,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Trace(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Trace(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Trace level using the specified parameters and formatting them with the supplied format provider. @@ -169,7 +159,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Trace level. @@ -183,7 +173,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Trace([Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Trace([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the Trace level. @@ -193,7 +183,7 @@ bool IsFatalEnabled /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Trace(Exception exception, string message) method instead. Marked obsolete with v4.3.11")] [EditorBrowsable(EditorBrowsableState.Never)] - void Trace([Localizable(false)] string message, Exception exception); + void Trace([Localizable(false)] string message, Exception? exception); /// /// Writes the diagnostic message at the Trace level using the specified parameter and formatting it with the supplied format provider. @@ -203,7 +193,7 @@ bool IsFatalEnabled /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the Trace level using the specified parameter. @@ -212,7 +202,7 @@ bool IsFatalEnabled /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the Trace level using the specified arguments formatting it with the supplied format provider. @@ -224,7 +214,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Trace level using the specified parameters. @@ -235,7 +225,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Trace level using the specified arguments formatting it with the supplied format provider. @@ -249,7 +239,7 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Writes the diagnostic message at the Trace level using the specified parameters. @@ -262,7 +252,17 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); + + /// + /// Obsolete and replaced by - Writes the diagnostic message and exception at the Trace level. + /// + /// A to be written. + /// An exception to be logged. + /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. + [Obsolete("Use Trace(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] + [EditorBrowsable(EditorBrowsableState.Never)] + void TraceException([Localizable(false)] string message, Exception? exception); #endregion @@ -276,7 +276,7 @@ bool IsFatalEnabled /// /// Type of the value. /// The value to be written. - void Debug(T value); + void Debug(T? value); /// /// Writes the diagnostic message at the Debug level. @@ -284,7 +284,7 @@ bool IsFatalEnabled /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - void Debug(IFormatProvider formatProvider, T value); + void Debug(IFormatProvider? formatProvider, T? value); /// /// Writes the diagnostic message at the Debug level. @@ -297,7 +297,7 @@ bool IsFatalEnabled /// /// A to be written. /// An exception to be logged. - void Debug(Exception exception, [Localizable(false)] string message); + void Debug(Exception? exception, [Localizable(false)] string message); /// /// Writes the diagnostic message and exception at the Debug level. @@ -306,7 +306,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Debug(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Debug(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message and exception at the Debug level. @@ -316,7 +316,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Debug(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Debug(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Debug level using the specified parameters and formatting them with the supplied format provider. @@ -325,7 +325,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Debug level. @@ -339,7 +339,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Debug([Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Debug([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Debug level using the specified parameter and formatting it with the supplied format provider. @@ -349,7 +349,7 @@ bool IsFatalEnabled /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the Debug level using the specified parameter. @@ -358,7 +358,7 @@ bool IsFatalEnabled /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the Debug level using the specified arguments formatting it with the supplied format provider. @@ -370,7 +370,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Debug level using the specified parameters. @@ -381,7 +381,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Debug level using the specified arguments formatting it with the supplied format provider. @@ -395,7 +395,7 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Writes the diagnostic message at the Debug level using the specified parameters. @@ -408,7 +408,7 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the Debug level. @@ -428,7 +428,7 @@ bool IsFatalEnabled /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Debug(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] [EditorBrowsable(EditorBrowsableState.Never)] - void DebugException([Localizable(false)] string message, Exception exception); + void DebugException([Localizable(false)] string message, Exception? exception); #endregion @@ -442,7 +442,7 @@ bool IsFatalEnabled /// /// Type of the value. /// The value to be written. - void Info(T value); + void Info(T? value); /// /// Writes the diagnostic message at the Info level. @@ -450,7 +450,7 @@ bool IsFatalEnabled /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - void Info(IFormatProvider formatProvider, T value); + void Info(IFormatProvider? formatProvider, T? value); /// /// Writes the diagnostic message at the Info level. @@ -463,7 +463,7 @@ bool IsFatalEnabled /// /// A to be written. /// An exception to be logged. - void Info(Exception exception, [Localizable(false)] string message); + void Info(Exception? exception, [Localizable(false)] string message); /// /// Writes the diagnostic message and exception at the Info level. @@ -472,7 +472,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Info(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Info(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message and exception at the Info level. @@ -482,7 +482,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Info(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Info(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Info level using the specified parameters and formatting them with the supplied format provider. @@ -491,7 +491,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Info level. @@ -505,7 +505,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Info([Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Info([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Info level using the specified parameter and formatting it with the supplied format provider. @@ -515,7 +515,7 @@ bool IsFatalEnabled /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the Info level using the specified parameter. @@ -536,7 +536,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Info level using the specified parameters. @@ -547,7 +547,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Info([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Info([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Info level using the specified arguments formatting it with the supplied format provider. @@ -561,7 +561,7 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Writes the diagnostic message at the Info level using the specified parameters. @@ -574,7 +574,7 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Info([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Info([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the Info level. @@ -584,7 +584,7 @@ bool IsFatalEnabled /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Info(Exception exception, string message) method instead. Marked obsolete with v4.3.11")] [EditorBrowsable(EditorBrowsableState.Never)] - void Info([Localizable(false)] string message, Exception exception); + void Info([Localizable(false)] string message, Exception? exception); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the Info level. @@ -594,7 +594,7 @@ bool IsFatalEnabled /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Info(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] [EditorBrowsable(EditorBrowsableState.Never)] - void InfoException([Localizable(false)] string message, Exception exception); + void InfoException([Localizable(false)] string message, Exception? exception); #endregion @@ -608,7 +608,7 @@ bool IsFatalEnabled /// /// Type of the value. /// The value to be written. - void Warn(T value); + void Warn(T? value); /// /// Writes the diagnostic message at the Warn level. @@ -616,7 +616,7 @@ bool IsFatalEnabled /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - void Warn(IFormatProvider formatProvider, T value); + void Warn(IFormatProvider? formatProvider, T? value); /// /// Writes the diagnostic message at the Warn level. @@ -629,7 +629,7 @@ bool IsFatalEnabled /// /// A to be written. /// An exception to be logged. - void Warn(Exception exception, [Localizable(false)] string message); + void Warn(Exception? exception, [Localizable(false)] string message); /// /// Writes the diagnostic message and exception at the Warn level. @@ -638,7 +638,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Warn(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Warn(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message and exception at the Warn level. @@ -648,7 +648,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Warn(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Warn(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Warn level using the specified parameters and formatting them with the supplied format provider. @@ -657,7 +657,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Warn level. @@ -671,7 +671,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Warn([Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Warn([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Warn level using the specified parameter and formatting it with the supplied format provider. @@ -681,7 +681,7 @@ bool IsFatalEnabled /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the Warn level using the specified parameter. @@ -690,7 +690,7 @@ bool IsFatalEnabled /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the Warn level using the specified arguments formatting it with the supplied format provider. @@ -702,7 +702,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Warn level using the specified parameters. @@ -713,7 +713,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Warn level using the specified arguments formatting it with the supplied format provider. @@ -727,7 +727,7 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Writes the diagnostic message at the Warn level using the specified parameters. @@ -740,7 +740,7 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the Warn level. @@ -750,7 +750,7 @@ bool IsFatalEnabled /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Warn(Exception exception, string message) method instead. Marked obsolete with v4.3.11")] [EditorBrowsable(EditorBrowsableState.Never)] - void Warn([Localizable(false)] string message, Exception exception); + void Warn([Localizable(false)] string message, Exception? exception); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the Warn level. @@ -760,7 +760,7 @@ bool IsFatalEnabled /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Warn(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] [EditorBrowsable(EditorBrowsableState.Never)] - void WarnException([Localizable(false)] string message, Exception exception); + void WarnException([Localizable(false)] string message, Exception? exception); #endregion @@ -774,7 +774,7 @@ bool IsFatalEnabled /// /// Type of the value. /// The value to be written. - void Error(T value); + void Error(T? value); /// /// Writes the diagnostic message at the Error level. @@ -782,7 +782,7 @@ bool IsFatalEnabled /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - void Error(IFormatProvider formatProvider, T value); + void Error(IFormatProvider? formatProvider, T? value); /// /// Writes the diagnostic message at the Error level. @@ -795,7 +795,7 @@ bool IsFatalEnabled /// /// A to be written. /// An exception to be logged. - void Error(Exception exception, [Localizable(false)] string message); + void Error(Exception? exception, [Localizable(false)] string message); /// /// Writes the diagnostic message and exception at the Error level. @@ -804,7 +804,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Error(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Error(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message and exception at the Error level. @@ -814,7 +814,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Error(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Error(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// @@ -824,7 +824,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Error level. @@ -838,7 +838,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Error([Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Error([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Error level using the specified parameter and formatting it with the supplied format provider. @@ -848,7 +848,7 @@ bool IsFatalEnabled /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the Error level using the specified parameter. @@ -857,7 +857,7 @@ bool IsFatalEnabled /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the Error level using the specified arguments formatting it with the supplied format provider. @@ -869,7 +869,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Error level using the specified parameters. @@ -880,7 +880,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Error level using the specified arguments formatting it with the supplied format provider. @@ -894,7 +894,7 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Writes the diagnostic message at the Error level using the specified parameters. @@ -907,7 +907,7 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the Error level. @@ -917,7 +917,7 @@ bool IsFatalEnabled /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Error(Exception exception, string message) method instead. Marked obsolete with v4.3.11")] [EditorBrowsable(EditorBrowsableState.Never)] - void Error([Localizable(false)] string message, Exception exception); + void Error([Localizable(false)] string message, Exception? exception); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the Error level. @@ -927,7 +927,7 @@ bool IsFatalEnabled /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Error(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] [EditorBrowsable(EditorBrowsableState.Never)] - void ErrorException([Localizable(false)] string message, Exception exception); + void ErrorException([Localizable(false)] string message, Exception? exception); #endregion @@ -941,7 +941,7 @@ bool IsFatalEnabled /// /// Type of the value. /// The value to be written. - void Fatal(T value); + void Fatal(T? value); /// /// Writes the diagnostic message at the Fatal level. @@ -949,7 +949,7 @@ bool IsFatalEnabled /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - void Fatal(IFormatProvider formatProvider, T value); + void Fatal(IFormatProvider? formatProvider, T? value); /// /// Writes the diagnostic message at the Fatal level. @@ -962,7 +962,7 @@ bool IsFatalEnabled /// /// A to be written. /// An exception to be logged. - void Fatal(Exception exception, [Localizable(false)] string message); + void Fatal(Exception? exception, [Localizable(false)] string message); /// /// Writes the diagnostic message and exception at the Fatal level. @@ -971,7 +971,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Fatal(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Fatal(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message and exception at the Fatal level. @@ -981,7 +981,7 @@ bool IsFatalEnabled /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Fatal(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Fatal(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Fatal level using the specified parameters and formatting them with the supplied format provider. @@ -990,7 +990,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Fatal level. @@ -1004,7 +1004,7 @@ bool IsFatalEnabled /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Fatal([Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Fatal([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the Fatal level using the specified parameter and formatting it with the supplied format provider. @@ -1014,7 +1014,7 @@ bool IsFatalEnabled /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the Fatal level using the specified parameter. @@ -1023,7 +1023,7 @@ bool IsFatalEnabled /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the Fatal level using the specified arguments formatting it with the supplied format provider. @@ -1035,7 +1035,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Fatal level using the specified parameters. @@ -1046,7 +1046,7 @@ bool IsFatalEnabled /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the Fatal level using the specified arguments formatting it with the supplied format provider. @@ -1060,7 +1060,7 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Writes the diagnostic message at the Fatal level using the specified parameters. @@ -1073,7 +1073,7 @@ bool IsFatalEnabled /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the Fatal level. @@ -1083,7 +1083,7 @@ bool IsFatalEnabled /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Fatal(Exception exception, string message) method instead. Marked obsolete with v4.3.11")] [EditorBrowsable(EditorBrowsableState.Never)] - void Fatal([Localizable(false)] string message, Exception exception); + void Fatal([Localizable(false)] string message, Exception? exception); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the Fatal level. @@ -1093,7 +1093,7 @@ bool IsFatalEnabled /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Fatal(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] [EditorBrowsable(EditorBrowsableState.Never)] - void FatalException([Localizable(false)] string message, Exception exception); + void FatalException([Localizable(false)] string message, Exception? exception); #endregion diff --git a/src/NLog/ILoggerBase-V1.cs b/src/NLog/ILoggerBase-V1.cs index 1903bb6ad7..ff231cf7b2 100644 --- a/src/NLog/ILoggerBase-V1.cs +++ b/src/NLog/ILoggerBase-V1.cs @@ -35,6 +35,7 @@ namespace NLog { using System; using System.ComponentModel; + using System.Runtime.CompilerServices; using JetBrains.Annotations; /// @@ -51,7 +52,10 @@ public partial interface ILoggerBase /// The log level. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Log(LogLevel level, object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, object? value); /// /// Writes the diagnostic message at the specified level. @@ -60,7 +64,10 @@ public partial interface ILoggerBase /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - void Log(LogLevel level, IFormatProvider formatProvider, object value); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, object? value); /// /// Writes the diagnostic message at the specified level using the specified parameters. @@ -71,7 +78,10 @@ public partial interface ILoggerBase /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2); /// /// Writes the diagnostic message at the specified level using the specified parameters. @@ -83,7 +93,10 @@ public partial interface ILoggerBase /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter and formatting it with the supplied format provider. @@ -94,7 +107,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -104,6 +120,9 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, bool argument); /// @@ -115,7 +134,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -125,6 +147,9 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, char argument); /// @@ -136,7 +161,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -146,6 +174,9 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, byte argument); /// @@ -157,7 +188,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -167,7 +201,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, string argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, string? argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter and formatting it with the supplied format provider. @@ -178,7 +215,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -188,6 +228,9 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, int argument); /// @@ -199,7 +242,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -209,6 +255,9 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, long argument); /// @@ -220,7 +269,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -230,6 +282,9 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, float argument); /// @@ -241,7 +296,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -251,6 +309,9 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, double argument); /// @@ -262,7 +323,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -272,6 +336,9 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument); /// @@ -283,7 +350,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -293,7 +363,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object? argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter and formatting it with the supplied format provider. @@ -304,7 +377,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -314,6 +390,9 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument); /// @@ -325,7 +404,10 @@ public partial interface ILoggerBase /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -336,6 +418,9 @@ public partial interface ILoggerBase [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, uint argument); /// @@ -348,7 +433,10 @@ public partial interface ILoggerBase [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); /// /// Writes the diagnostic message at the specified level using the specified value as a parameter. @@ -359,6 +447,9 @@ public partial interface ILoggerBase [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument); #endregion diff --git a/src/NLog/ILoggerBase.cs b/src/NLog/ILoggerBase.cs index ab44f9bed5..3caa6142a4 100644 --- a/src/NLog/ILoggerBase.cs +++ b/src/NLog/ILoggerBase.cs @@ -104,7 +104,7 @@ LogFactory Factory /// Type of the value. /// The log level. /// The value to be written. - void Log(LogLevel level, T value); + void Log(LogLevel level, T? value); /// /// Writes the diagnostic message at the specified level. @@ -113,7 +113,7 @@ LogFactory Factory /// The log level. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - void Log(LogLevel level, IFormatProvider formatProvider, T value); + void Log(LogLevel level, IFormatProvider? formatProvider, T? value); /// /// Writes the diagnostic message at the specified level. @@ -130,7 +130,7 @@ LogFactory Factory /// Arguments to format. /// An exception to be logged. [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Log(LogLevel level, Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message and exception at the specified level. @@ -141,7 +141,7 @@ LogFactory Factory /// Arguments to format. /// An exception to be logged. [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Log(LogLevel level, Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the specified level using the specified parameters and formatting them with the supplied format provider. @@ -151,7 +151,7 @@ LogFactory Factory /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the specified level. @@ -167,7 +167,7 @@ LogFactory Factory /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args); + void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args); /// /// Writes the diagnostic message at the specified level using the specified parameter and formatting it with the supplied format provider. @@ -178,7 +178,7 @@ LogFactory Factory /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the specified level using the specified parameter. @@ -188,7 +188,7 @@ LogFactory Factory /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument); + void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument); /// /// Writes the diagnostic message at the specified level using the specified arguments formatting it with the supplied format provider. @@ -201,7 +201,7 @@ LogFactory Factory /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the specified level using the specified parameters. @@ -213,7 +213,7 @@ LogFactory Factory /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2); + void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2); /// /// Writes the diagnostic message at the specified level using the specified arguments formatting it with the supplied format provider. @@ -228,7 +228,7 @@ LogFactory Factory /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Writes the diagnostic message at the specified level using the specified parameters. @@ -242,7 +242,7 @@ LogFactory Factory /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3); + void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the specified level. @@ -253,7 +253,7 @@ LogFactory Factory /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Log(LogLevel level, Exception exception, [Localizable(false)] string message, params object[] args) instead. Marked obsolete with v4.3.11")] [EditorBrowsable(EditorBrowsableState.Never)] - void Log(LogLevel level, [Localizable(false)] string message, Exception exception); + void Log(LogLevel level, [Localizable(false)] string message, Exception? exception); /// /// Obsolete and replaced by - Writes the diagnostic message and exception at the specified level. @@ -264,7 +264,7 @@ LogFactory Factory /// This method was marked as obsolete before NLog 4.3.11 and it may be removed in a future release. [Obsolete("Use Log(LogLevel level, Exception exception, [Localizable(false)] string message, params object[] args) instead. Marked obsolete with v4.3.11")] [EditorBrowsable(EditorBrowsableState.Never)] - void LogException(LogLevel level, [Localizable(false)] string message, Exception exception); + void LogException(LogLevel level, [Localizable(false)] string message, Exception? exception); #endregion } } diff --git a/src/NLog/ILoggerExtensions.cs b/src/NLog/ILoggerExtensions.cs index 927fe13e17..b731b1ee85 100644 --- a/src/NLog/ILoggerExtensions.cs +++ b/src/NLog/ILoggerExtensions.cs @@ -54,7 +54,7 @@ public static class ILoggerExtensions /// The logger to write the log event to. /// The log level. When not /// for chaining calls. - public static LogEventBuilder ForLogEvent([NotNull] this ILogger logger, LogLevel logLevel = null) + public static LogEventBuilder ForLogEvent(this ILogger logger, LogLevel? logLevel = null) { return logLevel is null ? new LogEventBuilder(logger) : new LogEventBuilder(logger, logLevel); } @@ -64,7 +64,7 @@ public static LogEventBuilder ForLogEvent([NotNull] this ILogger logger, LogLeve /// /// The logger to write the log event to. /// for chaining calls. - public static LogEventBuilder ForTraceEvent([NotNull] this ILogger logger) + public static LogEventBuilder ForTraceEvent(this ILogger logger) { return new LogEventBuilder(logger, LogLevel.Trace); } @@ -74,7 +74,7 @@ public static LogEventBuilder ForTraceEvent([NotNull] this ILogger logger) /// /// The logger to write the log event to. /// for chaining calls. - public static LogEventBuilder ForDebugEvent([NotNull] this ILogger logger) + public static LogEventBuilder ForDebugEvent(this ILogger logger) { return new LogEventBuilder(logger, LogLevel.Debug); } @@ -84,7 +84,7 @@ public static LogEventBuilder ForDebugEvent([NotNull] this ILogger logger) /// /// The logger to write the log event to. /// for chaining calls. - public static LogEventBuilder ForInfoEvent([NotNull] this ILogger logger) + public static LogEventBuilder ForInfoEvent(this ILogger logger) { return new LogEventBuilder(logger, LogLevel.Info); } @@ -94,7 +94,7 @@ public static LogEventBuilder ForInfoEvent([NotNull] this ILogger logger) /// /// The logger to write the log event to. /// for chaining calls. - public static LogEventBuilder ForWarnEvent([NotNull] this ILogger logger) + public static LogEventBuilder ForWarnEvent(this ILogger logger) { return new LogEventBuilder(logger, LogLevel.Warn); } @@ -104,7 +104,7 @@ public static LogEventBuilder ForWarnEvent([NotNull] this ILogger logger) /// /// The logger to write the log event to. /// for chaining calls. - public static LogEventBuilder ForErrorEvent([NotNull] this ILogger logger) + public static LogEventBuilder ForErrorEvent(this ILogger logger) { return new LogEventBuilder(logger, LogLevel.Error); } @@ -114,7 +114,7 @@ public static LogEventBuilder ForErrorEvent([NotNull] this ILogger logger) /// /// The logger to write the log event to. /// for chaining calls. - public static LogEventBuilder ForFatalEvent([NotNull] this ILogger logger) + public static LogEventBuilder ForFatalEvent(this ILogger logger) { return new LogEventBuilder(logger, LogLevel.Fatal); } @@ -126,7 +126,7 @@ public static LogEventBuilder ForFatalEvent([NotNull] this ILogger logger) /// The exception information of the logging event. /// The for the log event. Defaults to when not specified. /// for chaining calls. - public static LogEventBuilder ForExceptionEvent([NotNull] this ILogger logger, Exception exception, LogLevel logLevel = null) + public static LogEventBuilder ForExceptionEvent(this ILogger logger, Exception? exception, LogLevel? logLevel = null) { return ForLogEvent(logger, logLevel ?? LogLevel.Error).Exception(exception); } @@ -143,7 +143,7 @@ public static LogEventBuilder ForExceptionEvent([NotNull] this ILogger logger, E /// A logger implementation that will handle the message. /// The value to be written. [Conditional("DEBUG")] - public static void ConditionalDebug([NotNull] this ILogger logger, T value) + public static void ConditionalDebug(this ILogger logger, T? value) { logger.Debug(value); } @@ -156,7 +156,7 @@ public static void ConditionalDebug([NotNull] this ILogger logger, T value) /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. [Conditional("DEBUG")] - public static void ConditionalDebug([NotNull] this ILogger logger, IFormatProvider formatProvider, T value) + public static void ConditionalDebug(this ILogger logger, IFormatProvider? formatProvider, T? value) { logger.Debug(formatProvider, value); } @@ -167,7 +167,7 @@ public static void ConditionalDebug([NotNull] this ILogger logger, IFormatPro /// A logger implementation that will handle the message. /// A function returning message to be written. Function is not evaluated if logging is not enabled. [Conditional("DEBUG")] - public static void ConditionalDebug([NotNull] this ILogger logger, LogMessageGenerator messageFunc) + public static void ConditionalDebug(this ILogger logger, LogMessageGenerator messageFunc) { logger.Debug(messageFunc); } @@ -181,7 +181,7 @@ public static void ConditionalDebug([NotNull] this ILogger logger, LogMessageGen /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalDebug([NotNull] this ILogger logger, Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public static void ConditionalDebug(this ILogger logger, Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { logger.Debug(exception, message, args); } @@ -196,7 +196,7 @@ public static void ConditionalDebug([NotNull] this ILogger logger, Exception exc /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalDebug([NotNull] this ILogger logger, Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public static void ConditionalDebug(this ILogger logger, Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { logger.Debug(exception, formatProvider, message, args); } @@ -207,9 +207,9 @@ public static void ConditionalDebug([NotNull] this ILogger logger, Exception exc /// A logger implementation that will handle the message. /// Log message. [Conditional("DEBUG")] - public static void ConditionalDebug([NotNull] this ILogger logger, [Localizable(false)] string message) + public static void ConditionalDebug(this ILogger logger, [Localizable(false)] string message) { - logger.Debug(message, default(object[])); + logger.Debug(message); } /// @@ -220,7 +220,7 @@ public static void ConditionalDebug([NotNull] this ILogger logger, [Localizable( /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalDebug([NotNull] this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public static void ConditionalDebug(this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { logger.Debug(message, args); } @@ -234,7 +234,7 @@ public static void ConditionalDebug([NotNull] this ILogger logger, [Localizable( /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalDebug([NotNull] this ILogger logger, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public static void ConditionalDebug(this ILogger logger, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { logger.Debug(formatProvider, message, args); } @@ -248,11 +248,11 @@ public static void ConditionalDebug([NotNull] this ILogger logger, IFormatProvid /// The argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalDebug([NotNull] this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public static void ConditionalDebug(this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (logger.IsDebugEnabled) { - logger.Debug(message, new object[] { argument }); + logger.Debug(message, argument); } } @@ -267,11 +267,11 @@ public static void ConditionalDebug([NotNull] this ILogger logger, [L /// The second argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalDebug([NotNull] this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public static void ConditionalDebug(this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (logger.IsDebugEnabled) { - logger.Debug(message, new object[] { argument1, argument2 }); + logger.Debug(message, argument1, argument2); } } @@ -288,11 +288,11 @@ public static void ConditionalDebug([NotNull] this ILogg /// The third argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalDebug([NotNull] this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public static void ConditionalDebug(this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (logger.IsDebugEnabled) { - logger.Debug(message, new object[] { argument1, argument2, argument3 }); + logger.Debug(message, argument1, argument2, argument3); } } @@ -310,7 +310,7 @@ public static void ConditionalDebug([NotNull /// A logger implementation that will handle the message. /// The value to be written. [Conditional("DEBUG")] - public static void ConditionalTrace([NotNull] this ILogger logger, T value) + public static void ConditionalTrace(this ILogger logger, T? value) { logger.Trace(value); } @@ -323,7 +323,7 @@ public static void ConditionalTrace([NotNull] this ILogger logger, T value) /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. [Conditional("DEBUG")] - public static void ConditionalTrace([NotNull] this ILogger logger, IFormatProvider formatProvider, T value) + public static void ConditionalTrace(this ILogger logger, IFormatProvider? formatProvider, T? value) { logger.Trace(formatProvider, value); } @@ -334,7 +334,7 @@ public static void ConditionalTrace([NotNull] this ILogger logger, IFormatPro /// A logger implementation that will handle the message. /// A function returning message to be written. Function is not evaluated if logging is not enabled. [Conditional("DEBUG")] - public static void ConditionalTrace([NotNull] this ILogger logger, LogMessageGenerator messageFunc) + public static void ConditionalTrace(this ILogger logger, LogMessageGenerator messageFunc) { logger.Trace(messageFunc); } @@ -348,7 +348,7 @@ public static void ConditionalTrace([NotNull] this ILogger logger, LogMessageGen /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalTrace([NotNull] this ILogger logger, Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public static void ConditionalTrace(this ILogger logger, Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { logger.Trace(exception, message, args); } @@ -363,7 +363,7 @@ public static void ConditionalTrace([NotNull] this ILogger logger, Exception exc /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalTrace([NotNull] this ILogger logger, Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public static void ConditionalTrace(this ILogger logger, Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { logger.Trace(exception, formatProvider, message, args); } @@ -374,9 +374,9 @@ public static void ConditionalTrace([NotNull] this ILogger logger, Exception exc /// A logger implementation that will handle the message. /// Log message. [Conditional("DEBUG")] - public static void ConditionalTrace([NotNull] this ILogger logger, [Localizable(false)] string message) + public static void ConditionalTrace(this ILogger logger, [Localizable(false)] string message) { - logger.Trace(message, default(object[])); + logger.Trace(message); } /// @@ -387,7 +387,7 @@ public static void ConditionalTrace([NotNull] this ILogger logger, [Localizable( /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalTrace([NotNull] this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public static void ConditionalTrace(this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { logger.Trace(message, args); } @@ -401,7 +401,7 @@ public static void ConditionalTrace([NotNull] this ILogger logger, [Localizable( /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalTrace([NotNull] this ILogger logger, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public static void ConditionalTrace(this ILogger logger, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { logger.Trace(formatProvider, message, args); } @@ -415,11 +415,11 @@ public static void ConditionalTrace([NotNull] this ILogger logger, IFormatProvid /// The argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalTrace([NotNull] this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public static void ConditionalTrace(this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (logger.IsTraceEnabled) { - logger.Trace(message, new object[] { argument }); + logger.Trace(message, argument); } } @@ -434,11 +434,11 @@ public static void ConditionalTrace([NotNull] this ILogger logger, [L /// The second argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalTrace([NotNull] this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public static void ConditionalTrace(this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (logger.IsTraceEnabled) { - logger.Trace(message, new object[] { argument1, argument2 }); + logger.Trace(message, argument1, argument2); } } @@ -455,11 +455,11 @@ public static void ConditionalTrace([NotNull] this ILogg /// The third argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public static void ConditionalTrace([NotNull] this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public static void ConditionalTrace(this ILogger logger, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (logger.IsTraceEnabled) { - logger.Trace(message, new object[] { argument1, argument2, argument3 }); + logger.Trace(message, argument1, argument2, argument3); } } @@ -472,11 +472,11 @@ public static void ConditionalTrace([NotNull /// The log level. /// An exception to be logged. /// A function returning message to be written. Function is not evaluated if logging is not enabled. - public static void Log([NotNull] this ILogger logger, LogLevel level, Exception exception, LogMessageGenerator messageFunc) + public static void Log(this ILogger logger, LogLevel level, Exception? exception, LogMessageGenerator messageFunc) { if (logger.IsEnabled(level)) { - logger.Log(level, exception, messageFunc(), null); + logger.Log(level, exception, messageFunc(), ArrayHelper.Empty()); } } @@ -486,7 +486,7 @@ public static void Log([NotNull] this ILogger logger, LogLevel level, Exception /// A logger implementation that will handle the message. /// An exception to be logged. /// A function returning message to be written. Function is not evaluated if logging is not enabled. - public static void Trace([NotNull] this ILogger logger, Exception exception, LogMessageGenerator messageFunc) + public static void Trace(this ILogger logger, Exception? exception, LogMessageGenerator messageFunc) { if (logger.IsTraceEnabled) { @@ -502,7 +502,7 @@ public static void Trace([NotNull] this ILogger logger, Exception exception, Log /// A logger implementation that will handle the message. /// An exception to be logged. /// A function returning message to be written. Function is not evaluated if logging is not enabled. - public static void Debug([NotNull] this ILogger logger, Exception exception, LogMessageGenerator messageFunc) + public static void Debug(this ILogger logger, Exception? exception, LogMessageGenerator messageFunc) { if (logger.IsDebugEnabled) { @@ -518,7 +518,7 @@ public static void Debug([NotNull] this ILogger logger, Exception exception, Log /// A logger implementation that will handle the message. /// An exception to be logged. /// A function returning message to be written. Function is not evaluated if logging is not enabled. - public static void Info([NotNull] this ILogger logger, Exception exception, LogMessageGenerator messageFunc) + public static void Info(this ILogger logger, Exception? exception, LogMessageGenerator messageFunc) { if (logger.IsInfoEnabled) { @@ -534,7 +534,7 @@ public static void Info([NotNull] this ILogger logger, Exception exception, LogM /// A logger implementation that will handle the message. /// An exception to be logged. /// A function returning message to be written. Function is not evaluated if logging is not enabled. - public static void Warn([NotNull] this ILogger logger, Exception exception, LogMessageGenerator messageFunc) + public static void Warn(this ILogger logger, Exception? exception, LogMessageGenerator messageFunc) { if (logger.IsWarnEnabled) { @@ -550,7 +550,7 @@ public static void Warn([NotNull] this ILogger logger, Exception exception, LogM /// A logger implementation that will handle the message. /// An exception to be logged. /// A function returning message to be written. Function is not evaluated if logging is not enabled. - public static void Error([NotNull] this ILogger logger, Exception exception, LogMessageGenerator messageFunc) + public static void Error(this ILogger logger, Exception? exception, LogMessageGenerator messageFunc) { if (logger.IsErrorEnabled) { @@ -566,7 +566,7 @@ public static void Error([NotNull] this ILogger logger, Exception exception, Log /// A logger implementation that will handle the message. /// An exception to be logged. /// A function returning message to be written. Function is not evaluated if logging is not enabled. - public static void Fatal([NotNull] this ILogger logger, Exception exception, LogMessageGenerator messageFunc) + public static void Fatal(this ILogger logger, Exception? exception, LogMessageGenerator messageFunc) { if (logger.IsFatalEnabled) { diff --git a/src/NLog/IObjectTypeTransformer.cs b/src/NLog/IObjectTypeTransformer.cs index 2778a1a89d..a2c859c931 100644 --- a/src/NLog/IObjectTypeTransformer.cs +++ b/src/NLog/IObjectTypeTransformer.cs @@ -44,6 +44,6 @@ internal interface IObjectTypeTransformer /// /// Null if unknown object, or object cannot be handled /// - object TryTransformObject(object obj); + object? TryTransformObject(object obj); } } diff --git a/src/NLog/ISuppress.cs b/src/NLog/ISuppress.cs index 4247f4f365..c402f3be62 100644 --- a/src/NLog/ISuppress.cs +++ b/src/NLog/ISuppress.cs @@ -61,7 +61,7 @@ public interface ISuppress /// Return type of the provided function. /// Function to run. /// Result returned by the provided function or the default value of type in case of exception. - T Swallow(Func func); + T? Swallow(Func func); /// /// Runs the provided function and returns its result. If an exception is thrown, it is logged at Error level. @@ -71,7 +71,7 @@ public interface ISuppress /// Function to run. /// Fallback value to return in case of exception. /// Result returned by the provided function or fallback value in case of exception. - T Swallow(Func func, T fallback); + T? Swallow(Func func, T? fallback); #if !NET35 && !NET40 /// @@ -103,7 +103,7 @@ public interface ISuppress /// Return type of the provided function. /// Async function to run. /// A task that represents the completion of the supplied task. If the supplied task ends in the state, the result of the new task will be the result of the supplied task; otherwise, the result of the new task will be the default value of type . - Task SwallowAsync(Func> asyncFunc); + Task SwallowAsync(Func> asyncFunc); /// /// Runs the provided async function and returns its result. If the task does not run to completion, an exception is logged at Error level. @@ -113,7 +113,7 @@ public interface ISuppress /// Async function to run. /// Fallback value to return if the task does not end in the state. /// A task that represents the completion of the supplied task. If the supplied task ends in the state, the result of the new task will be the result of the supplied task; otherwise, the result of the new task will be the fallback value. - Task SwallowAsync(Func> asyncFunc, TResult fallback); + Task SwallowAsync(Func> asyncFunc, TResult? fallback); #endif } } diff --git a/src/NLog/IValueFormatter.cs b/src/NLog/IValueFormatter.cs index 2ca8f195ba..7bb47372a8 100644 --- a/src/NLog/IValueFormatter.cs +++ b/src/NLog/IValueFormatter.cs @@ -31,12 +31,12 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Text; -using NLog.MessageTemplates; - namespace NLog { + using System; + using System.Text; + using NLog.MessageTemplates; + /// /// Render a message template property to a string /// @@ -51,6 +51,6 @@ public interface IValueFormatter /// An object that supplies culture-specific formatting information. /// Output destination. /// Serialize succeeded (true/false) - bool FormatValue(object value, string format, CaptureType captureType, IFormatProvider formatProvider, StringBuilder builder); + bool FormatValue(object? value, string? format, CaptureType captureType, IFormatProvider? formatProvider, StringBuilder builder); } } diff --git a/src/NLog/Internal/AppDomainWrapper.cs b/src/NLog/Internal/AppDomainWrapper.cs deleted file mode 100644 index c201e99878..0000000000 --- a/src/NLog/Internal/AppDomainWrapper.cs +++ /dev/null @@ -1,250 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - -namespace NLog.Internal.Fakeables -{ - using System; - using System.Collections.Generic; - using System.Reflection; - using NLog.Common; - - /// - /// Adapter for to - /// - [Obsolete("For unit testing only. Marked obsolete on NLog 5.0")] - internal sealed class AppDomainWrapper : IAppDomain - { - private readonly AppDomain _currentAppDomain; - - /// - /// Initializes a new instance of the class. - /// - /// The to wrap. - public AppDomainWrapper(AppDomain appDomain) - { - _currentAppDomain = appDomain; - try - { - BaseDirectory = LookupBaseDirectory(appDomain) ?? string.Empty; - } - catch (Exception ex) - { - InternalLogger.Warn(ex, "AppDomain.BaseDirectory Failed"); - BaseDirectory = string.Empty; - } - - try - { - FriendlyName = appDomain.FriendlyName; - } - catch (Exception ex) - { - InternalLogger.Warn(ex, "AppDomain.FriendlyName Failed"); - FriendlyName = GetFriendlyNameFromEntryAssembly() ?? GetFriendlyNameFromProcessName() ?? string.Empty; - } - - try - { - ConfigurationFile = LookupConfigurationFile(appDomain); - } - catch (Exception ex) - { - InternalLogger.Warn(ex, "AppDomain.SetupInformation.ConfigurationFile Failed"); - ConfigurationFile = string.Empty; - } - - try - { - PrivateBinPath = LookupPrivateBinPath(appDomain); - } - catch (Exception ex) - { - InternalLogger.Warn(ex, "AppDomain.SetupInformation.PrivateBinPath Failed"); - PrivateBinPath = ArrayHelper.Empty(); - } - - Id = appDomain.Id; - } - - private static string LookupBaseDirectory(AppDomain appDomain) - { - return appDomain.BaseDirectory; - } - - private static string LookupConfigurationFile(AppDomain appDomain) - { -#if NETFRAMEWORK - return appDomain.SetupInformation.ConfigurationFile; -#else - return string.Empty; -#endif - } - - private static string[] LookupPrivateBinPath(AppDomain appDomain) - { -#if NETFRAMEWORK - string privateBinPath = appDomain.SetupInformation.PrivateBinPath; - return string.IsNullOrEmpty(privateBinPath) - ? ArrayHelper.Empty() - : privateBinPath.SplitAndTrimTokens(';'); -#else - return ArrayHelper.Empty(); -#endif - } - - private static string GetFriendlyNameFromEntryAssembly() - { - try - { - string assemblyName = Assembly.GetEntryAssembly()?.GetName()?.Name; - return string.IsNullOrEmpty(assemblyName) ? null : assemblyName; - } - catch - { - return null; - } - } - - private static string GetFriendlyNameFromProcessName() - { - try - { - string processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; - return string.IsNullOrEmpty(processName) ? null : processName; - } - catch - { - return null; - } - } - - /// - /// Creates an AppDomainWrapper for the current - /// - public static AppDomainWrapper CurrentDomain => new AppDomainWrapper(AppDomain.CurrentDomain); - - /// - /// Gets or sets the base directory that the assembly resolver uses to probe for assemblies. - /// - public string BaseDirectory { get; private set; } - - /// - /// Gets or sets the name of the configuration file for an application domain. - /// - public string ConfigurationFile { get; private set; } - - /// - /// Gets or sets the list of directories under the application base directory that are probed for private assemblies. - /// - public IEnumerable PrivateBinPath { get; private set; } - - /// - /// Gets or set the friendly name. - /// - public string FriendlyName { get; private set; } - - /// - /// Gets an integer that uniquely identifies the application domain within the process. - /// - public int Id { get; private set; } - - /// - /// Gets the assemblies that have been loaded into the execution context of this application domain. - /// - /// A list of assemblies in this application domain. - public IEnumerable GetAssemblies() - { - if (_currentAppDomain != null) - return _currentAppDomain.GetAssemblies(); - else - return Internal.ArrayHelper.Empty(); - } - - /// - /// Process exit event. - /// - public event EventHandler ProcessExit - { - add - { - if (processExitEvent == null && _currentAppDomain != null) - _currentAppDomain.ProcessExit += OnProcessExit; - processExitEvent += value; - } - remove - { - processExitEvent -= value; - if (processExitEvent == null && _currentAppDomain != null) - _currentAppDomain.ProcessExit -= OnProcessExit; - } - } - private event EventHandler processExitEvent; - - /// - /// Domain unloaded event. - /// - public event EventHandler DomainUnload - { - add - { - if (domainUnloadEvent == null && _currentAppDomain != null) - _currentAppDomain.DomainUnload += OnDomainUnload; - domainUnloadEvent += value; - - } - remove - { - domainUnloadEvent -= value; - if (domainUnloadEvent == null && _currentAppDomain != null) - _currentAppDomain.DomainUnload -= OnDomainUnload; - } - } - private event EventHandler domainUnloadEvent; - - private void OnDomainUnload(object sender, EventArgs e) - { - var handler = domainUnloadEvent; - handler?.Invoke(sender, e); - } - - private void OnProcessExit(object sender, EventArgs eventArgs) - { - var handler = processExitEvent; - handler?.Invoke(sender, eventArgs); - } - } -} - -#endif diff --git a/src/NLog/Internal/AppEnvironmentWrapper.cs b/src/NLog/Internal/AppEnvironmentWrapper.cs index 470b9244de..24e78a23e4 100644 --- a/src/NLog/Internal/AppEnvironmentWrapper.cs +++ b/src/NLog/Internal/AppEnvironmentWrapper.cs @@ -37,18 +37,23 @@ namespace NLog.Internal.Fakeables using System.Collections.Generic; using System.Diagnostics; using System.IO; - using System.Xml; using NLog.Common; internal sealed class AppEnvironmentWrapper : IAppEnvironment { -#if !NETSTANDARD1_3 - private const string UnknownProcessName = ""; - - private string _entryAssemblyLocation; - private string _entryAssemblyFileName; - private string _currentProcessFilePath; - private string _currentProcessBaseName; + const string LongUNCPrefix = @"\\?\UNC\"; + + private const string UnknownProcessName = "[unknown]"; + + private string? _entryAssemblyLocation; + private string? _entryAssemblyFileName; + private string? _currentProcessFilePath; + private string? _currentProcessBaseName; + private string? _appDomainBaseDirectory; + private string? _appDomainConfigFile; + private string? _appDomainFriendlyName; + private IEnumerable? _appDomainPrivateBinPath; + private int? _appDomainId; private int? _currentProcessId; /// @@ -61,74 +66,191 @@ internal sealed class AppEnvironmentWrapper : IAppEnvironment public string CurrentProcessBaseName => _currentProcessBaseName ?? (_currentProcessBaseName = LookupCurrentProcessNameWithFallback()); /// public int CurrentProcessId => _currentProcessId ?? (_currentProcessId = LookupCurrentProcessIdWithFallback()).Value; -#endif -#pragma warning disable CS0618 // Type or member is obsolete /// - public string AppDomainBaseDirectory => AppDomain.BaseDirectory; + public string AppDomainBaseDirectory => _appDomainBaseDirectory ?? (_appDomainBaseDirectory = LookupAppDomainBaseDirectory()); /// - public string AppDomainConfigurationFile => AppDomain.ConfigurationFile; + public string AppDomainConfigurationFile => _appDomainConfigFile ?? (_appDomainConfigFile = LookupAppDomainConfigFileSafe()); /// - public string AppDomainFriendlyName => AppDomain.FriendlyName; + public string AppDomainFriendlyName => _appDomainFriendlyName ?? (_appDomainFriendlyName = LookupAppDomainFriendlyName() ?? CurrentProcessBaseName); /// - public int AppDomainId => AppDomain.Id; + public int AppDomainId => _appDomainId ?? (_appDomainId = AppDomain.CurrentDomain.Id).Value; /// - public IEnumerable AppDomainPrivateBinPath => AppDomain.PrivateBinPath; + public IEnumerable AppDomainPrivateBinPath => _appDomainPrivateBinPath ?? (_appDomainPrivateBinPath = LookupAppDomainPrivateBinPathSafe()); /// - public IEnumerable GetAppDomainRuntimeAssemblies() => AppDomain.GetAssemblies(); + public IEnumerable GetAppDomainRuntimeAssemblies() => AppDomain.CurrentDomain?.GetAssemblies() ?? ArrayHelper.Empty(); /// - public event EventHandler ProcessExit + public event EventHandler ProcessExit { add { - AppDomain.ProcessExit += value; - AppDomain.DomainUnload += value; + AppDomain.CurrentDomain.ProcessExit += value; + AppDomain.CurrentDomain.DomainUnload += value; } remove { - AppDomain.DomainUnload -= value; - AppDomain.ProcessExit -= value; + AppDomain.CurrentDomain.DomainUnload -= value; + AppDomain.CurrentDomain.ProcessExit -= value; } } -#pragma warning restore CS0618 // Type or member is obsolete /// public string UserTempFilePath => Path.GetTempPath(); - [Obsolete("For unit testing only. Marked obsolete on NLog 5.0")] - public IAppDomain AppDomain { get; internal set; } + /// + public bool FileExists(string path) + { + return File.Exists(path); + } -#pragma warning disable CS0618 // Type or member is obsolete - public AppEnvironmentWrapper(IAppDomain appDomain) + public TextReader LoadTextFile(string path) { - AppDomain = appDomain; + return new StreamReader(path); } -#pragma warning restore CS0618 // Type or member is obsolete - /// - public bool FileExists(string path) + /// + /// Long UNC paths does not allow relative-path-logic using '..', and also cannot be loaded into Uri by XmlReader + /// + internal static string FixFilePathWithLongUNC(string filepath) { - return File.Exists(path); + if (!string.IsNullOrEmpty(filepath) && filepath.StartsWith(LongUNCPrefix, StringComparison.Ordinal) && filepath.Length < 260) + { + // Workaround .NET 9 regression returning long-form UNC + // path from AppDomain.CurrentDomain.BaseDirectory + // https://github.com/dotnet/runtime/issues/109846 + filepath = @"\\" + filepath.Substring(LongUNCPrefix.Length); + } + return filepath; } - /// - public XmlReader LoadXmlFile(string path) + private static string LookupAppDomainBaseDirectory() { - return XmlReader.Create(path); + try + { + return AppDomain.CurrentDomain.BaseDirectory ?? string.Empty; + } + catch (Exception ex) + { + if (ex.MustBeRethrownImmediately()) + throw; + + InternalLogger.Debug(ex, "AppDomain.BaseDirectory Failed"); + return string.Empty; + } + } + + private static string[] LookupAppDomainPrivateBinPathSafe() + { + try + { + return LookupAppDomainPrivateBinPath(); // Bonus try-catch when loading NLog for .NET Framework in .NET Core + } + catch (Exception ex) + { + if (ex.MustBeRethrownImmediately()) + throw; + + InternalLogger.Debug(ex, "AppDomain.CurrentDomain.SetupInformation.PrivateBinPath Failed"); + return ArrayHelper.Empty(); + } + } + + private static string[] LookupAppDomainPrivateBinPath() + { + try + { +#if NETFRAMEWORK + string privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath; + return string.IsNullOrEmpty(privateBinPath) + ? ArrayHelper.Empty() + : privateBinPath.SplitAndTrimTokens(';'); +#else + return ArrayHelper.Empty(); +#endif + } + catch (Exception ex) + { + if (ex.MustBeRethrownImmediately()) + throw; + + InternalLogger.Debug(ex, "AppDomain.CurrentDomain.SetupInformation.PrivateBinPath Failed"); + return ArrayHelper.Empty(); + } + } + + private static string LookupAppDomainConfigFileSafe() + { + try + { + return LookupAppDomainConfigFile(); // Bonus try-catch when loading NLog for .NET Framework in .NET Core + } + catch (Exception ex) + { + if (ex.MustBeRethrownImmediately()) + throw; + + InternalLogger.Debug(ex, "AppDomain.SetupInformation.ConfigurationFile Failed"); + return string.Empty; + } + } + + private static string LookupAppDomainConfigFile() + { + try + { +#if NETFRAMEWORK + return AppDomain.CurrentDomain.SetupInformation.ConfigurationFile ?? string.Empty; +#else + return string.Empty; +#endif + } + catch (Exception ex) + { + if (ex.MustBeRethrownImmediately()) + throw; + + InternalLogger.Debug(ex, "AppDomain.SetupInformation.ConfigurationFile Failed"); + return string.Empty; + } + } + + private static string LookupAppDomainFriendlyName() + { + try + { + var friendlyName = AppDomain.CurrentDomain.FriendlyName; + return !string.IsNullOrEmpty(friendlyName) ? friendlyName : LookupEntryAssemblyFriendlyName(); + } + catch (Exception ex) + { + if (ex.MustBeRethrownImmediately()) + throw; + + InternalLogger.Debug(ex, "AppDomain.FriendlyName Failed"); + return LookupEntryAssemblyFriendlyName(); + } + } + + private static string LookupEntryAssemblyFriendlyName() + { + try + { + var assemblyName = System.Reflection.Assembly.GetEntryAssembly()?.GetName()?.Name; + return assemblyName ?? UnknownProcessName; + } + catch + { + return UnknownProcessName; + } } -#if !NETSTANDARD1_3 private static string LookupEntryAssemblyLocation() { var entryAssembly = System.Reflection.Assembly.GetEntryAssembly(); - var entryLocation = entryAssembly?.Location; + var entryLocation = AssemblyHelpers.GetAssemblyFileLocation(entryAssembly); if (!string.IsNullOrEmpty(entryLocation)) - { return Path.GetDirectoryName(entryLocation); - } - -#pragma warning disable CS0618 // Type or member is obsolete - return AssemblyHelpers.GetAssemblyFileLocation(entryAssembly); -#pragma warning restore CS0618 // Type or member is obsolete + else + return string.Empty; } private static string LookupEntryAssemblyFileName() @@ -136,11 +258,9 @@ private static string LookupEntryAssemblyFileName() try { var entryAssembly = System.Reflection.Assembly.GetEntryAssembly(); - var entryLocation = entryAssembly?.Location; + var entryLocation = AssemblyHelpers.GetAssemblyFileLocation(entryAssembly); if (!string.IsNullOrEmpty(entryLocation)) - { return Path.GetFileName(entryLocation); - } // Fallback to the Assembly-Name when unable to extract FileName from Location var assemblyName = entryAssembly?.GetName()?.Name; @@ -176,7 +296,7 @@ private static string LookupCurrentProcessFilePathWithFallback() } } - private static string LookupCurrentProcessFilePath() + private static string? LookupCurrentProcessFilePath() { try { @@ -249,7 +369,7 @@ private static string LookupCurrentProcessNameWithFallback() } } - private static string LookupCurrentProcessName() + private static string? LookupCurrentProcessName() { try { @@ -289,9 +409,8 @@ private static string LookupCurrentProcessNameNative() return UnknownProcessName; } -#endif -#if !NETSTANDARD +#if NETFRAMEWORK private static string LookupCurrentProcessFilePathNative() { try diff --git a/src/NLog/Internal/AppendBuilderCreator.cs b/src/NLog/Internal/AppendBuilderCreator.cs index 2efcfb5893..2eea875dee 100644 --- a/src/NLog/Internal/AppendBuilderCreator.cs +++ b/src/NLog/Internal/AppendBuilderCreator.cs @@ -31,18 +31,17 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Text; - namespace NLog.Internal { + using System; + using System.Text; + /// /// Allocates new builder and appends to the provided target builder on dispose /// internal struct AppendBuilderCreator : IDisposable { private static readonly StringBuilderPool _builderPool = new StringBuilderPool(Environment.ProcessorCount * 2); - private readonly StringBuilder _appendTarget; /// /// Access the new builder allocated @@ -54,29 +53,11 @@ internal struct AppendBuilderCreator : IDisposable public AppendBuilderCreator(bool mustBeEmpty) { _builder = _builderPool.Acquire(); - _appendTarget = _builder.Item; - } - - public AppendBuilderCreator(StringBuilder appendTarget, bool mustBeEmpty) - { - _appendTarget = appendTarget; - if (_appendTarget.Length > 0 && mustBeEmpty) - { - _builder = _builderPool.Acquire(); - } - else - { - _builder = new StringBuilderPool.ItemHolder(_appendTarget, null, 0); - } } public void Dispose() { - if (!ReferenceEquals(_builder.Item, _appendTarget)) - { - _builder.Item.CopyTo(_appendTarget); - _builder.Dispose(); - } + _builder.Dispose(); } } } diff --git a/src/NLog/Internal/AssemblyHelpers.cs b/src/NLog/Internal/AssemblyHelpers.cs index accd660910..e7dc092c30 100644 --- a/src/NLog/Internal/AssemblyHelpers.cs +++ b/src/NLog/Internal/AssemblyHelpers.cs @@ -30,11 +30,10 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF // THE POSSIBILITY OF SUCH DAMAGE. // + namespace NLog.Internal { using System; - using System.IO; - using System.Linq; using System.Reflection; using NLog.Common; @@ -43,76 +42,36 @@ namespace NLog.Internal /// internal static class AssemblyHelpers { - /// - /// Gets all usable exported types from the given assembly. - /// - /// Assembly to scan. - /// Usable types from the given assembly. - /// Types which cannot be loaded are skipped. - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow extension loading from config", "IL2026")] - [Obsolete("Instead use NLog.LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] - public static Type[] SafeGetTypes(Assembly assembly) - { - try - { - return assembly.GetTypes(); - } - catch (ReflectionTypeLoadException typeLoadException) - { - var result = typeLoadException.Types?.Where(t => t != null)?.ToArray() ?? ArrayHelper.Empty(); - InternalLogger.Warn(typeLoadException, "Loaded {0} valid types from Assembly: {1}", result.Length, assembly.FullName); - foreach (var ex in typeLoadException.LoaderExceptions ?? ArrayHelper.Empty()) - { - InternalLogger.Warn(ex, "Type load exception."); - } - return result; - } - catch (Exception ex) - { - InternalLogger.Warn(ex, "Failed to load types from Assembly: {0}", assembly.FullName); - return ArrayHelper.Empty(); - } - } - -#if !NETSTANDARD1_3 - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Returns empty string for assemblies embedded in a single-file app", "IL3000")] public static string GetAssemblyFileLocation(Assembly assembly) { - string assemblyFullName = string.Empty; +#if !NETFRAMEWORK + // Notice assembly can be loaded from nuget-cache using NTFS-hard-link, and return unexpected file-location. + return assembly?.Location ?? string.Empty; +#else + if (assembly is null) + return string.Empty; + + var assemblyFullName = assembly.FullName; try { - if (assembly is null) - { - return string.Empty; - } - - assemblyFullName = assembly.FullName; - -#if NETSTANDARD - if (string.IsNullOrEmpty(assembly.Location)) - { - // Assembly with no actual location should be skipped (Avoid PlatformNotSupportedException) - InternalLogger.Debug("Ignoring assembly location because location is empty: {0}", assemblyFullName); - return string.Empty; - } -#endif - Uri assemblyCodeBase; if (!Uri.TryCreate(assembly.CodeBase, UriKind.RelativeOrAbsolute, out assemblyCodeBase)) { InternalLogger.Debug("Ignoring assembly location because code base is unknown: '{0}' ({1})", assembly.CodeBase, assemblyFullName); return string.Empty; } +#pragma warning restore SYSLIB0012 // Type or member is obsolete - var assemblyLocation = Path.GetDirectoryName(assemblyCodeBase.LocalPath); + var assemblyLocation = System.IO.Path.GetDirectoryName(assemblyCodeBase.LocalPath); if (string.IsNullOrEmpty(assemblyLocation)) { InternalLogger.Debug("Ignoring assembly location because it is not a valid directory: '{0}' ({1})", assemblyCodeBase.LocalPath, assemblyFullName); return string.Empty; } - DirectoryInfo directoryInfo = new DirectoryInfo(assemblyLocation); + var directoryInfo = new System.IO.DirectoryInfo(assemblyLocation); if (!directoryInfo.Exists) { InternalLogger.Debug("Ignoring assembly location because directory doesn't exists: '{0}' ({1})", assemblyLocation, assemblyFullName); @@ -120,7 +79,8 @@ public static string GetAssemblyFileLocation(Assembly assembly) } InternalLogger.Debug("Found assembly location directory: '{0}' ({1})", directoryInfo.FullName, assemblyFullName); - return directoryInfo.FullName; + var assemblyFileName = string.IsNullOrEmpty(assembly.Location) ? System.IO.Path.GetFileName(assemblyCodeBase.LocalPath) : System.IO.Path.GetFileName(assembly.Location); + return System.IO.Path.Combine(directoryInfo.FullName, assemblyFileName); } catch (System.PlatformNotSupportedException ex) { @@ -149,7 +109,37 @@ public static string GetAssemblyFileLocation(Assembly assembly) } return string.Empty; } +#endif } + + /// + /// Logs the assembly version and file version of the given Assembly. + /// + /// The assembly to log. + public static void LogAssemblyVersion(Assembly assembly) + { + try + { + if (!InternalLogger.IsInfoEnabled) + return; + + var fileVersion = assembly.GetFirstCustomAttribute()?.Version; + var productVersion = assembly.GetFirstCustomAttribute()?.InformationalVersion; + var globalAssemblyCache = false; +#if NETFRAMEWORK + if (assembly.GlobalAssemblyCache) + globalAssemblyCache = true; #endif + InternalLogger.Info("{0}. File version: {1}. Product version: {2}. GlobalAssemblyCache: {3}", + assembly.FullName, + fileVersion, + productVersion, + globalAssemblyCache); + } + catch (Exception ex) + { + InternalLogger.Error(ex, "Error logging version of assembly {0}.", assembly?.FullName); + } + } } } diff --git a/src/NLog/Internal/AssemblyMetadataAttribute.cs b/src/NLog/Internal/AssemblyMetadataAttribute.cs index 6c1e46c42b..6856c4fd30 100644 --- a/src/NLog/Internal/AssemblyMetadataAttribute.cs +++ b/src/NLog/Internal/AssemblyMetadataAttribute.cs @@ -32,6 +32,7 @@ // #if NET35 + namespace System.Reflection { [AttributeUsage(AttributeTargets.Assembly)] diff --git a/src/NLog/Internal/AsyncOperationCounter.cs b/src/NLog/Internal/AsyncOperationCounter.cs index 10bbb85b9d..6b6689cbfb 100644 --- a/src/NLog/Internal/AsyncOperationCounter.cs +++ b/src/NLog/Internal/AsyncOperationCounter.cs @@ -43,7 +43,7 @@ namespace NLog.Internal internal sealed class AsyncOperationCounter { private int _pendingOperationCounter; - private readonly LinkedList _pendingCompletionList = new LinkedList(); + private readonly LinkedList _pendingCompletionList = new LinkedList(); /// /// Mark operation has started @@ -57,12 +57,12 @@ public void BeginOperation() /// Mark operation has completed /// /// Exception coming from the completed operation [optional] - public void CompleteOperation(Exception exception) + public void CompleteOperation(Exception? exception) { NotifyCompletion(exception); } - private int NotifyCompletion(Exception exception) + private int NotifyCompletion(Exception? exception) { int pendingOperations = System.Threading.Interlocked.Decrement(ref _pendingOperationCounter); @@ -75,7 +75,7 @@ private int NotifyCompletion(Exception exception) { var nodeValue = nodeNext.Value; nodeNext = nodeNext.Next; - nodeValue(exception); // Will modify _pendingCompletionList + nodeValue?.Invoke(exception); // Will modify _pendingCompletionList } } } @@ -110,7 +110,7 @@ public AsyncContinuation RegisterCompletionNotification(AsyncContinuation asyncC return asyncContinuation; // No active operations } - var pendingCompletion = new LinkedListNode(null); + var pendingCompletion = new LinkedListNode(null); _pendingCompletionList.AddLast(pendingCompletion); remainingCompletionCounter = System.Threading.Interlocked.Increment(ref _pendingOperationCounter); if (remainingCompletionCounter <= 0) diff --git a/src/NLog/Internal/CallSiteInformation.cs b/src/NLog/Internal/CallSiteInformation.cs index 6a71ca1cca..169b480bae 100644 --- a/src/NLog/Internal/CallSiteInformation.cs +++ b/src/NLog/Internal/CallSiteInformation.cs @@ -103,7 +103,7 @@ public static void AddCallSiteHiddenClassType(Type classType) /// The stack trace. /// Index of the first user stack frame within the stack trace. /// Type of the logger or logger wrapper. This is still Logger if it's a subclass of Logger. - public void SetStackTrace(StackTrace stackTrace, int? userStackFrame = null, Type loggerType = null) + public void SetStackTrace(StackTrace stackTrace, int? userStackFrame = null, Type? loggerType = null) { StackTrace = stackTrace; if (!userStackFrame.HasValue && stackTrace != null) @@ -128,7 +128,7 @@ public void SetStackTrace(StackTrace stackTrace, int? userStackFrame = null, Typ /// /// /// - public void SetCallerInfo(string callerClassName, string callerMethodName, string callerFilePath, int callerLineNumber) + public void SetCallerInfo(string? callerClassName, string? callerMethodName, string? callerFilePath, int callerLineNumber) { CallerClassName = callerClassName; CallerMethodName = callerMethodName; @@ -142,7 +142,7 @@ public void SetCallerInfo(string callerClassName, string callerMethodName, strin /// Gets the stack frame of the method that did the logging. /// [Obsolete("Instead use ${callsite} or CallerMemberName. Marked obsolete on NLog 5.3")] - public StackFrame UserStackFrame => StackTrace?.GetFrame(UserStackFrameNumberLegacy ?? UserStackFrameNumber); + public StackFrame? UserStackFrame => StackTrace?.GetFrame(UserStackFrameNumberLegacy ?? UserStackFrameNumber); /// /// Gets the number index of the stack frame that represents the user @@ -158,29 +158,30 @@ public void SetCallerInfo(string callerClassName, string callerMethodName, strin /// /// Gets the entire stack trace. /// - public StackTrace StackTrace { get; private set; } + public StackTrace? StackTrace { get; private set; } - public MethodBase GetCallerStackFrameMethod(int skipFrames) + public MethodBase? GetCallerStackFrameMethod(int skipFrames) { - StackFrame frame = StackTrace?.GetFrame(UserStackFrameNumber + skipFrames); + StackFrame? frame = StackTrace?.GetFrame(UserStackFrameNumber + skipFrames); return StackTraceUsageUtils.GetStackMethod(frame); } - public string GetCallerClassName(MethodBase method, bool includeNameSpace, bool cleanAsyncMoveNext, bool cleanAnonymousDelegates) + public string GetCallerClassName(MethodBase? method, bool includeNameSpace, bool cleanAsyncMoveNext, bool cleanAnonymousDelegates) { - if (!string.IsNullOrEmpty(CallerClassName)) + var callerClassName = CallerClassName ?? string.Empty; + if (!string.IsNullOrEmpty(callerClassName)) { if (includeNameSpace) { - return CallerClassName; + return callerClassName; } else { - int lastDot = CallerClassName.LastIndexOf('.'); - if (lastDot < 0 || lastDot >= CallerClassName.Length - 1) - return CallerClassName; + int lastDot = callerClassName.LastIndexOf('.'); + if (lastDot < 0 || lastDot >= callerClassName.Length - 1) + return callerClassName; else - return CallerClassName.Substring(lastDot + 1); + return callerClassName.Substring(lastDot + 1); } } @@ -193,10 +194,11 @@ public string GetCallerClassName(MethodBase method, bool includeNameSpace, bool return StackTraceUsageUtils.GetStackFrameMethodClassName(method, includeNameSpace, cleanAsyncMoveNext, cleanAnonymousDelegates) ?? string.Empty; } - public string GetCallerMethodName(MethodBase method, bool includeMethodInfo, bool cleanAsyncMoveNext, bool cleanAnonymousDelegates) + public string GetCallerMethodName(MethodBase? method, bool includeMethodInfo, bool cleanAsyncMoveNext, bool cleanAnonymousDelegates) { - if (!string.IsNullOrEmpty(CallerMethodName)) - return CallerMethodName; + var callerMethodName = CallerMethodName ?? string.Empty; + if (!string.IsNullOrEmpty(callerMethodName)) + return callerMethodName; method = method ?? GetCallerStackFrameMethod(0); if (method is null) @@ -209,10 +211,11 @@ public string GetCallerMethodName(MethodBase method, bool includeMethodInfo, boo public string GetCallerFilePath(int skipFrames) { - if (!string.IsNullOrEmpty(CallerFilePath)) - return CallerFilePath; + var callerFilePath = CallerFilePath ?? string.Empty; + if (!string.IsNullOrEmpty(callerFilePath)) + return callerFilePath; - StackFrame frame = StackTrace?.GetFrame(UserStackFrameNumber + skipFrames); + StackFrame? frame = StackTrace?.GetFrame(UserStackFrameNumber + skipFrames); return frame?.GetFileName() ?? string.Empty; } @@ -221,13 +224,13 @@ public int GetCallerLineNumber(int skipFrames) if (CallerLineNumber.HasValue) return CallerLineNumber.Value; - StackFrame frame = StackTrace?.GetFrame(UserStackFrameNumber + skipFrames); + StackFrame? frame = StackTrace?.GetFrame(UserStackFrameNumber + skipFrames); return frame?.GetFileLineNumber() ?? 0; } - public string CallerClassName { get; internal set; } - public string CallerMethodName { get; private set; } - public string CallerFilePath { get; private set; } + public string? CallerClassName { get; internal set; } + public string? CallerMethodName { get; private set; } + public string? CallerFilePath { get; private set; } public int? CallerLineNumber { get; private set; } /// @@ -302,7 +305,7 @@ private static int SkipToUserStackFrameLegacy(StackFrame[] stackFrames, int firs /// /// Skip StackFrame when from hidden Assembly / ClassType /// - private static bool SkipStackFrameWhenHidden(MethodBase stackMethod) + private static bool SkipStackFrameWhenHidden(MethodBase? stackMethod) { var assembly = StackTraceUsageUtils.LookupAssemblyFromMethod(stackMethod); if (assembly is null || IsHiddenAssembly(assembly)) @@ -316,9 +319,9 @@ private static bool SkipStackFrameWhenHidden(MethodBase stackMethod) /// /// Skip StackFrame when type of the logger /// - private static bool SkipStackFrameWhenLoggerType(MethodBase stackMethod, Type loggerType) + private static bool SkipStackFrameWhenLoggerType(MethodBase? stackMethod, Type loggerType) { - Type declaringType = stackMethod?.DeclaringType; + Type? declaringType = stackMethod?.DeclaringType; var isLoggerType = declaringType != null && (loggerType == declaringType || declaringType.IsSubclassOf(loggerType) || loggerType.IsAssignableFrom(declaringType)); return isLoggerType; } diff --git a/src/NLog/Internal/CollectionExtensions.cs b/src/NLog/Internal/CollectionExtensions.cs index c5ea21c2e4..15442d35b2 100644 --- a/src/NLog/Internal/CollectionExtensions.cs +++ b/src/NLog/Internal/CollectionExtensions.cs @@ -31,23 +31,21 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Collections.Generic; -using JetBrains.Annotations; - namespace NLog.Internal { + using System; + using System.Collections.Generic; + internal static class CollectionExtensions { /// /// Memory optimized filtering /// /// Passing state too avoid delegate capture and memory-allocations. - [NotNull] - public static IList Filter([NotNull] this IList items, TState state, Func filter) + public static IList Filter(this IList items, TState state, Func filter) { var hasIgnoredLogEvents = false; - IList filterLogEvents = null; + IList? filterLogEvents = null; for (var i = 0; i < items.Count; ++i) { var item = items[i]; diff --git a/src/NLog/Internal/ConfigVariablesDictionary.cs b/src/NLog/Internal/ConfigVariablesDictionary.cs index 01567eca31..ea941c56ef 100644 --- a/src/NLog/Internal/ConfigVariablesDictionary.cs +++ b/src/NLog/Internal/ConfigVariablesDictionary.cs @@ -31,22 +31,22 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using NLog.Config; -using NLog.Layouts; - namespace NLog.Internal { + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using NLog.Config; + using NLog.Layouts; + [DebuggerDisplay("Count = {Count}")] internal sealed class ConfigVariablesDictionary : IDictionary { private readonly ThreadSafeDictionary _variables = new ThreadSafeDictionary(StringComparer.OrdinalIgnoreCase); private readonly LoggingConfiguration _configuration; - private ThreadSafeDictionary _dynamicVariables; - private ThreadSafeDictionary _apiVariables; + private ThreadSafeDictionary? _dynamicVariables; + private ThreadSafeDictionary? _apiVariables; public ConfigVariablesDictionary(LoggingConfiguration configuration) { @@ -84,9 +84,8 @@ public bool TryLookupDynamicVariable(string key, out Layout dynamicLayout) if (dynamicLayout != null) { dynamicLayout.Initialize(_configuration); + _dynamicVariables[key] = dynamicLayout; } - - _dynamicVariables[key] = dynamicLayout; } } diff --git a/src/NLog/Internal/DictionaryEntryEnumerable.cs b/src/NLog/Internal/DictionaryEntryEnumerable.cs index 7cb32a79ca..75fd0cb437 100644 --- a/src/NLog/Internal/DictionaryEntryEnumerable.cs +++ b/src/NLog/Internal/DictionaryEntryEnumerable.cs @@ -31,27 +31,31 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Collections; -using System.Collections.Generic; - namespace NLog.Internal { + using System; + using System.Collections; + using System.Collections.Generic; + /// /// Ensures that IDictionary.GetEnumerator returns DictionaryEntry values /// - internal struct DictionaryEntryEnumerable : IEnumerable + internal +#if !NETFRAMEWORK + readonly +#endif + struct DictionaryEntryEnumerable : IEnumerable { - private readonly IDictionary _dictionary; + private readonly IDictionary? _dictionary; - public DictionaryEntryEnumerable(IDictionary dictionary) + public DictionaryEntryEnumerable(IDictionary? dictionary) { _dictionary = dictionary; } public DictionaryEntryEnumerator GetEnumerator() { - return new DictionaryEntryEnumerator(_dictionary?.Count > 0 ? _dictionary : null); + return new DictionaryEntryEnumerator(_dictionary); } IEnumerator IEnumerable.GetEnumerator() @@ -64,15 +68,19 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - internal struct DictionaryEntryEnumerator : IEnumerator + internal +#if !NETFRAMEWORK + readonly +#endif + struct DictionaryEntryEnumerator : IEnumerator { private readonly IDictionaryEnumerator _entryEnumerator; public DictionaryEntry Current => _entryEnumerator.Entry; - public DictionaryEntryEnumerator(IDictionary dictionary) + public DictionaryEntryEnumerator(IDictionary? dictionary) { - _entryEnumerator = dictionary?.GetEnumerator(); + _entryEnumerator = dictionary?.Count > 0 ? dictionary.GetEnumerator() : EmptyDictionaryEnumerator.Default; } object IEnumerator.Current => Current; @@ -85,12 +93,26 @@ public void Dispose() public bool MoveNext() { - return _entryEnumerator?.MoveNext() ?? false; + return _entryEnumerator.MoveNext(); + } + + public void Reset() + { + _entryEnumerator.Reset(); } + } + private sealed class EmptyDictionaryEnumerator : IDictionaryEnumerator + { + public static readonly IDictionaryEnumerator Default = new EmptyDictionaryEnumerator(); + public DictionaryEntry Entry => default; + public object? Key => default; + public object? Value => default; + object? IEnumerator.Current => Entry; + public bool MoveNext() => false; public void Reset() { - _entryEnumerator?.Reset(); + // SONAR: Nothing to reset } } } diff --git a/src/NLog/Internal/DynamicallyAccessedMemberTypes.cs b/src/NLog/Internal/DynamicallyAccessedMemberTypes.cs index 8b1287eb60..96274ea008 100644 --- a/src/NLog/Internal/DynamicallyAccessedMemberTypes.cs +++ b/src/NLog/Internal/DynamicallyAccessedMemberTypes.cs @@ -198,7 +198,7 @@ public UnconditionalSuppressMessageAttribute(string category, string checkId) /// The Scope property is an optional argument that specifies the metadata scope for which /// the attribute is relevant. /// - public string Scope { get; set; } + public string? Scope { get; set; } /// /// Gets or sets a fully qualified path that represents the target of the attribute. @@ -209,7 +209,7 @@ public UnconditionalSuppressMessageAttribute(string category, string checkId) /// Because it is fully qualified, it can be long, particularly for targets such as parameters. /// The analysis tool user interface should be capable of automatically formatting the parameter. /// - public string Target { get; set; } + public string? Target { get; set; } /// /// Gets or sets an optional argument expanding on exclusion criteria. @@ -221,12 +221,47 @@ public UnconditionalSuppressMessageAttribute(string category, string checkId) /// and it may be desirable to suppress a violation against a statement in the method that will /// give a rule violation, but not against all statements in the method. /// - public string MessageId { get; set; } + public string? MessageId { get; set; } /// /// Gets or sets the justification for suppressing the code analysis message. /// - public string Justification { get; set; } + public string? Justification { get; set; } + } + + /// + /// Indicates that the specified method requires dynamic access to code that is not referenced + /// statically, for example through . + /// + /// + /// This allows tools to understand which methods are unsafe to call when removing unreferenced + /// code from an application. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] + internal sealed class RequiresUnreferencedCodeAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + /// + /// A message that contains information about the usage of unreferenced code. + /// + public RequiresUnreferencedCodeAttribute(string message) + { + Message = message; + } + + /// + /// Gets a message that contains information about the usage of unreferenced code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires unreferenced code, and what options a consumer has to deal with it. + /// + public string? Url { get; set; } } } diff --git a/src/NLog/Internal/EnvironmentHelper.cs b/src/NLog/Internal/EnvironmentHelper.cs index 8c5563e20f..57aac223f5 100644 --- a/src/NLog/Internal/EnvironmentHelper.cs +++ b/src/NLog/Internal/EnvironmentHelper.cs @@ -41,26 +41,11 @@ namespace NLog.Internal /// internal static class EnvironmentHelper { - internal static string NewLine - { - get - { - return Environment.NewLine; - } - } - internal static string GetMachineName() { try { -#if NETSTANDARD1_3 - var machineName = EnvironmentHelper.GetSafeEnvironmentVariable("COMPUTERNAME") ?? string.Empty; - if (string.IsNullOrEmpty(machineName)) - machineName = EnvironmentHelper.GetSafeEnvironmentVariable("HOSTNAME") ?? string.Empty; - return machineName; -#else return Environment.MachineName; -#endif } catch (System.Security.SecurityException) { @@ -72,18 +57,11 @@ internal static string GetSafeEnvironmentVariable(string name) { try { - string s = Environment.GetEnvironmentVariable(name); - - if (string.IsNullOrEmpty(s)) - { - return null; - } - - return s; + return Environment.GetEnvironmentVariable(name) ?? string.Empty; } catch (SecurityException) { - return null; + return string.Empty; } } } diff --git a/src/NLog/Internal/ExceptionHelper.cs b/src/NLog/Internal/ExceptionHelper.cs index 8dbdc5b932..277e9f2537 100644 --- a/src/NLog/Internal/ExceptionHelper.cs +++ b/src/NLog/Internal/ExceptionHelper.cs @@ -61,7 +61,7 @@ public static void MarkAsLoggedToInternalLogger(this Exception exception) /// Is this exception logged to the ? /// /// - /// trueif the has been logged to the . + /// if the has been logged to the . public static bool IsLoggedToInternalLogger(this Exception exception) { if (exception?.Data?.Count > 0) @@ -72,15 +72,15 @@ public static bool IsLoggedToInternalLogger(this Exception exception) } /// - /// Determines whether the exception must be rethrown and logs the error to the if is false. + /// Determines whether the exception must be rethrown and logs the error to the if is . /// /// Advised to log first the error to the before calling this method. /// /// The exception to check. /// Target Object context of the exception. /// Target Method context of the exception. - /// trueif the must be rethrown, false otherwise. - public static bool MustBeRethrown(this Exception exception, IInternalLoggerContext loggerContext = null, string callerMemberName = null) + /// if the must be rethrown, otherwise. + public static bool MustBeRethrown(this Exception exception, IInternalLoggerContext? loggerContext = null, string? callerMemberName = null) { if (exception.MustBeRethrownImmediately()) { @@ -119,10 +119,9 @@ public static bool MustBeRethrown(this Exception exception, IInternalLoggerConte /// Only used this method in special cases. /// /// The exception to check. - /// trueif the must be rethrown, false otherwise. + /// if the must be rethrown, otherwise. public static bool MustBeRethrownImmediately(this Exception exception) { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 if (exception is StackOverflowException) { return true; // StackOverflowException cannot be caught since .NetFramework 2.0 @@ -132,7 +131,6 @@ public static bool MustBeRethrownImmediately(this Exception exception) { return true; // ThreadAbortException will automatically be rethrown at end of catch-block } -#endif if (exception is OutOfMemoryException) { diff --git a/src/NLog/Internal/ExceptionMessageFormatProvider.cs b/src/NLog/Internal/ExceptionMessageFormatProvider.cs index 3bddd69dcd..c0b596ddcd 100644 --- a/src/NLog/Internal/ExceptionMessageFormatProvider.cs +++ b/src/NLog/Internal/ExceptionMessageFormatProvider.cs @@ -77,7 +77,7 @@ private static Exception GetPrimaryException(Exception exception) return exception; } - object IFormatProvider.GetFormat(Type formatType) + object? IFormatProvider.GetFormat(Type formatType) { return (formatType == typeof(ICustomFormatter)) ? this : null; } diff --git a/src/NLog/Internal/FakeAppDomain.cs b/src/NLog/Internal/FakeAppDomain.cs deleted file mode 100644 index 5a6883d324..0000000000 --- a/src/NLog/Internal/FakeAppDomain.cs +++ /dev/null @@ -1,191 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -#if NETSTANDARD1_3 || NETSTANDARD1_5 - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace NLog.Internal.Fakeables -{ - [Obsolete("For unit testing only. Marked obsolete on NLog 5.0")] - internal sealed class FakeAppDomain : IAppDomain - { -#if NETSTANDARD1_5 - private readonly System.Runtime.Loader.AssemblyLoadContext _defaultContext; -#endif - - /// Initializes a new instance of the class. - public FakeAppDomain() - { - BaseDirectory = AppContext.BaseDirectory; -#if NETSTANDARD1_5 - _defaultContext = System.Runtime.Loader.AssemblyLoadContext.Default; - - try - { - FriendlyName = GetFriendlyNameFromEntryAssembly() ?? GetFriendlyNameFromProcessName() ?? "UnknownAppDomain"; - } - catch - { - FriendlyName = "UnknownAppDomain"; - } -#endif - } - - #region Implementation of IAppDomain - -#if NETSTANDARD1_5 - private static string GetFriendlyNameFromEntryAssembly() - { - try - { - string assemblyName = Assembly.GetEntryAssembly()?.GetName()?.Name; - return string.IsNullOrEmpty(assemblyName) ? null : assemblyName; - } - catch - { - return null; - } - } - - private static string GetFriendlyNameFromProcessName() - { - try - { - string processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; - return string.IsNullOrEmpty(processName) ? null : processName; - } - catch - { - return null; - } - } -#endif - - /// - /// Gets or sets the base directory that the assembly resolver uses to probe for assemblies. - /// - public string BaseDirectory { get; private set; } - - /// - /// Gets or sets the name of the configuration file for an application domain. - /// - public string ConfigurationFile { get; set; } - - /// - /// Gets or sets the list of directories under the application base directory that are probed for private assemblies. - /// - public IEnumerable PrivateBinPath { get; set; } - - /// - /// Gets or set the friendly name. - /// - public string FriendlyName { get; set; } - - /// - /// Gets an integer that uniquely identifies the application domain within the process. - /// - public int Id { get; set; } - - /// - /// Gets the assemblies that have been loaded into the execution context of this application domain. - /// - /// A list of assemblies in this application domain. - public IEnumerable GetAssemblies() - { - return Internal.ArrayHelper.Empty(); - } - - /// - /// Process exit event. - /// - public event EventHandler ProcessExit - { - add - { -#if NETSTANDARD1_5 - if (_contextUnloadingEvent == null && _defaultContext != null) - _defaultContext.Unloading += OnContextUnloading; - _contextUnloadingEvent += value; -#endif - } - remove - { -#if NETSTANDARD1_5 - _contextUnloadingEvent -= value; - if (_contextUnloadingEvent == null && _defaultContext != null) - _defaultContext.Unloading -= OnContextUnloading; -#endif - } - } - - /// - /// Domain unloaded event. - /// - public event EventHandler DomainUnload - { - add - { -#if NETSTANDARD1_5 - if (_contextUnloadingEvent == null && _defaultContext != null) - _defaultContext.Unloading += OnContextUnloading; - _contextUnloadingEvent += value; -#endif - } - remove - { -#if NETSTANDARD1_5 - _contextUnloadingEvent -= value; - if (_contextUnloadingEvent == null && _defaultContext != null) - _defaultContext.Unloading -= OnContextUnloading; -#endif - } - } - -#if NETSTANDARD1_5 - private event EventHandler _contextUnloadingEvent; - - private void OnContextUnloading(System.Runtime.Loader.AssemblyLoadContext context) - { - var handler = _contextUnloadingEvent; - if (handler != null) handler.Invoke(context, EventArgs.Empty); - } -#endif -#endregion - } -} - -#endif diff --git a/src/NLog/Internal/FileInfoHelper.cs b/src/NLog/Internal/FileInfoHelper.cs index 4c57775f22..9db8ab3dd7 100644 --- a/src/NLog/Internal/FileInfoHelper.cs +++ b/src/NLog/Internal/FileInfoHelper.cs @@ -31,13 +31,19 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; - namespace NLog.Internal { + using System; + using System.IO; + internal static class FileInfoHelper { - internal static DateTime? LookupValidFileCreationTimeUtc(T fileInfo, Func primary, Func fallback, Func finalFallback = null) + public static DateTime? LookupValidFileCreationTimeUtc(this FileInfo fileInfo) + { + return LookupValidFileCreationTimeUtc(fileInfo, (f) => f.CreationTimeUtc, (f) => f.LastWriteTimeUtc); + } + + private static DateTime? LookupValidFileCreationTimeUtc(T fileInfo, Func primary, Func fallback, Func? finalFallback = null) { DateTime? fileCreationTime = primary(fileInfo); @@ -52,5 +58,25 @@ internal static class FileInfoHelper } return fileCreationTime; } + + public static bool IsRelativeFilePath(string filepath) + { + filepath = filepath?.TrimStart(ArrayHelper.Empty()) ?? string.Empty; + if (string.IsNullOrEmpty(filepath)) + return false; + + var firstchar = filepath[0]; + if (firstchar == Path.DirectorySeparatorChar || firstchar == Path.AltDirectorySeparatorChar) + return false; + + if (firstchar == '.') + return true; + + //on unix VolumeSeparatorChar == DirectorySeparatorChar + if (filepath.Length >= 2 && filepath[1] == Path.VolumeSeparatorChar && Path.VolumeSeparatorChar != Path.DirectorySeparatorChar) + return false; + + return true; + } } } diff --git a/src/NLog/Internal/FormatHelper.cs b/src/NLog/Internal/FormatHelper.cs index cb0b1edf89..d8f37550dd 100644 --- a/src/NLog/Internal/FormatHelper.cs +++ b/src/NLog/Internal/FormatHelper.cs @@ -40,51 +40,44 @@ internal static class FormatHelper /// /// Convert object to string /// - /// value + /// value /// format for conversion. /// /// - /// If is null and isn't a already, then the will get a locked by + /// If is null and isn't a already, then the will get a locked by /// - internal static string ConvertToString(object o, IFormatProvider formatProvider) + internal static string ConvertToString(object? value, IFormatProvider? formatProvider) { + if (value is string stringValue) + return stringValue; + if (value is null) + return string.Empty; + // if no IFormatProvider is specified, use the Configuration.DefaultCultureInfo value. if (formatProvider is null) { - if (SkipFormattableToString(o)) - return o?.ToString() ?? string.Empty; - - if (o is IFormattable) + if (value is IFormattable) { formatProvider = LogManager.LogFactory.DefaultCultureInfo; } } - return Convert.ToString(o, formatProvider); + return Convert.ToString(value, formatProvider); } - private static bool SkipFormattableToString(object value) + internal static string TryFormatToString(object? value, string? format, IFormatProvider? formatProvider) { - switch (Convert.GetTypeCode(value)) - { - case TypeCode.String: return true; - case TypeCode.Empty: return true; - default: return false; - } - } - - internal static string TryFormatToString(object value, string format, IFormatProvider formatProvider) - { - if (SkipFormattableToString(value)) - return value?.ToString() ?? string.Empty; - if (value is IFormattable formattable) { return formattable.ToString(format, formatProvider); } + else if (value is IConvertible convertible) + { + return convertible.ToString(formatProvider); + } else if (value is System.Collections.IEnumerable) { - return null; + return string.Empty; } else { diff --git a/src/NLog/Internal/Guard.cs b/src/NLog/Internal/Guard.cs index 1462834295..a7c1b52d43 100644 --- a/src/NLog/Internal/Guard.cs +++ b/src/NLog/Internal/Guard.cs @@ -32,6 +32,7 @@ // #if !NETCOREAPP3_1_OR_GREATER + namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Parameter)] diff --git a/src/NLog/Internal/IAppEnvironment.cs b/src/NLog/Internal/IAppEnvironment.cs index 2c1fabb164..ff2d09191e 100644 --- a/src/NLog/Internal/IAppEnvironment.cs +++ b/src/NLog/Internal/IAppEnvironment.cs @@ -47,9 +47,6 @@ internal interface IAppEnvironment : IFileSystem int AppDomainId { get; } IEnumerable AppDomainPrivateBinPath { get; } IEnumerable GetAppDomainRuntimeAssemblies(); - [Obsolete("Marked obsolete on NLog 5.0")] - IAppDomain AppDomain { get; } -#if !NETSTANDARD1_3 string CurrentProcessFilePath { get; } /// /// Gets current process name (excluding filename extension, if any). @@ -58,12 +55,11 @@ internal interface IAppEnvironment : IFileSystem int CurrentProcessId { get; } string EntryAssemblyLocation { get; } string EntryAssemblyFileName { get; } -#endif string UserTempFilePath { get; } /// /// Process exit event. /// - event EventHandler ProcessExit; + event EventHandler ProcessExit; } } diff --git a/src/NLog/Internal/IFileSystem.cs b/src/NLog/Internal/IFileSystem.cs index f8e5578574..0f04118cce 100644 --- a/src/NLog/Internal/IFileSystem.cs +++ b/src/NLog/Internal/IFileSystem.cs @@ -33,7 +33,7 @@ namespace NLog.Internal.Fakeables { - using System.Xml; + using System.IO; /// /// Abstract calls to FileSystem @@ -43,8 +43,8 @@ internal interface IFileSystem /// Determines whether the specified file exists. /// The file to check. bool FileExists(string path); - /// Returns the content of the specified file + /// Returns the content of the specified text-file /// The file to load. - XmlReader LoadXmlFile(string path); + TextReader LoadTextFile(string path); } } diff --git a/src/NLog/Internal/ILogMessageFormatter.cs b/src/NLog/Internal/ILogMessageFormatter.cs index 53981628f4..7fcb19cfa6 100644 --- a/src/NLog/Internal/ILogMessageFormatter.cs +++ b/src/NLog/Internal/ILogMessageFormatter.cs @@ -41,7 +41,10 @@ namespace NLog.Internal internal interface ILogMessageFormatter { /// - /// Perform message template parsing and formatting of LogEvent messages (True = Always, False = Never, Null = Auto Detect) + /// Perform message template parsing and formatting of LogEvent messages: + /// - = Always + /// - = Never + /// - = Auto Detect /// bool? EnableMessageTemplateParser { get; } @@ -56,7 +59,7 @@ internal interface ILogMessageFormatter /// Has the logevent properties? /// /// LogEvent with message to be formatted - /// False when logevent has no properties to be extracted + /// when logevent has no properties to be extracted bool HasProperties(LogEventInfo logEvent); /// diff --git a/src/NLog/Internal/IRawValue.cs b/src/NLog/Internal/IRawValue.cs index caeeeb22b6..3f1c503b3a 100644 --- a/src/NLog/Internal/IRawValue.cs +++ b/src/NLog/Internal/IRawValue.cs @@ -47,6 +47,6 @@ internal interface IRawValue /// /// The value /// RawValue supported? - bool TryGetRawValue(LogEventInfo logEvent, out object value); + bool TryGetRawValue(LogEventInfo logEvent, out object? value); } } diff --git a/src/NLog/Internal/IStringValueRenderer.cs b/src/NLog/Internal/IStringValueRenderer.cs index 61e975f710..c186de99cf 100644 --- a/src/NLog/Internal/IStringValueRenderer.cs +++ b/src/NLog/Internal/IStringValueRenderer.cs @@ -46,6 +46,6 @@ interface IStringValueRenderer /// /// /// null if not possible or unknown - string GetFormattedString(LogEventInfo logEvent); + string? GetFormattedString(LogEventInfo logEvent); } } diff --git a/src/NLog/Internal/ISupportsInitialize.cs b/src/NLog/Internal/ISupportsInitialize.cs index 581a21b947..c73d30cac9 100644 --- a/src/NLog/Internal/ISupportsInitialize.cs +++ b/src/NLog/Internal/ISupportsInitialize.cs @@ -33,7 +33,7 @@ namespace NLog.Internal { - using Config; + using NLog.Config; /// /// Supports object initialization and termination. @@ -43,8 +43,7 @@ internal interface ISupportsInitialize /// /// Initializes this instance. /// - /// The configuration. - void Initialize(LoggingConfiguration configuration); + void Initialize(LoggingConfiguration? configuration); /// /// Closes this instance. diff --git a/src/NLog/Internal/LogMessageTemplateFormatter.cs b/src/NLog/Internal/LogMessageTemplateFormatter.cs index ed810fca58..45d2115554 100644 --- a/src/NLog/Internal/LogMessageTemplateFormatter.cs +++ b/src/NLog/Internal/LogMessageTemplateFormatter.cs @@ -37,34 +37,33 @@ namespace NLog.Internal using System.Collections.Generic; using System.Globalization; using System.Text; - using JetBrains.Annotations; using MessageTemplates; using NLog.Config; internal sealed class LogMessageTemplateFormatter : ILogMessageFormatter { private static readonly StringBuilderPool _builderPool = new StringBuilderPool(Environment.ProcessorCount * 2); - private readonly IServiceProvider _serviceProvider; + private readonly LogFactory _logFactory; - private IValueFormatter ValueFormatter => _valueFormatter ?? (_valueFormatter = _serviceProvider.GetService()); - private IValueFormatter _valueFormatter; + private IValueFormatter ValueFormatter => _valueFormatter ?? (_valueFormatter = _logFactory.ServiceRepository.GetService()); + private IValueFormatter? _valueFormatter; /// - /// When true: Do not fallback to StringBuilder.Format for positional templates + /// When Do not fallback to StringBuilder.Format for positional templates /// - private readonly bool _forceTemplateRenderer; + private readonly bool _forceMessageTemplateRenderer; private readonly bool _singleTargetOnly; /// /// New formatter /// - /// - /// When true: Do not fallback to StringBuilder.Format for positional templates + /// + /// When Do not fallback to StringBuilder.Format for positional templates /// - public LogMessageTemplateFormatter([NotNull] IServiceProvider serviceProvider, bool forceTemplateRenderer, bool singleTargetOnly) + public LogMessageTemplateFormatter(LogFactory logFactory, bool forceMessageTemplateRenderer, bool singleTargetOnly) { - _serviceProvider = serviceProvider; - _forceTemplateRenderer = forceTemplateRenderer; + _logFactory = logFactory; + _forceMessageTemplateRenderer = forceMessageTemplateRenderer; _singleTargetOnly = singleTargetOnly; MessageFormatter = FormatMessage; } @@ -74,7 +73,7 @@ public LogMessageTemplateFormatter([NotNull] IServiceProvider serviceProvider, b /// public LogMessageFormatter MessageFormatter { get; } - public bool? EnableMessageTemplateParser => _forceTemplateRenderer ? true : default(bool?); + public bool? EnableMessageTemplateParser => _forceMessageTemplateRenderer ? true : default(bool?); /// public bool HasProperties(LogEventInfo logEvent) @@ -86,7 +85,7 @@ public bool HasProperties(LogEventInfo logEvent) { // Perform quick check for valid message template parameter names (No support for rewind if mixed message-template) TemplateEnumerator holeEnumerator = new TemplateEnumerator(logEvent.Message); - if (holeEnumerator.MoveNext() && holeEnumerator.Current.MaybePositionalTemplate) + if (!holeEnumerator.MoveNext() || holeEnumerator.Current.MaybePositionalTemplate) { return false; // Skip allocation of PropertiesDictionary } @@ -97,9 +96,9 @@ public bool HasProperties(LogEventInfo logEvent) public void AppendFormattedMessage(LogEventInfo logEvent, StringBuilder builder) { - if (_singleTargetOnly) + if (_singleTargetOnly && logEvent.Parameters?.Length > 0) { - Render(logEvent.Message, logEvent.FormatProvider ?? CultureInfo.CurrentCulture, logEvent.Parameters, builder, out _); + Render(logEvent.Message, logEvent.FormatProvider ?? _logFactory.DefaultCultureInfo ?? CultureInfo.CurrentCulture, logEvent.Parameters, builder, out _); } else { @@ -109,11 +108,12 @@ public void AppendFormattedMessage(LogEventInfo logEvent, StringBuilder builder) public string FormatMessage(LogEventInfo logEvent) { - if (LogMessageStringFormatter.HasParameters(logEvent)) + var parameters = logEvent.Parameters; + if (parameters?.Length > 0 && !string.IsNullOrEmpty(logEvent.Message)) { using (var builder = _builderPool.Acquire()) { - AppendToBuilder(logEvent, builder.Item); + AppendToBuilder(logEvent, parameters, builder.Item); return builder.Item.ToString(); } } @@ -121,10 +121,10 @@ public string FormatMessage(LogEventInfo logEvent) return logEvent.Message; } - private void AppendToBuilder(LogEventInfo logEvent, StringBuilder builder) + private void AppendToBuilder(LogEventInfo logEvent, object?[] parameters, StringBuilder builder) { - Render(logEvent.Message, logEvent.FormatProvider ?? CultureInfo.CurrentCulture, logEvent.Parameters, builder, out var messageTemplateParameterList); - logEvent.CreateOrUpdatePropertiesInternal(false, messageTemplateParameterList ?? ArrayHelper.Empty()); + Render(logEvent.Message, logEvent.FormatProvider ?? _logFactory.DefaultCultureInfo ?? CultureInfo.CurrentCulture, parameters, builder, out var messageTemplateParameterList); + logEvent.TryCreatePropertiesInternal(messageTemplateParameterList ?? ArrayHelper.Empty()); } /// @@ -135,41 +135,105 @@ private void AppendToBuilder(LogEventInfo logEvent, StringBuilder builder) /// Parameters for the holes. /// The String Builder destination. /// Parameters for the holes. - private void Render(string template, IFormatProvider formatProvider, object[] parameters, StringBuilder sb, out IList messageTemplateParameters) + private void Render(string template, IFormatProvider? formatProvider, object?[] parameters, StringBuilder sb, out IList? messageTemplateParameters) { + messageTemplateParameters = null; + + TemplateEnumerator templateEnumerator = new TemplateEnumerator(template); + if (!templateEnumerator.MoveNext() || (templateEnumerator.Current.MaybePositionalTemplate && !_forceMessageTemplateRenderer)) + { + // string.Format when not message-template for structured logging + sb.AppendFormat(formatProvider, template, parameters); + return; + } + + // Handle message-template-format or string-format or mixed-format int pos = 0; int holeIndex = 0; int holeStartPosition = 0; - messageTemplateParameters = null; int originalLength = sb.Length; - TemplateEnumerator templateEnumerator = new TemplateEnumerator(template); - while (templateEnumerator.MoveNext()) + do { - if (holeIndex == 0 && !_forceTemplateRenderer && templateEnumerator.Current.MaybePositionalTemplate && sb.Length == originalLength) - { - // Not a structured template - sb.AppendFormat(formatProvider, template, parameters); - return; - } - var literal = templateEnumerator.Current.Literal; sb.Append(template, pos, literal.Print); pos += literal.Print; if (literal.Skip == 0) { pos++; + continue; + } + + pos += literal.Skip; + var hole = templateEnumerator.Current.Hole; + if (hole.Alignment != 0) + holeStartPosition = sb.Length; + if (hole.Index != -1 && messageTemplateParameters is null) + { + holeIndex++; + RenderHolePositional(sb, hole, formatProvider, parameters[hole.Index]); } else { + var holeParameter = parameters[holeIndex]; + if (messageTemplateParameters is null) + { + messageTemplateParameters = new MessageTemplateParameter[parameters.Length]; + if (holeIndex != 0) + { + // rewind and try again + templateEnumerator = new TemplateEnumerator(template); + sb.Length = originalLength; + holeIndex = 0; + pos = 0; + continue; + } + } + messageTemplateParameters[holeIndex++] = new MessageTemplateParameter(hole.Name, holeParameter, hole.Format, hole.CaptureType); + RenderHole(sb, hole, formatProvider, holeParameter); + } + if (hole.Alignment != 0) + RenderPadding(sb, hole.Alignment, holeStartPosition); + } while (templateEnumerator.MoveNext()); + + messageTemplateParameters = VerifyMessageTemplateParameters(messageTemplateParameters, holeIndex); + } + +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + internal string Render(ref TemplateEnumerator templateEnumerator, IFormatProvider? formatProvider, in ReadOnlySpan parameters, out IList? messageTemplateParameters) + { + // Handle message-template-format or string-format or mixed-format + messageTemplateParameters = null; + + using (var builder = _builderPool.Acquire()) + { + var sb = builder.Item; + + string template = templateEnumerator.Template; + int pos = 0; + int holeStartPosition = 0; + int holeIndex = 0; + + do + { + var literal = templateEnumerator.Current.Literal; + sb.Append(template, pos, literal.Print); + pos += literal.Print; + if (literal.Skip == 0) + { + pos++; + continue; + } + pos += literal.Skip; var hole = templateEnumerator.Current.Hole; + if (hole.Alignment != 0) holeStartPosition = sb.Length; if (hole.Index != -1 && messageTemplateParameters is null) { holeIndex++; - RenderHole(sb, hole, formatProvider, parameters[hole.Index], true); + RenderHolePositional(sb, hole, formatProvider, parameters[hole.Index]); } else { @@ -181,7 +245,7 @@ private void Render(string template, IFormatProvider formatProvider, object[] pa { // rewind and try again templateEnumerator = new TemplateEnumerator(template); - sb.Length = originalLength; + sb.ClearBuilder(); holeIndex = 0; pos = 0; continue; @@ -192,9 +256,16 @@ private void Render(string template, IFormatProvider formatProvider, object[] pa } if (hole.Alignment != 0) RenderPadding(sb, hole.Alignment, holeStartPosition); - } + } while (templateEnumerator.MoveNext()); + + messageTemplateParameters = VerifyMessageTemplateParameters(messageTemplateParameters, holeIndex); + return sb.ToString(); } + } +#endif + private static IList? VerifyMessageTemplateParameters(IList? messageTemplateParameters, int holeIndex) + { if (messageTemplateParameters != null && holeIndex != messageTemplateParameters.Count) { var truncateParameters = new MessageTemplateParameter[holeIndex]; @@ -202,24 +273,36 @@ private void Render(string template, IFormatProvider formatProvider, object[] pa truncateParameters[i] = messageTemplateParameters[i]; messageTemplateParameters = truncateParameters; } - } - private void RenderHole(StringBuilder sb, Hole hole, IFormatProvider formatProvider, object value, bool legacy = false) - { - RenderHole(sb, hole.CaptureType, hole.Format, formatProvider, value, legacy); + return messageTemplateParameters; } - private void RenderHole(StringBuilder sb, CaptureType captureType, string holeFormat, IFormatProvider formatProvider, object value, bool legacy = false) + private void RenderHolePositional(StringBuilder sb, in Hole hole, IFormatProvider? formatProvider, object? value) { if (value is null) { sb.Append("NULL"); - return; } + else if (hole.CaptureType == CaptureType.Normal) + { + MessageTemplates.ValueFormatter.FormatToString(value, hole.Format, formatProvider, sb); + } + else + { + RenderHole(sb, hole.CaptureType, hole.Format, formatProvider, value); + } + } - if (captureType == CaptureType.Normal && legacy) + private void RenderHole(StringBuilder sb, in Hole hole, IFormatProvider? formatProvider, object? value) + { + RenderHole(sb, hole.CaptureType, hole.Format, formatProvider, value); + } + + private void RenderHole(StringBuilder sb, CaptureType captureType, string? holeFormat, IFormatProvider? formatProvider, object? value) + { + if (value is null) { - MessageTemplates.ValueFormatter.FormatToString(value, holeFormat, formatProvider, sb); + sb.Append("NULL"); } else { diff --git a/src/NLog/Internal/MruCache.cs b/src/NLog/Internal/MruCache.cs index 2a5a13c458..f6d68cfd8b 100644 --- a/src/NLog/Internal/MruCache.cs +++ b/src/NLog/Internal/MruCache.cs @@ -31,10 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System.Collections.Generic; - namespace NLog.Internal { + using System.Collections.Generic; + /// /// Most-Recently-Used-Cache, that discards less frequently used items on overflow /// @@ -60,7 +60,7 @@ public MruCache(int maxCapacity) /// /// Key of the item to be inserted in the cache. /// Value of the item to be inserted in the cache. - /// true when the key does not already exist in the cache, false otherwise. + /// when the key does not already exist in the cache, otherwise. public bool TryAddValue(TKey key, TValue value) { lock (_dictionary) @@ -151,8 +151,8 @@ private void PruneCache() /// /// Key of the item to be searched in the cache. /// Output value of the item found in the cache. - /// True when the key is found in the cache, false otherwise. - public bool TryGetValue(TKey key, out TValue value) + /// when the key is found in the cache, otherwise. + public bool TryGetValue(TKey key, out TValue? value) { MruCacheItem item; try @@ -197,6 +197,10 @@ public bool TryGetValue(TKey key, out TValue value) return true; } + private +#if !NETFRAMEWORK + readonly +#endif struct MruCacheItem { public readonly TValue Value; diff --git a/src/NLog/Internal/MultiFileWatcher.cs b/src/NLog/Internal/MultiFileWatcher.cs index 8b34a2ccd9..55112b9a25 100644 --- a/src/NLog/Internal/MultiFileWatcher.cs +++ b/src/NLog/Internal/MultiFileWatcher.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.Internal { using System; @@ -56,7 +54,7 @@ internal sealed class MultiFileWatcher : IDisposable /// /// Occurs when a change is detected in one of the monitored files. /// - public event FileSystemEventHandler FileChanged; + public event FileSystemEventHandler? FileChanged; public MultiFileWatcher() : this(NotifyFilters.LastWrite | NotifyFilters.CreationTime | NotifyFilters.Size | NotifyFilters.Security | NotifyFilters.Attributes) @@ -91,22 +89,6 @@ public void StopWatching() } } - /// - /// Stops watching the specified file. - /// - /// - public void StopWatching(string fileName) - { - lock (_watcherMap) - { - if (_watcherMap.TryGetValue(fileName, out var watcher)) - { - StopWatching(watcher); - _watcherMap.Remove(fileName); - } - } - } - /// /// Watches the specified files for changes. /// @@ -142,9 +124,11 @@ public void Watch(string fileName) InternalLogger.Debug("Start watching file-filter '{0}' in directory: {1}", fileFilter, directory); } } - catch (System.Security.SecurityException ex) + catch (Exception ex) { InternalLogger.Debug(ex, "Failed to start FileSystemWatcher with file-path: {0}", fileName); + if (LogManager.ThrowExceptions) + throw; } } @@ -155,7 +139,7 @@ private bool TryAddWatch(string fileName, string directory, string fileFilter) if (_watcherMap.ContainsKey(fileName)) return false; - FileSystemWatcher watcher = null; + FileSystemWatcher? watcher = null; try { @@ -179,7 +163,10 @@ private bool TryAddWatch(string fileName, string directory, string fileFilter) catch (Exception ex) { InternalLogger.Error(ex, "Failed to start FileSystemWatcher with file-filter '{0}' in directory: {1}", fileFilter, directory); - if (ex.MustBeRethrown()) + if (ex is System.Security.SecurityException || ex is UnauthorizedAccessException || ex is NotSupportedException || ex is NotImplementedException || ex is PlatformNotSupportedException) + return false; + + if (LogManager.ThrowExceptions) throw; if (watcher != null) @@ -214,7 +201,7 @@ private void StopWatching(FileSystemWatcher watcher) catch (Exception ex) { InternalLogger.Error(ex, "Failed to stop FileSystemWatcher with file-filter '{0}' in directory: {1}", fileFilter, fileDirectory); - if (ex.MustBeRethrown()) + if (LogManager.ThrowExceptions) throw; } } @@ -239,7 +226,7 @@ private void OnFileChanged(object source, FileSystemEventArgs e) catch (Exception ex) { #if DEBUG - if (ex.MustBeRethrownImmediately()) + if (LogManager.ThrowExceptions) throw; // Throwing exceptions here might crash the entire application (.NET 2.0 behavior) #endif InternalLogger.Error(ex, "Error handling event from FileSystemWatcher with file-filter: '{0}' in directory: {1}", e.Name, e.FullPath); @@ -248,5 +235,3 @@ private void OnFileChanged(object source, FileSystemEventArgs e) } } } - -#endif diff --git a/src/NLog/Internal/NativeMethods.cs b/src/NLog/Internal/NativeMethods.cs index a26fe374bc..f9c185653b 100644 --- a/src/NLog/Internal/NativeMethods.cs +++ b/src/NLog/Internal/NativeMethods.cs @@ -31,7 +31,7 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD +#if NETFRAMEWORK namespace NLog.Internal { diff --git a/src/NLog/Internal/NetStandardHelpers.cs b/src/NLog/Internal/NetStandardHelpers.cs deleted file mode 100644 index aabaa8c0ca..0000000000 --- a/src/NLog/Internal/NetStandardHelpers.cs +++ /dev/null @@ -1,120 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -#if NETSTANDARD1_3 || NETSTANDARD1_5 - -namespace NLog.Internal -{ - using System; - using System.Diagnostics; - using System.IO; - using System.Reflection; - using System.Threading; - - static class NetStandardHelpers - { - public static void Close(this IDisposable disposable) - { - disposable.Dispose(); - } - -#pragma warning disable S2953 // Methods named "Dispose" should implement "IDisposable.Dispose" - public static bool Dispose(this IDisposable disposable, EventWaitHandle waitHandle) -#pragma warning restore S2953 // Methods named "Dispose" should implement "IDisposable.Dispose" - { - disposable.Dispose(); - return false; - } - - public static bool IsDefined(this Type type, Type other, bool inherit) - { - return type.GetTypeInfo().IsDefined(other, inherit); - } - - public static bool IsSubclassOf(this Type type, Type other) - { - return type.GetTypeInfo().IsSubclassOf(other); - } - - public static MethodInfo GetMethod(this Type type, string name, BindingFlags bindingAttr, object binder, Type[] types, object[] modifiers) - { - if (binder != null) - throw new ArgumentException("Not supported", nameof(binder)); - if (modifiers != null) - throw new ArgumentException("Not supported", nameof(modifiers)); - - foreach (MethodInfo method in type.GetMethods(bindingAttr)) - { - if (method.Name != name) - continue; - - var parameters = method.GetParameters(); - if (parameters is null || parameters.Length != types.Length) - continue; - - for (int i = 0; i < parameters.Length; ++i) - { - if (parameters[i].ParameterType != types[i]) - { - parameters = null; - break; - } - } - - if (parameters != null) - { - return method; - } - } - - return null; - } - - public static byte[] GetBuffer(this MemoryStream memoryStream) - { - ArraySegment bytes; - if (memoryStream.TryGetBuffer(out bytes)) - { - return bytes.Array; - } - return memoryStream.ToArray(); - } - - public static StackFrame GetFrame(this StackTrace strackTrace, int number) - { - return strackTrace.GetFrames()[number]; - } - } -} - -#endif diff --git a/src/NLog/Internal/ObjectGraphScanner.cs b/src/NLog/Internal/ObjectGraphScanner.cs index 4ad16a4ff9..daf87b011e 100644 --- a/src/NLog/Internal/ObjectGraphScanner.cs +++ b/src/NLog/Internal/ObjectGraphScanner.cs @@ -68,7 +68,7 @@ public static List FindReachableObjects(ConfigurationItemFactory configFac var visitedObjects = new HashSet(SingleItemOptimizedHashSet.ReferenceEqualityComparer.Default); foreach (var rootObject in rootObjects) { - if (PropertyHelper.IsConfigurationItemType(configFactory, rootObject?.GetType())) + if (PropertyHelper.IsConfigurationItemType(configFactory, rootObject.GetType())) { ScanProperties(configFactory, aggressiveSearch, rootObject, result, 0, visitedObjects); } @@ -76,7 +76,7 @@ public static List FindReachableObjects(ConfigurationItemFactory configFac return result; } - private static void ScanProperties(ConfigurationItemFactory configFactory, bool aggressiveSearch, object targetObject, List result, int level, HashSet visitedObjects) + private static void ScanProperties(ConfigurationItemFactory configFactory, bool aggressiveSearch, object? targetObject, List result, int level, HashSet visitedObjects) where T : class { if (targetObject is null) @@ -109,11 +109,11 @@ private static void ScanProperties(ConfigurationItemFactory configFactory, bo if (string.IsNullOrEmpty(configProp.Key)) continue; // Ignore default values - if (!PropertyHelper.IsConfigurationItemType(configFactory, configProp.Value.PropertyType)) + var propInfo = configProp.Value; + if (!PropertyHelper.IsConfigurationItemType(configFactory, propInfo.PropertyType)) continue; - var propInfo = configProp.Value; - var propValue = propInfo.GetValue(targetObject, null); + var propValue = ScanPropertyValue(targetObject, type, propInfo); if (propValue is null) continue; @@ -122,6 +122,22 @@ private static void ScanProperties(ConfigurationItemFactory configFactory, bo } } + private static object? ScanPropertyValue(object targetObject, Type type, PropertyInfo propInfo) + { + try + { + return propInfo.GetValue(targetObject, null); + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "Failed scanning property: {0}.{1}", type, propInfo.Name); + if (exception.MustBeRethrownImmediately()) + throw; + + return null; + } + } + private static void ScanPropertyForObject(ConfigurationItemFactory configFactory, bool aggressiveSearch, object propValue, PropertyInfo prop, List result, int level, HashSet visitedObjects) where T : class { if (InternalLogger.IsTraceEnabled) diff --git a/src/NLog/Internal/ObjectHandleSerializer.cs b/src/NLog/Internal/ObjectHandleSerializer.cs index b58a2c41aa..698fb631f6 100644 --- a/src/NLog/Internal/ObjectHandleSerializer.cs +++ b/src/NLog/Internal/ObjectHandleSerializer.cs @@ -43,7 +43,7 @@ namespace NLog.Internal internal class ObjectHandleSerializer : ISerializable { [NonSerialized] - private readonly object _wrapped; + private readonly object? _wrapped; public ObjectHandleSerializer() { @@ -56,7 +56,7 @@ public ObjectHandleSerializer(object wrapped) protected ObjectHandleSerializer(SerializationInfo info, StreamingContext context) { - Type type = null; + Type? type = null; try { type = (Type)info.GetValue("wrappedtype", typeof(Type)); @@ -85,11 +85,11 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) string serializedString = string.Empty; try { - serializedString = _wrapped?.ToString(); + serializedString = _wrapped?.ToString() ?? string.Empty; } finally { - info.AddValue("wrappedvalue", serializedString ?? string.Empty); + info.AddValue("wrappedvalue", serializedString); } } } diff --git a/src/NLog/Internal/ObjectPropertyPath.cs b/src/NLog/Internal/ObjectPropertyPath.cs index 54557b7183..72819e4eba 100644 --- a/src/NLog/Internal/ObjectPropertyPath.cs +++ b/src/NLog/Internal/ObjectPropertyPath.cs @@ -35,15 +35,15 @@ namespace NLog.Internal { internal struct ObjectPropertyPath { - public string[] PathNames { get; private set; } + public string[]? PathNames { get; private set; } /// /// Object Path to check /// - public string Value + public string? Value { get => PathNames?.Length > 0 ? string.Join(".", PathNames) : null; - set => PathNames = StringHelpers.IsNullOrWhiteSpace(value) ? null : value.SplitAndTrimTokens('.'); + set => PathNames = StringHelpers.IsNullOrWhiteSpace(value) ? null : value?.SplitAndTrimTokens('.'); } } } diff --git a/src/NLog/Internal/ObjectReflectionCache.cs b/src/NLog/Internal/ObjectReflectionCache.cs index 7eed396e57..8c1f91bd91 100644 --- a/src/NLog/Internal/ObjectReflectionCache.cs +++ b/src/NLog/Internal/ObjectReflectionCache.cs @@ -37,7 +37,7 @@ namespace NLog.Internal using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -#if !NET35 && !NET40 && !NETSTANDARD1_3 && !NETSTANDARD1_5 +#if !NET35 && !NET40 && NETFRAMEWORK using System.Dynamic; #endif using System.Linq; @@ -51,17 +51,17 @@ namespace NLog.Internal internal sealed class ObjectReflectionCache : IObjectTypeTransformer { private MruCache ObjectTypeCache => _objectTypeCache ?? System.Threading.Interlocked.CompareExchange(ref _objectTypeCache, new MruCache(10000), null) ?? _objectTypeCache; - private MruCache _objectTypeCache; + private MruCache? _objectTypeCache; private readonly IServiceProvider _serviceProvider; private IObjectTypeTransformer ObjectTypeTransformation => _objectTypeTransformation ?? (_objectTypeTransformation = _serviceProvider?.GetService() ?? this); - private IObjectTypeTransformer _objectTypeTransformation; + private IObjectTypeTransformer? _objectTypeTransformation; public ObjectReflectionCache(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } - object IObjectTypeTransformer.TryTransformObject(object obj) + object? IObjectTypeTransformer.TryTransformObject(object? obj) { return null; } @@ -101,7 +101,7 @@ public ObjectPropertyList LookupObjectProperties(object value) /// /// Try get value from , using , and set into /// - public bool TryGetObjectProperty(object value, string[] objectPath, out object foundValue) + public bool TryGetObjectProperty(object? value, string[]? objectPath, out object? foundValue) { foundValue = null; @@ -133,8 +133,6 @@ public bool TryGetObjectProperty(object value, string[] objectPath, out object f return true; } - [UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2072")] - [UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2075")] public bool TryLookupExpandoObject(object value, out ObjectPropertyList objectPropertyList) { if (value is IDictionary expando) @@ -143,7 +141,7 @@ public bool TryLookupExpandoObject(object value, out ObjectPropertyList objectPr return true; } -#if !NET35 && !NET40 && !NETSTANDARD1_3 && !NETSTANDARD1_5 +#if !NET35 && !NET40 && NETFRAMEWORK if (value is DynamicObject d) { var dictionary = DynamicObjectToDict(d); @@ -165,17 +163,13 @@ public bool TryLookupExpandoObject(object value, out ObjectPropertyList objectPr return true; } - foreach (var interfaceType in value.GetType().GetInterfaces()) + var dictionaryEnumerator = TryGetDictionaryEnumerator(value); + if (dictionaryEnumerator != null) { - if (IsGenericDictionaryEnumeratorType(interfaceType)) - { - var dictionaryEnumerator = (IDictionaryEnumerator)Activator.CreateInstance(typeof(DictionaryEnumerator<,>).MakeGenericType(interfaceType.GetGenericArguments())); - propertyInfos = new ObjectPropertyInfos(null, new[] { new FastPropertyLookup(string.Empty, TypeCode.Object, (o, p) => dictionaryEnumerator.GetEnumerator(o)) }); - - ObjectTypeCache.TryAddValue(objectType, propertyInfos); - objectPropertyList = new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup); - return true; - } + propertyInfos = new ObjectPropertyInfos(ArrayHelper.Empty(), new[] { new FastPropertyLookup(string.Empty, TypeCode.Object, (o, p) => dictionaryEnumerator.GetEnumerator(o)) }); + ObjectTypeCache.TryAddValue(objectType, propertyInfos); + objectPropertyList = new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup); + return true; } objectPropertyList = default(ObjectPropertyList); @@ -225,19 +219,16 @@ private static bool ConvertSimpleToString(Type objectType) if (typeof(System.IO.Stream).IsAssignableFrom(objectType)) return true; // Skip serializing properties that often throws exceptions - if (typeof(System.Net.IPAddress).IsAssignableFrom(objectType)) - return true; // Skip serializing properties that often throws exceptions - return false; } private static PropertyInfo[] GetPublicProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { - PropertyInfo[] properties = null; + PropertyInfo[]? properties = null; try { - properties = type.GetProperties(PublicProperties); + properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); } catch (Exception ex) { @@ -274,34 +265,35 @@ private static FastPropertyLookup[] BuildFastLookup(PropertyInfo[] properties, b var getterMethod = prop.GetGetMethod(); Type propertyType = getterMethod.ReturnType; ReflectionHelpers.LateBoundMethod valueLookup = ReflectionHelpers.CreateLateBoundMethod(getterMethod); -#if NETSTANDARD1_3 - TypeCode typeCode = propertyType == typeof(string) ? TypeCode.String : (propertyType == typeof(int) ? TypeCode.Int32 : TypeCode.Object); -#else TypeCode typeCode = Type.GetTypeCode(propertyType); // Skip cyclic-reference checks when not TypeCode.Object -#endif fastLookup[fastAccessIndex++] = new FastPropertyLookup(prop.Name, typeCode, valueLookup); } return fastLookup; } - private const BindingFlags PublicProperties = BindingFlags.Instance | BindingFlags.Public; - - internal struct ObjectPropertyList : IEnumerable + internal +#if !NETFRAMEWORK + readonly +#endif + struct ObjectPropertyList : IEnumerable { internal static readonly StringComparer NameComparer = StringComparer.Ordinal; private static readonly FastPropertyLookup[] CreateIDictionaryEnumerator = new[] { new FastPropertyLookup(string.Empty, TypeCode.Object, (o, p) => ((IDictionary)o).GetEnumerator()) }; private readonly object _object; private readonly PropertyInfo[] _properties; - private readonly FastPropertyLookup[] _fastLookup; + private readonly FastPropertyLookup[]? _fastLookup; - public struct PropertyValue + public +#if !NETFRAMEWORK + readonly +#endif + struct PropertyValue { public readonly string Name; - public readonly object Value; + public readonly object? Value; public TypeCode TypeCode => Value is null ? TypeCode.Empty : _typecode; private readonly TypeCode _typecode; public bool HasNameAndValue => Name != null && Value != null; - public PropertyValue(string name, object value, TypeCode typeCode) { Name = name; @@ -312,23 +304,37 @@ public PropertyValue(string name, object value, TypeCode typeCode) public PropertyValue(object owner, PropertyInfo propertyInfo) { Name = propertyInfo.Name; - Value = propertyInfo.GetValue(owner, null); _typecode = TypeCode.Object; + try + { + Value = propertyInfo.GetValue(owner, null); + } + catch + { + Value = null; + } } public PropertyValue(object owner, FastPropertyLookup fastProperty) { Name = fastProperty.Name; - Value = fastProperty.ValueLookup(owner, null); _typecode = fastProperty.TypeCode; + try + { + Value = fastProperty.ValueLookup(owner, ArrayHelper.Empty()); + } + catch + { + Value = null; + } } } - public bool IsSimpleValue => _properties?.Length == 0; + public bool IsSimpleValue => _properties.Length == 0 && (_fastLookup is null || _fastLookup.Length == 0); public object ObjectValue => _object; - internal ObjectPropertyList(object value, PropertyInfo[] properties, FastPropertyLookup[] fastLookup) + internal ObjectPropertyList(object value, PropertyInfo[] properties, FastPropertyLookup[]? fastLookup) { _object = value; _properties = properties; @@ -338,55 +344,45 @@ internal ObjectPropertyList(object value, PropertyInfo[] properties, FastPropert public ObjectPropertyList(IDictionary value) { _object = value; // Expando objects - _properties = null; + _properties = ArrayHelper.Empty(); _fastLookup = CreateIDictionaryEnumerator; } public bool TryGetPropertyValue(string name, out PropertyValue propertyValue) { - if (_properties != null) + if (_properties.Length == 0) { - if (_fastLookup != null) - { - return TryFastLookupPropertyValue(name, out propertyValue); - } - else + if (_object is IDictionary expandoObject) { - return TrySlowLookupPropertyValue(name, out propertyValue); + if (expandoObject.TryGetValue(name, out var objectValue)) + { + propertyValue = new PropertyValue(name, objectValue, TypeCode.Object); + return true; + } + propertyValue = default(PropertyValue); + return false; } + + return TryListLookupPropertyValue(name, out propertyValue); } - else if (_object is IDictionary expandoObject) + + if (_fastLookup != null) { - if (expandoObject.TryGetValue(name, out var objectValue)) + // Scans properties for name (Skips string-compare and value-lookup until finding match) + int nameHashCode = NameComparer.GetHashCode(name); + foreach (var fastProperty in _fastLookup) { - propertyValue = new PropertyValue(name, objectValue, TypeCode.Object); - return true; + if (fastProperty.NameHashCode == nameHashCode && NameComparer.Equals(fastProperty.Name, name)) + { + propertyValue = new PropertyValue(_object, fastProperty); + return true; + } } propertyValue = default(PropertyValue); return false; } - else - { - return TryListLookupPropertyValue(name, out propertyValue); - } - } - /// - /// Scans properties for name (Skips string-compare and value-lookup until finding match) - /// - private bool TryFastLookupPropertyValue(string name, out PropertyValue propertyValue) - { - int nameHashCode = NameComparer.GetHashCode(name); - foreach (var fastProperty in _fastLookup) - { - if (fastProperty.NameHashCode == nameHashCode && NameComparer.Equals(fastProperty.Name, name)) - { - propertyValue = new PropertyValue(_object, fastProperty); - return true; - } - } - propertyValue = default(PropertyValue); - return false; + return TrySlowLookupPropertyValue(name, out propertyValue); } /// @@ -430,10 +426,11 @@ public override string ToString() public Enumerator GetEnumerator() { - if (_properties != null) + if (_properties.Length > 0 || _fastLookup is null || _fastLookup.Length == 0) return new Enumerator(_object, _properties, _fastLookup); - else - return new Enumerator((IEnumerator>)_fastLookup[0].ValueLookup(_object, null)); + + var expandoObject = _fastLookup[0].ValueLookup(_object, ArrayHelper.Empty()); + return expandoObject is null ? default(Enumerator) : new Enumerator((IEnumerator>)expandoObject); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -442,12 +439,12 @@ public Enumerator GetEnumerator() public struct Enumerator : IEnumerator { private readonly object _owner; - private readonly PropertyInfo[] _properties; - private readonly FastPropertyLookup[] _fastLookup; - private readonly IEnumerator> _enumerator; + private readonly PropertyInfo[]? _properties; + private readonly FastPropertyLookup[]? _fastLookup; + private readonly IEnumerator>? _enumerator; private int _index; - internal Enumerator(object owner, PropertyInfo[] properties, FastPropertyLookup[] fastLookup) + internal Enumerator(object owner, PropertyInfo[] properties, FastPropertyLookup[]? fastLookup) { _owner = owner; _properties = properties; @@ -475,8 +472,10 @@ public PropertyValue Current return new PropertyValue(_owner, _fastLookup[_index]); else if (_properties != null) return new PropertyValue(_owner, _properties[_index]); - else + else if (_enumerator != null) return new PropertyValue(_enumerator.Current.Key, _enumerator.Current.Value, TypeCode.Object); + else + return default(PropertyValue); } catch (Exception ex) { @@ -498,7 +497,7 @@ public bool MoveNext() if (_properties != null) return ++_index < (_fastLookup?.Length ?? _properties.Length); else - return _enumerator.MoveNext(); + return _enumerator?.MoveNext() == true; } public void Reset() @@ -506,12 +505,16 @@ public void Reset() if (_properties != null) _index = -1; else - _enumerator.Reset(); + _enumerator?.Reset(); } } } - internal struct FastPropertyLookup + internal +#if !NETFRAMEWORK + readonly +#endif + struct FastPropertyLookup { public readonly string Name; public readonly ReflectionHelpers.LateBoundMethod ValueLookup; @@ -527,14 +530,18 @@ public FastPropertyLookup(string name, TypeCode typeCode, ReflectionHelpers.Late } } - private struct ObjectPropertyInfos : IEquatable + private +#if !NETFRAMEWORK + readonly +#endif + struct ObjectPropertyInfos : IEquatable { public readonly PropertyInfo[] Properties; - public readonly FastPropertyLookup[] FastLookup; + public readonly FastPropertyLookup[]? FastLookup; public static readonly ObjectPropertyInfos SimpleToString = new ObjectPropertyInfos(ArrayHelper.Empty(), ArrayHelper.Empty()); - public ObjectPropertyInfos(PropertyInfo[] properties, FastPropertyLookup[] fastLookup) + public ObjectPropertyInfos(PropertyInfo[] properties, FastPropertyLookup[]? fastLookup) { Properties = properties; FastLookup = fastLookup; @@ -548,7 +555,7 @@ public bool Equals(ObjectPropertyInfos other) } } -#if !NET35 && !NET40 && !NETSTANDARD1_3 && !NETSTANDARD1_5 +#if !NET35 && !NET40 && NETFRAMEWORK private static Dictionary DynamicObjectToDict(DynamicObject d) { var newVal = new Dictionary(); @@ -581,34 +588,132 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy } #endif - private static bool IsGenericDictionaryEnumeratorType(Type interfaceType) + private static IDictionaryEnumerator? TryGetDictionaryEnumerator(object value) { - if (interfaceType.IsGenericType()) + if (!(value is IEnumerable) || value is string) + return null; + + if (value is IDictionary) + return new DictionaryEnumerator(); + if (value is IDictionary) + return new DictionaryEnumerator(); +#if !NET35 + if (value is IReadOnlyDictionary) + return new DictionaryEnumerator(); + if (value is IReadOnlyDictionary) + return new DictionaryEnumerator(); +#endif + + if (value is IDictionary && value.GetType().IsGenericType) { - if (interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>) + if (value.GetType().GetGenericArguments()[0] == typeof(string)) + return new DictionaryEnumerator(); + else + return null; + } + + return TryBuildDictionaryEnumerator(value); + } + + [UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2075")] + private static IDictionaryEnumerator? TryBuildDictionaryEnumerator(object value) + { + foreach (var interfaceType in value.GetType().GetInterfaces()) + { + if (interfaceType.IsGenericType + && (interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>) #if !NET35 - || interfaceType.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>) + || interfaceType.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>) #endif - ) + ) + && interfaceType.GetGenericArguments()[0] == typeof(string)) { - if (interfaceType.GetGenericArguments()[0] == typeof(string)) - { - return true; - } +#if NETFRAMEWORK + return (IDictionaryEnumerator)Activator.CreateInstance(typeof(DictionaryEnumerator<,>).MakeGenericType(interfaceType.GetGenericArguments())); +#else + // AOT does not support creating new types at runtime + return new DictionaryEnumerator(); +#endif } } - return false; + return null; } private interface IDictionaryEnumerator { - IEnumerator> GetEnumerator(object value); + IEnumerator> GetEnumerator(object value); } - internal sealed class DictionaryEnumerator : IDictionaryEnumerator + private sealed class DictionaryEnumerator : IDictionaryEnumerator { - public IEnumerator> GetEnumerator(object value) + private Func>>? _enumerateCollection; + + IEnumerator> IDictionaryEnumerator.GetEnumerator(object value) + { + if (value is IDictionary dictionary) + { + if (dictionary.Count > 0) + return YieldEnumerator(dictionary); + } + else if (value is IEnumerable collection) + { + return YieldEnumerator(collection); + } + return EmptyDictionaryEnumerator.Default; + } + + private static IEnumerator> YieldEnumerator(IDictionary dictionary) + { + foreach (var item in new DictionaryEntryEnumerable(dictionary)) + yield return new KeyValuePair(item.Key?.ToString() ?? string.Empty, item.Value); + } + + private IEnumerator> YieldEnumerator(IEnumerable collection) + { + if (_enumerateCollection is null) + { + var enumerator = collection.GetEnumerator(); + if (!enumerator.MoveNext()) + return EmptyDictionaryEnumerator.Default; + _enumerateCollection = BuildEnumerator(enumerator.Current); + } + + return _enumerateCollection(collection); + } + + [UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2075")] + private Func>> BuildEnumerator(object firstItem) + { + if (firstItem.GetType().IsGenericType && firstItem.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + var getKeyProperty = firstItem.GetType().GetProperty(nameof(KeyValuePair.Key)); + var getValueProperty = firstItem.GetType().GetProperty(nameof(KeyValuePair.Value)); + return (collection) => + { + return EnumerateItems(collection, getKeyProperty, getValueProperty); + }; + } + else + { + return (collection) => EmptyDictionaryEnumerator.Default; + } + } + + private static IEnumerator> EnumerateItems(IEnumerable collection, PropertyInfo getKeyProperty, PropertyInfo getValueProperty) + { + foreach (var item in collection) + { + var keyString = getKeyProperty.GetValue(item, null); + var valueObject = getValueProperty.GetValue(item, null); + yield return new KeyValuePair(keyString?.ToString() ?? string.Empty, valueObject); + } + } + } + + private sealed class DictionaryEnumerator : IDictionaryEnumerator + { + public IEnumerator> GetEnumerator(object value) { if (value is IDictionary dictionary) { @@ -625,39 +730,39 @@ public IEnumerator> GetEnumerator(object value) return EmptyDictionaryEnumerator.Default; } - private static IEnumerator> YieldEnumerator(IDictionary dictionary) + private static IEnumerator> YieldEnumerator(IDictionary dictionary) { foreach (var item in dictionary) - yield return new KeyValuePair(item.Key.ToString(), item.Value); + yield return new KeyValuePair(item.Key?.ToString() ?? string.Empty, item.Value); } #if !NET35 - private static IEnumerator> YieldEnumerator(IReadOnlyDictionary dictionary) + private static IEnumerator> YieldEnumerator(IReadOnlyDictionary dictionary) { foreach (var item in dictionary) - yield return new KeyValuePair(item.Key.ToString(), item.Value); + yield return new KeyValuePair(item.Key?.ToString() ?? string.Empty, item.Value); } #endif + } - private sealed class EmptyDictionaryEnumerator : IEnumerator> - { - public static readonly EmptyDictionaryEnumerator Default = new EmptyDictionaryEnumerator(); + private sealed class EmptyDictionaryEnumerator : IEnumerator> + { + public static readonly EmptyDictionaryEnumerator Default = new EmptyDictionaryEnumerator(); - KeyValuePair IEnumerator>.Current => default(KeyValuePair); + KeyValuePair IEnumerator>.Current => default(KeyValuePair); - object IEnumerator.Current => default(KeyValuePair); + object IEnumerator.Current => default(KeyValuePair); - bool IEnumerator.MoveNext() => false; + bool IEnumerator.MoveNext() => false; - void IDisposable.Dispose() - { - // Nothing here on purpose - } + void IDisposable.Dispose() + { + // Nothing here on purpose + } - void IEnumerator.Reset() - { - // Nothing here on purpose - } + void IEnumerator.Reset() + { + // Nothing here on purpose } } } diff --git a/src/NLog/Internal/OverloadResolutionPriorityAttribute.cs b/src/NLog/Internal/OverloadResolutionPriorityAttribute.cs new file mode 100644 index 0000000000..bea34420d8 --- /dev/null +++ b/src/NLog/Internal/OverloadResolutionPriorityAttribute.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +#if NETSTANDARD2_1_OR_GREATER && !NET9_0_OR_GREATER + +namespace System.Runtime.CompilerServices +{ + /// + /// Specifies the priority of a member in overload resolution. When unspecified, the default priority is 0. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + internal sealed class OverloadResolutionPriorityAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The priority of the attributed member. Higher numbers are prioritized, lower numbers are deprioritized. 0 is the default if no attribute is present. + public OverloadResolutionPriorityAttribute(int priority) + { + Priority = priority; + } + + /// + /// The priority of the member. + /// + public int Priority { get; } + } +} + +#endif diff --git a/src/NLog/Internal/PathHelpers.cs b/src/NLog/Internal/PathHelpers.cs index 736a4a0e05..237c23ee7c 100644 --- a/src/NLog/Internal/PathHelpers.cs +++ b/src/NLog/Internal/PathHelpers.cs @@ -31,10 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System.IO; - namespace NLog.Internal { + using System.IO; + internal static class PathHelpers { /// @@ -46,12 +46,12 @@ internal static class PathHelpers /// internal static string CombinePaths(string path, string dir, string file) { - if (dir != null) + if (!string.IsNullOrEmpty(dir)) { path = Path.Combine(path, dir); } - if (file != null) + if (!string.IsNullOrEmpty(file)) { path = Path.Combine(path, file); } @@ -72,7 +72,7 @@ public static string TrimDirectorySeparators(string path) { var newpath = path?.TrimEnd(DirectorySeparatorChars) ?? string.Empty; if (newpath.EndsWith(":", System.StringComparison.Ordinal)) - return path; // Support root-path on Windows (But Linux root-path is off limits) + return path ?? string.Empty; // Support root-path on Windows (But Linux root-path is off limits) else return newpath; } diff --git a/src/NLog/Internal/PlatformDetector.cs b/src/NLog/Internal/PlatformDetector.cs index 2a43441f1c..61c5a85d35 100644 --- a/src/NLog/Internal/PlatformDetector.cs +++ b/src/NLog/Internal/PlatformDetector.cs @@ -56,7 +56,7 @@ internal static class PlatformDetector /// public static bool IsUnix => CurrentOS == RuntimeOS.Linux || CurrentOS == RuntimeOS.MacOSX; -#if !NETSTANDARD +#if NETFRAMEWORK /// /// Gets a value indicating whether current runtime is Mono-based /// @@ -66,33 +66,23 @@ internal static class PlatformDetector private static RuntimeOS GetCurrentRuntimeOS() { -#if NETSTANDARD - if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) - return RuntimeOS.WindowsNT; - else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX)) - return RuntimeOS.MacOSX; - else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux)) - return RuntimeOS.Linux; - return RuntimeOS.Unknown; -#else +#if NETFRAMEWORK PlatformID platformID = Environment.OSVersion.Platform; if ((int)platformID == 4 || (int)platformID == 128) - { return RuntimeOS.Linux; - } - if (platformID == PlatformID.Win32Windows) - { return RuntimeOS.Windows9x; - } - if (platformID == PlatformID.Win32NT) - { return RuntimeOS.WindowsNT; - } - - return RuntimeOS.Unknown; +#else + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux)) + return RuntimeOS.Linux; + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX)) + return RuntimeOS.MacOSX; + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) + return RuntimeOS.WindowsNT; #endif + return RuntimeOS.Unknown; } } } diff --git a/src/NLog/Internal/PropertiesDictionary.cs b/src/NLog/Internal/PropertiesDictionary.cs index d11105f6d8..77eccff135 100644 --- a/src/NLog/Internal/PropertiesDictionary.cs +++ b/src/NLog/Internal/PropertiesDictionary.cs @@ -47,21 +47,25 @@ namespace NLog.Internal /// in the collection, and in positional order. /// [DebuggerDisplay("Count = {Count}")] - internal sealed class PropertiesDictionary : IDictionary + internal sealed class PropertiesDictionary : IDictionary { - private struct PropertyValue + private +#if !NETFRAMEWORK + readonly +#endif + struct PropertyValue { /// /// Value of the property /// - public readonly object Value; + public readonly object? Value; /// /// Has property been captured from message-template ? /// public readonly bool IsMessageProperty; - public PropertyValue(object value, bool isMessageProperty) + public PropertyValue(object? value, bool isMessageProperty) { Value = value; IsMessageProperty = isMessageProperty; @@ -71,43 +75,34 @@ public PropertyValue(object value, bool isMessageProperty) /// /// The properties of the logEvent /// - private Dictionary _eventProperties; + private Dictionary? _eventProperties; /// /// The properties extracted from the message-template /// - private IList _messageProperties; + private IList? _messageProperties; /// /// Wraps the list of message-template-parameters as IDictionary-interface /// /// Message-template-parameters - public PropertiesDictionary(IList messageParameters = null) + public PropertiesDictionary(IList? messageParameters = null) { if (messageParameters?.Count > 0) { - MessageProperties = messageParameters; + _messageProperties = SetMessageProperties(messageParameters, null); } } -#if !NET35 /// /// Transforms the list of event-properties into IDictionary-interface /// - /// Message-template-parameters - public PropertiesDictionary(IReadOnlyList> eventProperties) + public PropertiesDictionary(int initialCapacity) { - var propertyCount = eventProperties.Count; - if (propertyCount > 0) + if (initialCapacity > 3) { - _eventProperties = new Dictionary(propertyCount, PropertyKeyComparer.Default); - for (int i = 0; i < propertyCount; ++i) - { - var property = eventProperties[i]; - _eventProperties[property.Key] = new PropertyValue(property.Value, false); - } + _eventProperties = new Dictionary(initialCapacity, PropertyKeyComparer.Default); } } -#endif private bool IsEmpty => (_eventProperties is null || _eventProperties.Count == 0) && (_messageProperties is null || _messageProperties.Count == 0); @@ -126,10 +121,14 @@ private Dictionary EventProperties public IList MessageProperties { get => _messageProperties ?? ArrayHelper.Empty(); - internal set => _messageProperties = SetMessageProperties(value, _messageProperties); } - private IList SetMessageProperties(IList newMessageProperties, IList oldMessageProperties) + public void ResetMessageProperties(IList? newMessageProperties = null) + { + _messageProperties = SetMessageProperties(newMessageProperties, _messageProperties); + } + + private IList? SetMessageProperties(IList? newMessageProperties, IList? oldMessageProperties) { if (_eventProperties is null && VerifyUniqueMessageTemplateParametersFast(newMessageProperties)) { @@ -168,7 +167,7 @@ private static void RemoveOldMessageProperties(IList o } } - private static Dictionary BuildEventProperties(IList messageProperties) + private static Dictionary BuildEventProperties(IList? messageProperties) { if (messageProperties?.Count > 0) { @@ -183,7 +182,7 @@ private static Dictionary BuildEventProperties(IList - public object this[object key] + public object? this[object key] { get { @@ -198,17 +197,14 @@ public object this[object key] } /// - public ICollection Keys => KeyCollection; + public ICollection Keys => IsEmpty ? EmptyKeyCollection : new KeyCollection(this); /// - public ICollection Values => ValueCollection; - - private DictionaryCollection KeyCollection => IsEmpty ? EmptyKeyCollection : new DictionaryCollection(this, true); + public ICollection Values => IsEmpty ? EmptyValueCollection : new ValueCollection(this); - private DictionaryCollection ValueCollection => IsEmpty ? EmptyValueCollection : new DictionaryCollection(this, false); - private static readonly DictionaryCollection EmptyKeyCollection = new DictionaryCollection(new PropertiesDictionary(), true); - private static readonly DictionaryCollection EmptyValueCollection = new DictionaryCollection(new PropertiesDictionary(), false); + private static readonly KeyCollection EmptyKeyCollection = new KeyCollection(new PropertiesDictionary()); + private static readonly ValueCollection EmptyValueCollection = new ValueCollection(new PropertiesDictionary()); /// public int Count => (_eventProperties?.Count) ?? (_messageProperties?.Count) ?? 0; @@ -217,13 +213,13 @@ public object this[object key] public bool IsReadOnly => false; /// - public void Add(object key, object value) + public void Add(object key, object? value) { EventProperties.Add(key, new PropertyValue(value, false)); } /// - public void Add(KeyValuePair item) + public void Add(KeyValuePair item) { Add(item.Key, item.Value); } @@ -238,7 +234,7 @@ public void Clear() } /// - public bool Contains(KeyValuePair item) + public bool Contains(KeyValuePair item) { if (!IsEmpty && (_eventProperties != null || ContainsKey(item.Key))) { @@ -258,7 +254,7 @@ public bool ContainsKey(object key) } /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) + public void CopyTo(KeyValuePair[] array, int arrayIndex) { Guard.ThrowIfNull(array); if (arrayIndex < 0) @@ -279,9 +275,9 @@ internal PropertyDictionaryEnumerator GetPropertyEnumerator() } /// - public IEnumerator> GetEnumerator() + public IEnumerator> GetEnumerator() { - return IsEmpty ? System.Linq.Enumerable.Empty>().GetEnumerator() : new PropertyDictionaryEnumerator(this); + return IsEmpty ? System.Linq.Enumerable.Empty>().GetEnumerator() : new PropertyDictionaryEnumerator(this); } /// @@ -301,14 +297,14 @@ public bool Remove(object key) } /// - public bool Remove(KeyValuePair item) + public bool Remove(KeyValuePair item) { if (!IsEmpty && (_eventProperties != null || ContainsKey(item.Key))) { - if (((IDictionary)EventProperties).Remove(new KeyValuePair(item.Key, new PropertyValue(item.Value, false)))) + if (((ICollection>)EventProperties).Remove(new KeyValuePair(item.Key, new PropertyValue(item.Value, false)))) return true; - if (((IDictionary)EventProperties).Remove(new KeyValuePair(item.Key, new PropertyValue(item.Value, true)))) + if (((ICollection>)EventProperties).Remove(new KeyValuePair(item.Key, new PropertyValue(item.Value, true)))) return true; } @@ -316,17 +312,18 @@ public bool Remove(KeyValuePair item) } /// - public bool TryGetValue(object key, out object value) + public bool TryGetValue(object key, out object? value) { if (!IsEmpty) { - if (_eventProperties is null && _messageProperties?.Count < 5) + if (_eventProperties is null) { return TryLookupMessagePropertyValue(key, out value); } - else if (EventProperties.TryGetValue(key, out var valueItem)) + + if (EventProperties.TryGetValue(key, out var eventProperty)) { - value = valueItem.Value; + value = eventProperty.Value; return true; } } @@ -335,9 +332,23 @@ public bool TryGetValue(object key, out object value) return false; } - private bool TryLookupMessagePropertyValue(object key, out object propertyValue) + private bool TryLookupMessagePropertyValue(object key, out object? propertyValue) { - if (key is string keyString) + if (_messageProperties is null || _messageProperties.Count == 0) + { + propertyValue = null; + return false; + } + + if (_messageProperties.Count > 10) + { + if (EventProperties.TryGetValue(key, out var eventProperty)) + { + propertyValue = eventProperty.Value; + return true; + } + } + else if (key is string keyString) { for (int i = 0; i < _messageProperties.Count; ++i) { @@ -369,7 +380,7 @@ private bool TryLookupMessagePropertyValue(object key, out object propertyValue) /// /// Message-template-parameters /// Are all parameter names unique (true / false) - private static bool VerifyUniqueMessageTemplateParametersFast(IList parameterList) + private static bool VerifyUniqueMessageTemplateParametersFast(IList? parameterList) { if (parameterList is null) return true; @@ -434,7 +445,7 @@ internal static string GenerateUniquePropertyName(string originalN return newItemName; } - public struct PropertyDictionaryEnumerator : IEnumerator> + public struct PropertyDictionaryEnumerator : IEnumerator> { private readonly PropertiesDictionary _dictionary; private Dictionary.Enumerator _eventEnumerator; @@ -447,18 +458,18 @@ public PropertyDictionaryEnumerator(PropertiesDictionary dictionary) _messagePropertiesIndex = dictionary._messageProperties?.Count > 0 ? -1 : default(int?); } - public KeyValuePair Current + public KeyValuePair Current { get { if (_messagePropertiesIndex.HasValue) { - var property = _dictionary._messageProperties[_messagePropertiesIndex.Value]; - return new KeyValuePair(property.Name, property.Value); + var property = _dictionary.MessageProperties[_messagePropertiesIndex.Value]; + return new KeyValuePair(property.Name, property.Value); } if (_dictionary._eventProperties != null) { - return new KeyValuePair(_eventEnumerator.Current.Key, _eventEnumerator.Current.Value.Value); + return new KeyValuePair(_eventEnumerator.Current.Key, _eventEnumerator.Current.Value.Value); } throw new InvalidOperationException(); } @@ -470,7 +481,7 @@ public MessageTemplateParameter CurrentParameter { if (_messagePropertiesIndex.HasValue) { - return _dictionary._messageProperties[_messagePropertiesIndex.Value]; + return _dictionary.MessageProperties[_messagePropertiesIndex.Value]; } if (_dictionary._eventProperties != null) { @@ -481,19 +492,19 @@ public MessageTemplateParameter CurrentParameter } } - public KeyValuePair CurrentProperty + public KeyValuePair CurrentProperty { get { if (_messagePropertiesIndex.HasValue) { - var property = _dictionary._messageProperties[_messagePropertiesIndex.Value]; - return new KeyValuePair(property.Name, property.Value); + var property = _dictionary.MessageProperties[_messagePropertiesIndex.Value]; + return new KeyValuePair(property.Name, property.Value); } if (_dictionary._eventProperties != null) { string propertyName = XmlHelper.XmlConvertToString(_eventEnumerator.Current.Key ?? string.Empty) ?? string.Empty; - return new KeyValuePair(propertyName, _eventEnumerator.Current.Value.Value); + return new KeyValuePair(propertyName, _eventEnumerator.Current.Value.Value); } throw new InvalidOperationException(); } @@ -528,7 +539,8 @@ private bool MoveNextValidEventProperty() private bool MoveNextValidMessageParameter() { - if (_messagePropertiesIndex.Value + 1 < _dictionary._messageProperties.Count) + var messageProperties = _dictionary.MessageProperties; + if (_messagePropertiesIndex.HasValue && messageProperties != null && _messagePropertiesIndex.Value + 1 < messageProperties.Count) { var eventProperties = _dictionary._eventProperties; if (eventProperties is null) @@ -537,9 +549,9 @@ private bool MoveNextValidMessageParameter() return true; } - for (int i = _messagePropertiesIndex.Value + 1; i < _dictionary._messageProperties.Count; ++i) + for (int i = _messagePropertiesIndex.Value + 1; i < messageProperties.Count; ++i) { - if (eventProperties.TryGetValue(_dictionary._messageProperties[i].Name, out var valueItem) && valueItem.IsMessageProperty) + if (eventProperties.TryGetValue(messageProperties[i].Name, out var valueItem) && valueItem.IsMessageProperty) { _messagePropertiesIndex = i; return true; @@ -564,15 +576,13 @@ public void Reset() } [DebuggerDisplay("Count = {Count}")] - private sealed class DictionaryCollection : ICollection + private sealed class ValueCollection : ICollection { private readonly PropertiesDictionary _dictionary; - private readonly bool _keyCollection; - public DictionaryCollection(PropertiesDictionary dictionary, bool keyCollection) + public ValueCollection(PropertiesDictionary dictionary) { _dictionary = dictionary; - _keyCollection = keyCollection; } /// @@ -581,23 +591,18 @@ public DictionaryCollection(PropertiesDictionary dictionary, bool keyCollection) /// public bool IsReadOnly => true; - /// Will always throw, as collection is readonly - public void Add(object item) { throw new NotSupportedException(); } + public void Add(object? item) { throw new NotSupportedException(); } /// Will always throw, as collection is readonly public void Clear() { throw new NotSupportedException(); } /// Will always throw, as collection is readonly - public bool Remove(object item) { throw new NotSupportedException(); } + public bool Remove(object? item) { throw new NotSupportedException(); } /// - public bool Contains(object item) + public bool Contains(object? item) { - if (_keyCollection) - { - return _dictionary.ContainsKey(item); - } if (!_dictionary.IsEmpty) { if (_dictionary.EventProperties.ContainsValue(new PropertyValue(item, false))) @@ -609,6 +614,82 @@ public bool Contains(object item) return false; } + /// + public void CopyTo(object?[] array, int arrayIndex) + { + Guard.ThrowIfNull(array); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + + if (!_dictionary.IsEmpty) + { + foreach (var propertyItem in _dictionary) + { + array[arrayIndex++] = propertyItem.Value; + } + } + } + + /// + public IEnumerator GetEnumerator() + { + return new ValueCollectionEnumerator(_dictionary); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private sealed class ValueCollectionEnumerator : IEnumerator + { + PropertyDictionaryEnumerator _enumerator; + + public ValueCollectionEnumerator(PropertiesDictionary dictionary) + { + _enumerator = dictionary.GetPropertyEnumerator(); + } + + /// + public object? Current => _enumerator.Current.Value; + public void Dispose() => _enumerator.Dispose(); + public bool MoveNext() => _enumerator.MoveNext(); + public void Reset() => _enumerator.Reset(); + } + } + + [DebuggerDisplay("Count = {Count}")] + private sealed class KeyCollection : ICollection + { + private readonly PropertiesDictionary _dictionary; + + public KeyCollection(PropertiesDictionary dictionary) + { + _dictionary = dictionary; + } + + /// + public int Count => _dictionary.Count; + + /// + public bool IsReadOnly => true; + + /// Will always throw, as collection is readonly + public void Add(object item) { throw new NotSupportedException(); } + + /// Will always throw, as collection is readonly + public void Clear() { throw new NotSupportedException(); } + + /// Will always throw, as collection is readonly + public bool Remove(object item) { throw new NotSupportedException(); } + + /// + public bool Contains(object item) + { + return _dictionary.ContainsKey(item); + } + /// public void CopyTo(object[] array, int arrayIndex) { @@ -620,7 +701,7 @@ public void CopyTo(object[] array, int arrayIndex) { foreach (var propertyItem in _dictionary) { - array[arrayIndex++] = _keyCollection ? propertyItem.Key : propertyItem.Value; + array[arrayIndex++] = propertyItem.Key; } } } @@ -628,7 +709,7 @@ public void CopyTo(object[] array, int arrayIndex) /// public IEnumerator GetEnumerator() { - return new DictionaryCollectionEnumerator(_dictionary, _keyCollection); + return new KeyCollectionEnumerator(_dictionary); } /// @@ -637,19 +718,17 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - private sealed class DictionaryCollectionEnumerator : IEnumerator + private sealed class KeyCollectionEnumerator : IEnumerator { PropertyDictionaryEnumerator _enumerator; - private readonly bool _keyCollection; - public DictionaryCollectionEnumerator(PropertiesDictionary dictionary, bool keyCollection) + public KeyCollectionEnumerator(PropertiesDictionary dictionary) { _enumerator = dictionary.GetPropertyEnumerator(); - _keyCollection = keyCollection; } /// - public object Current => _keyCollection ? _enumerator.Current.Key : _enumerator.Current.Value; + public object Current => _enumerator.Current.Key; public void Dispose() => _enumerator.Dispose(); public bool MoveNext() => _enumerator.MoveNext(); public void Reset() => _enumerator.Reset(); diff --git a/src/NLog/Internal/PropertyHelper.cs b/src/NLog/Internal/PropertyHelper.cs index 9d796f391f..90e1efd654 100644 --- a/src/NLog/Internal/PropertyHelper.cs +++ b/src/NLog/Internal/PropertyHelper.cs @@ -52,30 +52,32 @@ namespace NLog.Internal /// internal static class PropertyHelper { - private static readonly Dictionary> _parameterInfoCache = new Dictionary>(); - private static readonly Dictionary> _propertyConversionMapper = BuildPropertyConversionMapper(); + private static readonly Dictionary?> _parameterInfoCache = new Dictionary?>(); + private static readonly Dictionary> _propertyConversionMapper = BuildPropertyConversionMapper(); #pragma warning disable S1144 // Unused private types or members should be removed. BUT they help CoreRT to provide config through reflection +#pragma warning disable CS0618 // Type or member is obsolete private static readonly RequiredParameterAttribute _requiredParameterAttribute = new RequiredParameterAttribute(); - private static readonly ArrayParameterAttribute _arrayParameterAttribute = new ArrayParameterAttribute(null, string.Empty); +#pragma warning restore CS0618 // Type or member is obsolete + private static readonly ArrayParameterAttribute _arrayParameterAttribute = new ArrayParameterAttribute(typeof(string), string.Empty); private static readonly DefaultParameterAttribute _defaultParameterAttribute = new DefaultParameterAttribute(); private static readonly NLogConfigurationIgnorePropertyAttribute _ignorePropertyAttribute = new NLogConfigurationIgnorePropertyAttribute(); private static readonly NLogConfigurationItemAttribute _configPropertyAttribute = new NLogConfigurationItemAttribute(); private static readonly FlagsAttribute _flagsAttribute = new FlagsAttribute(); #pragma warning restore S1144 // Unused private types or members should be removed - private static Dictionary> BuildPropertyConversionMapper() + private static Dictionary> BuildPropertyConversionMapper() { - return new Dictionary>() + return new Dictionary>() { { typeof(Layout), TryParseLayoutValue }, { typeof(SimpleLayout), TryParseLayoutValue }, { typeof(ConditionExpression), TryParseConditionValue }, - { typeof(Encoding), TryParseEncodingValue }, + { typeof(Encoding), (stringvalue, factory) => PropertyTypeConverter.ConvertToEncoding(stringvalue) }, { typeof(string), (stringvalue, factory) => stringvalue }, { typeof(int), (stringvalue, factory) => Convert.ChangeType(stringvalue.Trim(), TypeCode.Int32, CultureInfo.InvariantCulture) }, { typeof(bool), (stringvalue, factory) => Convert.ChangeType(stringvalue.Trim(), TypeCode.Boolean, CultureInfo.InvariantCulture) }, - { typeof(CultureInfo), (stringvalue, factory) => TryParseCultureInfo(stringvalue) }, + { typeof(CultureInfo), (stringvalue, factory) => PropertyTypeConverter.ConvertToCultureInfo(stringvalue) }, { typeof(Type), (stringvalue, factory) => PropertyTypeConverter.ConvertToType(stringvalue.Trim(), true) }, { typeof(LineEndingMode), (stringvalue, factory) => LineEndingMode.FromString(stringvalue.Trim()) }, { typeof(Uri), (stringvalue, factory) => new Uri(stringvalue.Trim()) } @@ -84,7 +86,7 @@ private static Dictionary> internal static void SetPropertyFromString(object targetObject, PropertyInfo propInfo, string stringValue, ConfigurationItemFactory configurationItemFactory) { - object propertyValue = null; + object? propertyValue = null; try { @@ -119,7 +121,7 @@ internal static void SetPropertyFromString(object targetObject, PropertyInfo pro SetPropertyValueForObject(targetObject, propertyValue, propInfo); } - internal static void SetPropertyValueForObject(object targetObject, object value, PropertyInfo propInfo) + internal static void SetPropertyValueForObject(object targetObject, object? value, PropertyInfo propInfo) { try { @@ -148,7 +150,7 @@ internal static void SetPropertyValueForObject(object targetObject, object value /// property name on /// result when success. /// success. - internal static bool TryGetPropertyInfo(ConfigurationItemFactory configFactory, object obj, string propertyName, out PropertyInfo result) + internal static bool TryGetPropertyInfo(ConfigurationItemFactory configFactory, object obj, string propertyName, out PropertyInfo? result) { var configProperties = TryLookupConfigItemProperties(configFactory, obj.GetType()); if (configProperties is null) @@ -169,7 +171,7 @@ internal static bool TryGetPropertyInfo(ConfigurationItemFactory configFactory, [UnconditionalSuppressMessage("Trimming - Ignore since obsolete", "IL2075")] [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - private static bool TryGetPropertyInfo(object obj, string propertyName, out PropertyInfo result) + private static bool TryGetPropertyInfo(object obj, string propertyName, out PropertyInfo? result) { InternalLogger.Debug("Object reflection needed to configure instance of type: {0} (Lookup property={1})", obj.GetType(), propertyName); @@ -184,13 +186,13 @@ private static bool TryGetPropertyInfo(object obj, string propertyName, out Prop return false; } - internal static Type GetArrayItemType(PropertyInfo propInfo) + internal static Type? GetArrayItemType(PropertyInfo propInfo) { var arrayParameterAttribute = propInfo.GetFirstCustomAttribute(); return arrayParameterAttribute?.ItemType; } - internal static bool IsConfigurationItemType(ConfigurationItemFactory configFactory, Type type) + internal static bool IsConfigurationItemType(ConfigurationItemFactory configFactory, Type? type) { if (type is null || IsSimplePropertyType(type)) return false; @@ -209,7 +211,7 @@ internal static Dictionary GetAllConfigItemProperties(Conf return TryLookupConfigItemProperties(configFactory, type) ?? new Dictionary(); } - private static Dictionary TryLookupConfigItemProperties(ConfigurationItemFactory configFactory, Type type) + private static Dictionary? TryLookupConfigItemProperties(ConfigurationItemFactory configFactory, Type type) { lock (_parameterInfoCache) { @@ -229,34 +231,9 @@ private static Dictionary TryLookupConfigItemProperties(Co } } - internal static void CheckRequiredParameters(ConfigurationItemFactory configFactory, object o) - { - foreach (var configProp in GetAllConfigItemProperties(configFactory, o.GetType())) - { - var propInfo = configProp.Value; - var propertyType = propInfo.PropertyType; - if (propertyType != null && (propertyType.IsClass() || Nullable.GetUnderlyingType(propertyType) != null)) - { - if (propInfo.IsDefined(_requiredParameterAttribute.GetType(), false)) - { - object value = propInfo.GetValue(o, null); - if (value is null) - { - throw new NLogConfigurationException( - $"Required parameter '{propInfo.Name}' on '{o}' was not specified."); - } - } - } - } - } - internal static bool IsSimplePropertyType(Type type) { -#if !NETSTANDARD1_3 if (Type.GetTypeCode(type) != TypeCode.Object) -#else - if (type.IsPrimitive() || type == typeof(string)) -#endif return true; if (type == typeof(CultureInfo)) @@ -275,7 +252,7 @@ internal static bool IsSimplePropertyType(Type type) } [UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL2070")] - private static bool TryImplicitConversion(Type resultType, string value, out object result) + private static bool TryImplicitConversion(Type resultType, string value, out object? result) { try { @@ -303,7 +280,8 @@ private static bool TryImplicitConversion(Type resultType, string value, out obj return false; } - private static bool TryNLogSpecificConversion(Type propertyType, string value, ConfigurationItemFactory configurationItemFactory, out object newValue) + [UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL2067")] + private static bool TryNLogSpecificConversion(Type propertyType, string value, ConfigurationItemFactory configurationItemFactory, out object? newValue) { if (_propertyConversionMapper.TryGetValue(propertyType, out var objectConverter)) { @@ -311,11 +289,10 @@ private static bool TryNLogSpecificConversion(Type propertyType, string value, C return true; } - if (propertyType.IsGenericType() && propertyType.GetGenericTypeDefinition() == typeof(Layout<>)) + if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Layout<>)) { - var simpleLayout = new SimpleLayout(value, configurationItemFactory); - var concreteType = typeof(Layout<>).MakeGenericType(propertyType.GetGenericArguments()); - newValue = Activator.CreateInstance(concreteType, BindingFlags.Instance | BindingFlags.Public, null, new object[] { simpleLayout }, null); + var simpleLayout = string.IsNullOrEmpty(value) ? SimpleLayout.Default : new SimpleLayout(value, configurationItemFactory); + newValue = Activator.CreateInstance(propertyType, BindingFlags.Instance | BindingFlags.Public, null, new object[] { simpleLayout }, null); return true; } @@ -323,9 +300,9 @@ private static bool TryNLogSpecificConversion(Type propertyType, string value, C return false; } - private static bool TryGetEnumValue(Type resultType, string value, out object result) + private static bool TryGetEnumValue(Type resultType, string value, out object? result) { - if (!resultType.IsEnum()) + if (!resultType.IsEnum) { result = null; return false; @@ -351,27 +328,9 @@ private static bool TryGetEnumValue(Type resultType, string value, out object re } } - private static object TryParseCultureInfo(string stringValue) - { - stringValue = stringValue?.Trim(); - if (string.IsNullOrEmpty(stringValue)) - return CultureInfo.InvariantCulture; - else - return new CultureInfo(stringValue); - } - - private static object TryParseEncodingValue(string stringValue, ConfigurationItemFactory configurationItemFactory) - { - _ = configurationItemFactory; // Discard unreferenced parameter - stringValue = stringValue.Trim(); - if (string.Equals(stringValue, nameof(Encoding.UTF8), StringComparison.OrdinalIgnoreCase)) - stringValue = Encoding.UTF8.WebName; // Support utf8 without hyphen (And not just Utf-8) - return Encoding.GetEncoding(stringValue); - } - private static object TryParseLayoutValue(string stringValue, ConfigurationItemFactory configurationItemFactory) { - return new SimpleLayout(stringValue, configurationItemFactory); + return string.IsNullOrEmpty(stringValue) ? Layout.Empty : new SimpleLayout(stringValue, configurationItemFactory); } private static object TryParseConditionValue(string stringValue, ConfigurationItemFactory configurationItemFactory) @@ -392,59 +351,141 @@ private static object TryParseConditionValue(string stringValue, ConfigurationIt /// /// If there is a comma in the value, then (single) quote the value. For single quotes, use the backslash as escape /// - private static bool TryFlatListConversion(object obj, PropertyInfo propInfo, string valueRaw, ConfigurationItemFactory configurationItemFactory, out object newValue) + private static bool TryFlatListConversion(object obj, PropertyInfo propInfo, string valueRaw, ConfigurationItemFactory configurationItemFactory, out object? newValue) { - if (propInfo.PropertyType.IsGenericType() && TryCreateCollectionObject(obj, propInfo, valueRaw, out var newList, out var collectionAddMethod, out var propertyType)) + var collectionType = propInfo.PropertyType; + if (!collectionType.IsGenericType || !typeof(IEnumerable).IsAssignableFrom(collectionType)) { - var values = valueRaw.SplitQuoted(',', '\'', '\\'); - foreach (var value in values) + newValue = null; + return false; + } + + try + { + if (TryCreateCollectionObject(obj, propInfo, out var newList, out var collectionAddMethod, out var propertyType) && collectionAddMethod != null && propertyType != null) { - if (!(TryGetEnumValue(propertyType, value, out newValue) - || TryNLogSpecificConversion(propertyType, value, configurationItemFactory, out newValue) - || TryImplicitConversion(propertyType, value, out newValue) - || TryTypeConverterConversion(propertyType, value, out newValue))) + var values = valueRaw.SplitQuoted(',', '\'', '\\'); + foreach (var value in values) { - newValue = Convert.ChangeType(value, propertyType, CultureInfo.InvariantCulture); + if (!(TryGetEnumValue(propertyType, value, out newValue) + || TryNLogSpecificConversion(propertyType, value, configurationItemFactory, out newValue) + || TryImplicitConversion(propertyType, value, out newValue) + || TryTypeConverterConversion(propertyType, value, out newValue))) + { + newValue = Convert.ChangeType(value, propertyType, CultureInfo.InvariantCulture); + } + + collectionAddMethod.Invoke(newList, new object?[] { newValue }); } - collectionAddMethod.Invoke(newList, new object[] { newValue }); + newValue = newList; + return true; } - newValue = newList; - return true; + newValue = null; + return false; + } + catch (Exception ex) + { + throw new NLogConfigurationException($"Failed to parse collection for property '{propInfo.Name}' on {obj.GetType()} with value '{valueRaw}'", ex); } - - newValue = null; - return false; } - private static bool TryCreateCollectionObject(object obj, PropertyInfo propInfo, string valueRaw, out object collectionObject, out MethodInfo collectionAddMethod, out Type collectionItemType) + [UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL2072")] + [UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL2075")] + private static bool TryCreateCollectionObject(object obj, PropertyInfo propInfo, out object? collectionObject, out MethodInfo? collectionAddMethod, out Type? collectionItemType) { + collectionObject = null; + collectionAddMethod = null; + collectionItemType = null; + var collectionType = propInfo.PropertyType; + if (TryCreateListCollection(collectionType, out collectionObject, out collectionAddMethod, out collectionItemType)) + return true; + + if (TryCreateListCollection(collectionType, out collectionObject, out collectionAddMethod, out collectionItemType)) + return true; + var typeDefinition = collectionType.GetGenericTypeDefinition(); + if (typeDefinition == typeof(List<>)) + { + collectionObject = Activator.CreateInstance(collectionType); + collectionAddMethod = collectionType.GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); + collectionItemType = collectionType.GetGenericArguments()[0]; + return true; + } + + if (typeDefinition != typeof(IList<>) && typeDefinition != typeof(IEnumerable<>) && typeDefinition != typeof(HashSet<>) #if !NET35 - var isSet = typeDefinition == typeof(ISet<>) || typeDefinition == typeof(HashSet<>); -#else - var isSet = typeDefinition == typeof(HashSet<>); + && typeDefinition != typeof(ISet<>) #endif - //not checking "implements" interface as we are creating HashSet or List and also those checks are expensive - if (isSet || typeDefinition == typeof(List<>) || typeDefinition == typeof(IList<>) || typeDefinition == typeof(IEnumerable<>)) //set or list/array etc - { - object hashsetComparer = isSet ? ExtractHashSetComparer(obj, propInfo) : null; + ) + return false; - //note: type.GenericTypeArguments is .NET 4.5+ - collectionItemType = collectionType.GetGenericArguments()[0]; - collectionObject = isSet ? CreateCollectionHashSetInstance(collectionItemType, hashsetComparer, out collectionAddMethod) : CreateCollectionListInstance(collectionItemType, out collectionAddMethod); - //no support for array - if (collectionObject is null) + var existingValue = propInfo.IsValidPublicProperty() ? propInfo.GetPropertyValue(obj) : null; + if (existingValue?.GetType().IsGenericType == true) + { + if (existingValue.GetType().GetGenericTypeDefinition() == typeof(List<>)) { - throw new NLogConfigurationException($"Cannot create instance of {collectionType.ToString()} for value {valueRaw}"); + var existingType = existingValue.GetType(); + collectionObject = Activator.CreateInstance(existingType); + collectionAddMethod = existingType.GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); + collectionItemType = existingType.GetGenericArguments()[0]; + return true; } - if (collectionAddMethod is null) + + if (existingValue.GetType().GetGenericTypeDefinition() == typeof(HashSet<>)) { - throw new NLogConfigurationException($"Add method on type {collectionType.ToString()} for value {valueRaw} not found"); + object? hashSetComparer = null; + var existingType = existingValue.GetType(); + var comparerPropInfo = existingType.GetProperty("Comparer", BindingFlags.Public | BindingFlags.Instance); + if (comparerPropInfo.IsValidPublicProperty()) + { + hashSetComparer = comparerPropInfo.GetPropertyValue(existingValue); + } + if (hashSetComparer != null) + { + var constructor = existingType.GetConstructor(new[] { hashSetComparer.GetType() }); + if (constructor != null) + collectionObject = constructor.Invoke(new[] { hashSetComparer }); + else + collectionObject = Activator.CreateInstance(existingType); + } + else + { + collectionObject = Activator.CreateInstance(existingType); + } + collectionAddMethod = existingType.GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); + collectionItemType = existingType.GetGenericArguments()[0]; + return true; } + } + + if (TryCreateHashSetCollection(collectionType, out collectionObject, out collectionAddMethod, out collectionItemType)) + return true; + + if (TryCreateHashSetCollection(collectionType, out collectionObject, out collectionAddMethod, out collectionItemType)) + return true; + if (typeDefinition == typeof(HashSet<>)) + { + collectionObject = Activator.CreateInstance(collectionType); + collectionAddMethod = collectionType.GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); + collectionItemType = collectionType.GetGenericArguments()[0]; + return true; + } + + InternalLogger.Debug("Object type reflection needed to configure instance of type: {0}", collectionType); + return TryCreateTypeCollection(collectionType, out collectionObject, out collectionAddMethod, out collectionItemType); + } + + private static bool TryCreateListCollection(Type collectionType, out object? collectionObject, out MethodInfo? collectionAddMethod, out Type? collectionItemType) + { + if (collectionType.IsAssignableFrom(typeof(List))) + { + collectionObject = new List(); + collectionAddMethod = typeof(List).GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); + collectionItemType = typeof(T); return true; } @@ -454,59 +495,48 @@ private static bool TryCreateCollectionObject(object obj, PropertyInfo propInfo, return false; } - private static object CreateCollectionHashSetInstance(Type collectionItemType, object hashSetComparer, out MethodInfo collectionAddMethod) + private static bool TryCreateHashSetCollection(Type collectionType, out object? collectionObject, out MethodInfo? collectionAddMethod, out Type? collectionItemType) { - var concreteType = typeof(HashSet<>).MakeGenericType(collectionItemType); - - collectionAddMethod = typeof(HashSet<>).MakeGenericType(collectionItemType).GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); - - if (hashSetComparer != null) + if (collectionType.IsAssignableFrom(typeof(HashSet))) { - var constructor = concreteType.GetConstructor(new[] { hashSetComparer.GetType() }); - if (constructor != null) - { - return constructor.Invoke(new[] { hashSetComparer }); - } + collectionObject = new HashSet(); + collectionAddMethod = typeof(HashSet).GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); + collectionItemType = typeof(T); + return true; } - return Activator.CreateInstance(concreteType); - } - - private static object CreateCollectionListInstance(Type collectionItemType, out MethodInfo collectionAddMethod) - { - var concreteType = typeof(List<>).MakeGenericType(collectionItemType); - - collectionAddMethod = typeof(List<>).MakeGenericType(collectionItemType).GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); - - return Activator.CreateInstance(concreteType); + collectionObject = null; + collectionAddMethod = null; + collectionItemType = null; + return false; } - /// - /// Attempt to reuse the HashSet.Comparer from the original HashSet-object (Ex. StringComparer.OrdinalIgnoreCase) - /// - private static object ExtractHashSetComparer(object obj, PropertyInfo propInfo) + [UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL3050")] + private static bool TryCreateTypeCollection(Type propertyType, out object collectionObject, out MethodInfo collectionAddMethod, out Type collectionItemType) { - var exitingValue = propInfo.IsValidPublicProperty() ? propInfo.GetPropertyValue(obj) : null; - if (exitingValue != null) +#if !NET35 + var typeDefinition = propertyType.GetGenericTypeDefinition(); + if (typeDefinition == typeof(ISet<>)) { - // Found original HashSet-object. See if we can extract the Comparer - if (exitingValue.GetType().GetGenericTypeDefinition() == typeof(HashSet<>)) - { - var comparerPropInfo = typeof(HashSet<>).MakeGenericType(exitingValue.GetType().GetGenericArguments()[0]).GetProperty("Comparer", BindingFlags.Instance | BindingFlags.Public); - if (comparerPropInfo.IsValidPublicProperty()) - { - return comparerPropInfo.GetPropertyValue(exitingValue); - } - } + var setType = typeof(HashSet<>).MakeGenericType(propertyType.GetGenericArguments()); + collectionAddMethod = setType.GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); + collectionItemType = propertyType.GetGenericArguments()[0]; + collectionObject = Activator.CreateInstance(setType); + return true; } +#endif - return null; + var listType = typeof(List<>).MakeGenericType(propertyType.GetGenericArguments()); + collectionAddMethod = listType.GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); + collectionItemType = propertyType.GetGenericArguments()[0]; + collectionObject = Activator.CreateInstance(listType); + return true; } [UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL2026")] [UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL2067")] [UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL2072")] - internal static bool TryTypeConverterConversion(Type type, string value, out object newValue) + internal static bool TryTypeConverterConversion(Type type, string value, out object? newValue) { if (typeof(IConvertible).IsAssignableFrom(type) || type.IsAssignableFrom(typeof(string))) { @@ -536,7 +566,7 @@ internal static bool TryTypeConverterConversion(Type type, string value, out obj } } - private static bool TryCreatePropertyInfoDictionary(ConfigurationItemFactory configFactory, Type objectType, out Dictionary result) + private static bool TryCreatePropertyInfoDictionary(ConfigurationItemFactory configFactory, Type objectType, out Dictionary? result) { result = null; @@ -605,13 +635,13 @@ private static bool HasCustomConfigurationProperties(Type objectType, Dictionary return false; } - private static bool IncludeConfigurationPropertyInfo(Type objectType, PropertyInfo propInfo, bool checkDefaultValue, out string overridePropertyName) + private static bool IncludeConfigurationPropertyInfo(Type objectType, PropertyInfo propInfo, bool checkDefaultValue, out string? overridePropertyName) { overridePropertyName = null; try { - var propertyType = propInfo?.PropertyType; + var propertyType = propInfo.PropertyType; if (propertyType is null) return false; diff --git a/src/NLog/Internal/ReflectionHelpers.cs b/src/NLog/Internal/ReflectionHelpers.cs index 90cebda36a..ffcf88e432 100644 --- a/src/NLog/Internal/ReflectionHelpers.cs +++ b/src/NLog/Internal/ReflectionHelpers.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using JetBrains.Annotations; - namespace NLog.Internal { using System; @@ -56,7 +54,7 @@ internal static class ReflectionHelpers /// public static bool IsStaticClass(this Type type) { - return type.IsClass() && type.IsAbstract() && type.IsSealed(); + return type.IsClass && type.IsAbstract && type.IsSealed; } /// @@ -64,13 +62,7 @@ public static bool IsStaticClass(this Type type) /// /// Object instance, use null for static methods. /// Complete list of parameters that matches the method, including optional/default parameters. - public delegate object LateBoundMethod(object target, object[] arguments); - - /// - /// Optimized delegate for calling a constructor - /// - /// Complete list of parameters that matches the constructor, including optional/default parameters. Could be null for no parameters. - public delegate object LateBoundConstructor([CanBeNull] object[] arguments); + public delegate object? LateBoundMethod(object target, object?[] arguments); /// /// Creates an optimized delegate for calling the MethodInfo using Expression-Trees @@ -79,6 +71,28 @@ public static bool IsStaticClass(this Type type) /// Optimized delegate for invoking the MethodInfo public static LateBoundMethod CreateLateBoundMethod(MethodInfo methodInfo) { +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER + if (!System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported) + { + return (target, args) => + { + try + { + return methodInfo.Invoke(target, args); + } + catch (TargetInvocationException exception) + { + throw exception.InnerException ?? exception; + } + }; + } +#endif + + return CompileLateBoundMethod(methodInfo); + } + + private static LateBoundMethod CompileLateBoundMethod(MethodInfo methodInfo) + { var instanceParameter = Expression.Parameter(typeof(object), "instance"); var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); @@ -88,10 +102,10 @@ public static LateBoundMethod CreateLateBoundMethod(MethodInfo methodInfo) // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...) if (methodCall.Type == typeof(void)) { - var lambda = Expression.Lambda>( + var lambda = Expression.Lambda>( methodCall, instanceParameter, parametersParameter); - Action execute = lambda.Compile(); + Action execute = lambda.Compile(); return (instance, parameters) => { execute(instance, parameters); @@ -108,25 +122,6 @@ public static LateBoundMethod CreateLateBoundMethod(MethodInfo methodInfo) } } - /// - /// Creates an optimized delegate for calling the constructors using Expression-Trees - /// - /// Constructor to optimize - /// Optimized delegate for invoking the constructor - public static LateBoundConstructor CreateLateBoundConstructor(ConstructorInfo constructor) - { - var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); - - // build parameter list - var parameterExpressions = BuildParameterList(constructor, parametersParameter); - - var ctorCall = Expression.New(constructor, parameterExpressions); - - var lambda = Expression.Lambda(ctorCall, parametersParameter); - - return lambda.Compile(); - } - private static IEnumerable BuildParameterList(MethodBase methodInfo, ParameterExpression parametersParameter) { var parameterExpressions = new List(); @@ -163,128 +158,26 @@ private static UnaryExpression CreateParameterExpression(ParameterInfo parameter return valueCast; } - public static bool IsPublic(this Type type) - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - return type.IsPublic; -#else - return type.GetTypeInfo().IsPublic; -#endif - } - - public static bool IsEnum(this Type type) - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - return type.IsEnum; -#else - return type.GetTypeInfo().IsEnum; -#endif - } - - public static bool IsPrimitive(this Type type) - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - return type.IsPrimitive; -#else - return type.GetTypeInfo().IsPrimitive; -#endif - } - - public static bool IsValueType(this Type type) + public static TAttr? GetFirstCustomAttribute(this Type type) where TAttr : Attribute { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - return type.IsValueType; -#else - return type.GetTypeInfo().IsValueType; -#endif - } - - public static bool IsSealed(this Type type) - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - return type.IsSealed; -#else - return type.GetTypeInfo().IsSealed; -#endif - } - - public static bool IsAbstract(this Type type) - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - return type.IsAbstract; -#else - return type.GetTypeInfo().IsAbstract; -#endif - } - - public static bool IsClass(this Type type) - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - return type.IsClass; -#else - return type.GetTypeInfo().IsClass; -#endif - } - - public static bool IsGenericType(this Type type) - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - return type.IsGenericType; -#else - return type.GetTypeInfo().IsGenericType; -#endif - } - - [CanBeNull] - public static TAttr GetFirstCustomAttribute(this Type type) where TAttr : Attribute - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 return Attribute.GetCustomAttributes(type, typeof(TAttr)).FirstOrDefault() as TAttr; -#else - var typeInfo = type.GetTypeInfo(); - return typeInfo.GetCustomAttributes().FirstOrDefault(); -#endif } - [CanBeNull] - public static TAttr GetFirstCustomAttribute(this PropertyInfo info) + public static TAttr? GetFirstCustomAttribute(this PropertyInfo info) where TAttr : Attribute { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 return Attribute.GetCustomAttributes(info, typeof(TAttr)).FirstOrDefault() as TAttr; -#else - return info.GetCustomAttributes(typeof(TAttr), false).FirstOrDefault() as TAttr; -#endif } - [CanBeNull] - public static TAttr GetFirstCustomAttribute(this Assembly assembly) + public static TAttr? GetFirstCustomAttribute(this Assembly assembly) where TAttr : Attribute { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 return Attribute.GetCustomAttributes(assembly, typeof(TAttr)).FirstOrDefault() as TAttr; -#else - return assembly.GetCustomAttributes(typeof(TAttr)).FirstOrDefault() as TAttr; -#endif } public static IEnumerable GetCustomAttributes(this Type type, bool inherit) where TAttr : Attribute { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 return (TAttr[])type.GetCustomAttributes(typeof(TAttr), inherit); -#else - return type.GetTypeInfo().GetCustomAttributes(inherit); -#endif - } - - public static Assembly GetAssembly(this Type type) - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - return type.Assembly; -#else - var typeInfo = type.GetTypeInfo(); - return typeInfo.Assembly; -#endif } public static bool IsValidPublicProperty(this PropertyInfo p) @@ -298,15 +191,6 @@ public static object GetPropertyValue(this PropertyInfo p, object instance) return p.GetValue(instance); #else return p.GetGetMethod().Invoke(instance, null); -#endif - } - - public static MethodInfo GetDelegateInfo(this Delegate method) - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - return method.Method; -#else - return System.Reflection.RuntimeReflectionExtensions.GetMethodInfo(method); #endif } } diff --git a/src/NLog/Internal/ReusableAsyncLogEventList.cs b/src/NLog/Internal/ReusableAsyncLogEventList.cs index 8189e0a67d..c614816232 100644 --- a/src/NLog/Internal/ReusableAsyncLogEventList.cs +++ b/src/NLog/Internal/ReusableAsyncLogEventList.cs @@ -31,11 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System.Collections.Generic; -using NLog.Common; - namespace NLog.Internal { + using System.Collections.Generic; + using NLog.Common; + /// /// Controls a single allocated AsyncLogEventInfo-List for reuse (only one active user) /// diff --git a/src/NLog/Internal/ReusableBuilderCreator.cs b/src/NLog/Internal/ReusableBuilderCreator.cs index abae711d22..bfedaae321 100644 --- a/src/NLog/Internal/ReusableBuilderCreator.cs +++ b/src/NLog/Internal/ReusableBuilderCreator.cs @@ -31,10 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System.Text; - namespace NLog.Internal { + using System.Text; + /// /// Controls a single allocated StringBuilder for reuse (only one active user) /// diff --git a/src/NLog/Internal/ReusableObjectCreator.cs b/src/NLog/Internal/ReusableObjectCreator.cs index f2ef85611f..56209a0dde 100644 --- a/src/NLog/Internal/ReusableObjectCreator.cs +++ b/src/NLog/Internal/ReusableObjectCreator.cs @@ -31,16 +31,16 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; - namespace NLog.Internal { + using System; + /// /// Controls a single allocated object for reuse (only one active user) /// internal class ReusableObjectCreator where T : class { - protected T _reusableObject; + protected T? _reusableObject; private readonly Action _clearObject; private readonly Func _createObject; @@ -69,7 +69,11 @@ private void Deallocate(T reusableObject) _reusableObject = reusableObject; } - public struct LockOject : IDisposable + public +#if !NETFRAMEWORK + readonly +#endif + struct LockOject : IDisposable { /// /// Access the acquired reusable object diff --git a/src/NLog/Internal/ReusableStreamCreator.cs b/src/NLog/Internal/ReusableStreamCreator.cs index 479e3acc89..bf6d482e1c 100644 --- a/src/NLog/Internal/ReusableStreamCreator.cs +++ b/src/NLog/Internal/ReusableStreamCreator.cs @@ -31,11 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.IO; - namespace NLog.Internal { + using System; + using System.IO; + /// /// Controls a single allocated MemoryStream for reuse (only one active user) /// @@ -73,7 +73,7 @@ private static void ResetBatchCapacity(MemoryStream memoryStream) void IDisposable.Dispose() { - _reusableObject.Dispose(); + _reusableObject?.Dispose(); } } } diff --git a/src/NLog/Internal/ScopeContextAsyncState.cs b/src/NLog/Internal/ScopeContextAsyncState.cs index 4e9880d648..4b29d50c18 100644 --- a/src/NLog/Internal/ScopeContextAsyncState.cs +++ b/src/NLog/Internal/ScopeContextAsyncState.cs @@ -43,10 +43,10 @@ namespace NLog.Internal /// internal abstract class ScopeContextAsyncState : IDisposable { - public IScopeContextAsyncState Parent { get; } + public IScopeContextAsyncState? Parent { get; } private bool _disposed; - protected ScopeContextAsyncState(IScopeContextAsyncState parent) + protected ScopeContextAsyncState(IScopeContextAsyncState? parent) { Parent = parent; } @@ -66,28 +66,28 @@ void IDisposable.Dispose() /// internal interface IScopeContextAsyncState : IDisposable { - IScopeContextAsyncState Parent { get; } - object NestedState { get; } + IScopeContextAsyncState? Parent { get; } + object? NestedState { get; } long NestedStateTimestamp { get; } - IReadOnlyCollection> CaptureContextProperties(ref ScopeContextPropertyCollector contextCollector); - IList CaptureNestedContext(ref ScopeContextNestedStateCollector contextCollector); + IReadOnlyCollection>? CaptureContextProperties(ref ScopeContextPropertyCollector contextCollector); + IList? CaptureNestedContext(ref ScopeContextNestedStateCollector contextCollector); } struct ScopeContextPropertyCollector { - IReadOnlyCollection> _allProperties; - List> _propertyCollector; + IReadOnlyCollection>? _allProperties; + List> _propertyCollector; public bool IsCollectorEmpty => _allProperties is null || (_allProperties.Count == 0 && _propertyCollector is null); public bool IsCollectorInactive => _allProperties is null; - public ScopeContextPropertyCollector(List> propertyCollector = null) + public ScopeContextPropertyCollector(List> propertyCollector) { _allProperties = _propertyCollector = propertyCollector; } - public IReadOnlyCollection> StartCaptureProperties(IScopeContextAsyncState state) + public IReadOnlyCollection> StartCaptureProperties(IScopeContextAsyncState? state) { while (state != null) { @@ -100,7 +100,7 @@ public IReadOnlyCollection> StartCaptureProperties( return CaptureCompleted(null); } - public IReadOnlyCollection> CaptureCompleted(IReadOnlyCollection> properties) + public IReadOnlyCollection> CaptureCompleted(IReadOnlyCollection>? properties) { if (_allProperties?.Count > 0) { @@ -108,7 +108,7 @@ public IReadOnlyCollection> CaptureCompleted(IReadO { if (_propertyCollector is null) { - return _allProperties = MergeUniqueProperties(properties); + return _allProperties = MergeUniqueProperties(_allProperties, properties); } AddProperties(properties); @@ -121,19 +121,19 @@ public IReadOnlyCollection> CaptureCompleted(IReadO if (properties?.Count > 0) return _allProperties = EnsureUniqueProperties(properties); else - return _allProperties = Array.Empty>(); + return _allProperties = Array.Empty>(); } } - private IReadOnlyCollection> MergeUniqueProperties(IReadOnlyCollection> properties) + private static IReadOnlyCollection> MergeUniqueProperties(IReadOnlyCollection> currentProperties, IReadOnlyCollection> properties) { - var scopeProperties = new Dictionary(_allProperties.Count + properties.Count, ScopeContext.DefaultComparer); + var scopeProperties = new Dictionary(currentProperties.Count + properties.Count, ScopeContext.DefaultComparer); ScopeContextPropertyEnumerator.CopyScopePropertiesToDictionary(properties, scopeProperties); - ScopeContextPropertyEnumerator.CopyScopePropertiesToDictionary(_allProperties, scopeProperties); + ScopeContextPropertyEnumerator.CopyScopePropertiesToDictionary(currentProperties, scopeProperties); return scopeProperties; } - private static IReadOnlyCollection> EnsureUniqueProperties(IReadOnlyCollection> properties) + private static IReadOnlyCollection> EnsureUniqueProperties(IReadOnlyCollection> properties) { var scopePropertyCount = properties.Count; if (scopePropertyCount > 1) @@ -143,9 +143,9 @@ private static IReadOnlyCollection> EnsureUniquePro { return properties; } - else if (scopePropertyCount > 10 || !ScopeContextPropertyEnumerator.HasUniqueCollectionKeys(properties, ScopeContext.DefaultComparer)) + else if (scopePropertyCount > 10 || !ScopeContextPropertyEnumerator.HasUniqueCollectionKeys(properties, ScopeContext.DefaultComparer)) { - var scopeProperties = new Dictionary(Math.Min(scopePropertyCount, 10), ScopeContext.DefaultComparer); + var scopeProperties = new Dictionary(Math.Min(scopePropertyCount, 10), ScopeContext.DefaultComparer); ScopeContextPropertyEnumerator.CopyScopePropertiesToDictionary(properties, scopeProperties); return scopeProperties; } @@ -154,31 +154,31 @@ private static IReadOnlyCollection> EnsureUniquePro return properties; } - public void AddProperty(string propertyName, object propertyValue) + public void AddProperty(string propertyName, object? propertyValue) { - if (IsCollectorEmpty) + if (_allProperties is null || IsCollectorEmpty) { - _allProperties = new[] { new KeyValuePair(propertyName, propertyValue) }; + _allProperties = new[] { new KeyValuePair(propertyName, propertyValue) }; } else { if (_propertyCollector is null) { - _propertyCollector = new List>(Math.Max(4, _allProperties.Count + 1)); - _propertyCollector.Add(new KeyValuePair(propertyName, propertyValue)); + _propertyCollector = new List>(Math.Max(4, _allProperties.Count + 1)); + _propertyCollector.Add(new KeyValuePair(propertyName, propertyValue)); CollectProperties(_allProperties); _allProperties = _propertyCollector; } else { - _propertyCollector.Insert(0, new KeyValuePair(propertyName, propertyValue)); + _propertyCollector.Insert(0, new KeyValuePair(propertyName, propertyValue)); } } } - public void AddProperties(IReadOnlyCollection> properties) + public void AddProperties(IReadOnlyCollection> properties) { - if (IsCollectorEmpty) + if (_allProperties is null || IsCollectorEmpty) { _allProperties = properties; } @@ -186,7 +186,7 @@ public void AddProperties(IReadOnlyCollection> prop { if (_propertyCollector is null) { - _propertyCollector = new List>(Math.Max(4, _allProperties.Count + properties.Count)); + _propertyCollector = new List>(Math.Max(4, _allProperties.Count + properties.Count)); CollectProperties(properties); CollectProperties(_allProperties); _allProperties = _propertyCollector; @@ -198,7 +198,7 @@ public void AddProperties(IReadOnlyCollection> prop else { int insertPosition = 0; - using (var scopeEnumerator = new ScopeContextPropertyEnumerator(properties)) + using (var scopeEnumerator = new ScopeContextPropertyEnumerator(properties)) { while (scopeEnumerator.MoveNext()) { @@ -210,9 +210,9 @@ public void AddProperties(IReadOnlyCollection> prop } } - private void CollectProperties(IReadOnlyCollection> properties) + private void CollectProperties(IReadOnlyCollection> properties) { - using (var scopeEnumerator = new ScopeContextPropertyEnumerator(properties)) + using (var scopeEnumerator = new ScopeContextPropertyEnumerator(properties)) { while (scopeEnumerator.MoveNext()) { @@ -232,7 +232,7 @@ struct ScopeContextNestedStateCollector public bool IsCollectorInactive => _allNestedStates is null; - public IList StartCaptureNestedStates(IScopeContextAsyncState state) + public IList StartCaptureNestedStates(IScopeContextAsyncState? state) { _allNestedStates = _allNestedStates ?? Array.Empty(); @@ -277,47 +277,51 @@ internal sealed class ScopedContextNestedAsyncState : ScopeContextAsyncState, { private readonly T _value; - public ScopedContextNestedAsyncState(IScopeContextAsyncState parent, T state) + public ScopedContextNestedAsyncState(IScopeContextAsyncState? parent, T state) : base(parent) { NestedStateTimestamp = ScopeContext.GetNestedContextTimestampNow(); _value = state; } - object IScopeContextAsyncState.NestedState => _value; + object? IScopeContextAsyncState.NestedState => _value; public long NestedStateTimestamp { get; } - IList IScopeContextAsyncState.CaptureNestedContext(ref ScopeContextNestedStateCollector contextCollector) + IList? IScopeContextAsyncState.CaptureNestedContext(ref ScopeContextNestedStateCollector contextCollector) { + object? objectValue = _value; + if (contextCollector.IsCollectorEmpty) { if (Parent is null) { - return new object[] { _value }; // We are done + return objectValue is null ? Array.Empty() : new object[] { objectValue }; // We are done } else if (contextCollector.IsCollectorInactive) { - contextCollector.PushNestedState(_value); // Mark as active + if (objectValue is not null) + contextCollector.PushNestedState(objectValue); // Mark as active return contextCollector.StartCaptureNestedStates(Parent); } } - contextCollector.PushNestedState(_value); + if (objectValue is not null) + contextCollector.PushNestedState(objectValue); return null; // continue with parent } - IReadOnlyCollection> IScopeContextAsyncState.CaptureContextProperties(ref ScopeContextPropertyCollector contextCollector) + IReadOnlyCollection>? IScopeContextAsyncState.CaptureContextProperties(ref ScopeContextPropertyCollector contextCollector) { if (contextCollector.IsCollectorInactive) { if (Parent is null) { - return Array.Empty>(); // We are done + return Array.Empty>(); // We are done } else { - contextCollector.AddProperties(Array.Empty>()); // Mark as active + contextCollector.AddProperties(Array.Empty>()); // Mark as active return contextCollector.StartCaptureProperties(Parent); // Start parent enumeration } } @@ -339,19 +343,19 @@ public override string ToString() internal sealed class ScopeContextPropertyAsyncState : ScopeContextAsyncState, IScopeContextPropertiesAsyncState { long IScopeContextAsyncState.NestedStateTimestamp => 0; - object IScopeContextAsyncState.NestedState => null; + object? IScopeContextAsyncState.NestedState => null; public string Name { get; } - public TValue Value { get; } - private IReadOnlyCollection> _allProperties; + public TValue? Value { get; } + private IReadOnlyCollection>? _allProperties; - public ScopeContextPropertyAsyncState(IScopeContextAsyncState parent, string name, TValue value) + public ScopeContextPropertyAsyncState(IScopeContextAsyncState? parent, string name, TValue? value) : base(parent) { Name = name; Value = value; } - IList IScopeContextAsyncState.CaptureNestedContext(ref ScopeContextNestedStateCollector contextCollector) + IList? IScopeContextAsyncState.CaptureNestedContext(ref ScopeContextNestedStateCollector contextCollector) { if (contextCollector.IsCollectorInactive) { @@ -368,7 +372,7 @@ IList IScopeContextAsyncState.CaptureNestedContext(ref ScopeContextNeste return null; // Continue with Parent } - IReadOnlyCollection> IScopeContextAsyncState.CaptureContextProperties(ref ScopeContextPropertyCollector contextCollector) + IReadOnlyCollection>? IScopeContextAsyncState.CaptureContextProperties(ref ScopeContextPropertyCollector contextCollector) { if (contextCollector.IsCollectorEmpty) { @@ -402,35 +406,37 @@ public override string ToString() /// /// Immutable state for ScopeContext Multiple Properties (MDLC) /// - internal sealed class ScopeContextPropertiesAsyncState : ScopeContextAsyncState, IScopeContextPropertiesAsyncState, IReadOnlyCollection> + internal sealed class ScopeContextPropertiesAsyncState : ScopeContextAsyncState, IScopeContextPropertiesAsyncState, IReadOnlyCollection> { public long NestedStateTimestamp { get; } - public object NestedState { get; } + public object? NestedState { get; } - private readonly IReadOnlyCollection> _scopeProperties; - private IReadOnlyCollection> _allProperties; + private readonly IReadOnlyCollection> _scopeProperties; + private IReadOnlyCollection>? _allProperties; - public ScopeContextPropertiesAsyncState(IScopeContextAsyncState parent, Dictionary allProperties) + public ScopeContextPropertiesAsyncState(IScopeContextAsyncState? parent, Dictionary allProperties) : base(parent) { _allProperties = allProperties; // Collapsed dictionary that includes all properties from parent scopes with case-insensitive-comparer + _scopeProperties = Array.Empty>(); } - public ScopeContextPropertiesAsyncState(IScopeContextAsyncState parent, Dictionary allProperties, object nestedState) + public ScopeContextPropertiesAsyncState(IScopeContextAsyncState? parent, Dictionary allProperties, object? nestedState) : base(parent) { _allProperties = allProperties; // Collapsed dictionary that includes all properties from parent scopes with case-insensitive-comparer + _scopeProperties = Array.Empty>(); NestedState = nestedState; NestedStateTimestamp = ScopeContext.GetNestedContextTimestampNow(); } - public ScopeContextPropertiesAsyncState(IScopeContextAsyncState parent, IReadOnlyCollection> scopeProperties) + public ScopeContextPropertiesAsyncState(IScopeContextAsyncState? parent, IReadOnlyCollection> scopeProperties) : base(parent) { _scopeProperties = scopeProperties; } - public ScopeContextPropertiesAsyncState(IScopeContextAsyncState parent, IReadOnlyCollection> scopeProperties, object nestedState) + public ScopeContextPropertiesAsyncState(IScopeContextAsyncState? parent, IReadOnlyCollection> scopeProperties, object? nestedState) : base(parent) { _scopeProperties = scopeProperties; @@ -438,7 +444,7 @@ public ScopeContextPropertiesAsyncState(IScopeContextAsyncState parent, IReadOnl NestedStateTimestamp = ScopeContext.GetNestedContextTimestampNow(); } - IList IScopeContextAsyncState.CaptureNestedContext(ref ScopeContextNestedStateCollector contextCollector) + IList? IScopeContextAsyncState.CaptureNestedContext(ref ScopeContextNestedStateCollector contextCollector) { if (NestedState is null) { @@ -467,13 +473,13 @@ IList IScopeContextAsyncState.CaptureNestedContext(ref ScopeContextNeste } } - IReadOnlyCollection> IScopeContextAsyncState.CaptureContextProperties(ref ScopeContextPropertyCollector contextCollector) + IReadOnlyCollection>? IScopeContextAsyncState.CaptureContextProperties(ref ScopeContextPropertyCollector contextCollector) { if (contextCollector.IsCollectorEmpty) { if (_allProperties is null) { - contextCollector.AddProperties(_scopeProperties as IReadOnlyCollection> ?? this); + contextCollector.AddProperties(_scopeProperties as IReadOnlyCollection> ?? this); _allProperties = contextCollector.StartCaptureProperties(Parent); // Capture all properties from parents } return _allProperties; // We are done @@ -482,7 +488,7 @@ IReadOnlyCollection> IScopeContextAsyncState.Captur { if (_allProperties is null) { - contextCollector.AddProperties(_scopeProperties as IReadOnlyCollection> ?? this); + contextCollector.AddProperties(_scopeProperties as IReadOnlyCollection> ?? this); return null; // Continue with Parent } else @@ -499,9 +505,9 @@ public override string ToString() public int Count => _scopeProperties.Count; - IEnumerator> IEnumerable>.GetEnumerator() => new ScopeContextPropertyEnumerator(_scopeProperties); + IEnumerator> IEnumerable>.GetEnumerator() => new ScopeContextPropertyEnumerator(_scopeProperties); - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => new ScopeContextPropertyEnumerator(_scopeProperties); + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => new ScopeContextPropertyEnumerator(_scopeProperties); } /// @@ -511,10 +517,10 @@ public override string ToString() internal sealed class ScopeContextLegacyAsyncState : ScopeContextAsyncState, IScopeContextAsyncState { public object[] NestedContext { get; } - public IReadOnlyCollection> MappedContext { get; } + public IReadOnlyCollection>? MappedContext { get; } public long NestedStateTimestamp { get; } - public ScopeContextLegacyAsyncState(IReadOnlyCollection> allProperties, object[] nestedContext, long nestedContextTimestamp) + public ScopeContextLegacyAsyncState(IReadOnlyCollection>? allProperties, object[] nestedContext, long nestedContextTimestamp) : base(null) // Always top parent { MappedContext = allProperties; @@ -522,13 +528,13 @@ public ScopeContextLegacyAsyncState(IReadOnlyCollection allProperties, out object[] nestedContext, out long nestedContextTimestamp) + public static void CaptureLegacyContext(IScopeContextAsyncState? contextState, out Dictionary allProperties, out object[] nestedContext, out long nestedContextTimestamp) { var nestedStateCollector = new ScopeContextNestedStateCollector(); var propertyCollector = new ScopeContextPropertyCollector(); var nestedStates = contextState?.CaptureNestedContext(ref nestedStateCollector) ?? Array.Empty(); - var scopeProperties = contextState?.CaptureContextProperties(ref propertyCollector) ?? Array.Empty>(); - allProperties = new Dictionary(scopeProperties.Count, ScopeContext.DefaultComparer); + var scopeProperties = contextState?.CaptureContextProperties(ref propertyCollector) ?? Array.Empty>(); + allProperties = new Dictionary(scopeProperties.Count, ScopeContext.DefaultComparer); ScopeContextPropertyEnumerator.CopyScopePropertiesToDictionary(scopeProperties, allProperties); nestedContextTimestamp = 0L; @@ -545,9 +551,7 @@ public static void CaptureLegacyContext(IScopeContextAsyncState contextState, ou if (nestedContextTimestamp == 0L) nestedContextTimestamp = ScopeContext.GetNestedContextTimestampNow(); - nestedContext = nestedStates as object[]; - if (nestedContext == null) - nestedContext = System.Linq.Enumerable.ToArray(nestedStates); + nestedContext = nestedStates as object[] ?? System.Linq.Enumerable.ToArray(nestedStates); } else { @@ -555,7 +559,7 @@ public static void CaptureLegacyContext(IScopeContextAsyncState contextState, ou } } - object IScopeContextAsyncState.NestedState => NestedContext?.Length > 0 ? NestedContext[0] : null; + object? IScopeContextAsyncState.NestedState => NestedContext?.Length > 0 ? NestedContext[0] : null; IList IScopeContextAsyncState.CaptureNestedContext(ref ScopeContextNestedStateCollector contextCollector) { @@ -581,7 +585,7 @@ IList IScopeContextAsyncState.CaptureNestedContext(ref ScopeContextNeste } } - IReadOnlyCollection> IScopeContextAsyncState.CaptureContextProperties(ref ScopeContextPropertyCollector contextCollector) + IReadOnlyCollection>? IScopeContextAsyncState.CaptureContextProperties(ref ScopeContextPropertyCollector contextCollector) { if (contextCollector.IsCollectorEmpty) { diff --git a/src/NLog/Internal/ScopeContextPropertyEnumerator.cs b/src/NLog/Internal/ScopeContextPropertyEnumerator.cs index bb45a27aed..47cd1cd15c 100644 --- a/src/NLog/Internal/ScopeContextPropertyEnumerator.cs +++ b/src/NLog/Internal/ScopeContextPropertyEnumerator.cs @@ -31,28 +31,28 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System.Collections.Generic; - namespace NLog.Internal { - internal struct ScopeContextPropertyEnumerator : IEnumerator> + using System.Collections.Generic; + + internal struct ScopeContextPropertyEnumerator : IEnumerator> { - readonly IEnumerator> _scopeEnumerator; + readonly IEnumerator>? _scopeEnumerator; #if !NET35 && !NET40 - readonly IReadOnlyList> _scopeList; + readonly IReadOnlyList>? _scopeList; int _scopeIndex; #endif - Dictionary.Enumerator _dictionaryEnumerator; + Dictionary.Enumerator _dictionaryEnumerator; public ScopeContextPropertyEnumerator(IEnumerable> scopeProperties) { #if !NET35 && !NET40 - if (scopeProperties is IReadOnlyList> scopeList) + if (scopeProperties is IReadOnlyList> scopeList) { _scopeEnumerator = null; _scopeList = scopeList; _scopeIndex = -1; - _dictionaryEnumerator = default(Dictionary.Enumerator); + _dictionaryEnumerator = default(Dictionary.Enumerator); return; } else @@ -62,27 +62,27 @@ public ScopeContextPropertyEnumerator(IEnumerable> } #endif - if (scopeProperties is Dictionary scopeDictionary) + if (scopeProperties is Dictionary scopeDictionary) { _scopeEnumerator = null; _dictionaryEnumerator = scopeDictionary.GetEnumerator(); } - else if (scopeProperties is IEnumerable> scopeEnumerator) + else if (scopeProperties is IEnumerable> scopeEnumerator) { _scopeEnumerator = scopeEnumerator.GetEnumerator(); - _dictionaryEnumerator = default(Dictionary.Enumerator); + _dictionaryEnumerator = default(Dictionary.Enumerator); } else { _scopeEnumerator = CreateScopeEnumerable(scopeProperties).GetEnumerator(); - _dictionaryEnumerator = default(Dictionary.Enumerator); + _dictionaryEnumerator = default(Dictionary.Enumerator); } } #if !NET35 && !NET40 - public static void CopyScopePropertiesToDictionary(IReadOnlyCollection> parentContext, Dictionary scopeDictionary) + public static void CopyScopePropertiesToDictionary(IReadOnlyCollection> parentContext, Dictionary scopeDictionary) { - using (var propertyEnumerator = new ScopeContextPropertyEnumerator(parentContext)) + using (var propertyEnumerator = new ScopeContextPropertyEnumerator(parentContext)) { while (propertyEnumerator.MoveNext()) { @@ -136,13 +136,13 @@ public static bool HasUniqueCollectionKeys(IEnumerable> CreateScopeEnumerable(IEnumerable> scopeProperties) + private static IEnumerable> CreateScopeEnumerable(IEnumerable> scopeProperties) { foreach (var property in scopeProperties) - yield return new KeyValuePair(property.Key, property.Value); + yield return new KeyValuePair(property.Key, property.Value); } - public KeyValuePair Current + public KeyValuePair Current { get { @@ -216,7 +216,7 @@ public void Reset() } else { - _dictionaryEnumerator = default(Dictionary.Enumerator); + _dictionaryEnumerator = default(Dictionary.Enumerator); } } } diff --git a/src/NLog/Internal/SetupConfigurationLoggingRuleBuilder.cs b/src/NLog/Internal/SetupConfigurationLoggingRuleBuilder.cs index 2cbf21c198..f62ba7c108 100644 --- a/src/NLog/Internal/SetupConfigurationLoggingRuleBuilder.cs +++ b/src/NLog/Internal/SetupConfigurationLoggingRuleBuilder.cs @@ -40,7 +40,7 @@ namespace NLog.Internal internal sealed class SetupConfigurationLoggingRuleBuilder : ISetupConfigurationLoggingRuleBuilder, IList { - public SetupConfigurationLoggingRuleBuilder(LogFactory logFactory, LoggingConfiguration configuration, string loggerNamePattern = null, string ruleName = null) + public SetupConfigurationLoggingRuleBuilder(LogFactory logFactory, LoggingConfiguration configuration, string? loggerNamePattern = null, string? ruleName = null) { LoggingRule = new LoggingRule(ruleName) { LoggerNamePattern = loggerNamePattern ?? "*" }; Configuration = configuration; diff --git a/src/NLog/Internal/SetupConfigurationTargetBuilder.cs b/src/NLog/Internal/SetupConfigurationTargetBuilder.cs index d548042ff3..a7a8a25889 100644 --- a/src/NLog/Internal/SetupConfigurationTargetBuilder.cs +++ b/src/NLog/Internal/SetupConfigurationTargetBuilder.cs @@ -42,9 +42,9 @@ namespace NLog.Internal internal sealed class SetupConfigurationTargetBuilder : ISetupConfigurationTargetBuilder, IList { private readonly IList _targets = new List(); - private string _targetName; + private string? _targetName; - public SetupConfigurationTargetBuilder(LogFactory logFactory, LoggingConfiguration configuration, string targetName = null) + public SetupConfigurationTargetBuilder(LogFactory logFactory, LoggingConfiguration configuration, string? targetName = null) { Configuration = configuration; LogFactory = logFactory; @@ -59,7 +59,7 @@ public SetupConfigurationTargetBuilder(LogFactory logFactory, LoggingConfigurati private void UpdateTargetName(Target item) { - if (!string.IsNullOrEmpty(_targetName)) + if (_targetName != null && !string.IsNullOrEmpty(_targetName)) { item.Name = _targetName; _targetName = string.Empty; // Mark that target-name has been used diff --git a/src/NLog/Internal/SetupLoadConfigurationBuilder.cs b/src/NLog/Internal/SetupLoadConfigurationBuilder.cs index ba4ff4f1f5..fcbcfc2b46 100644 --- a/src/NLog/Internal/SetupLoadConfigurationBuilder.cs +++ b/src/NLog/Internal/SetupLoadConfigurationBuilder.cs @@ -37,10 +37,10 @@ namespace NLog.Internal internal sealed class SetupLoadConfigurationBuilder : ISetupLoadConfigurationBuilder { - internal SetupLoadConfigurationBuilder(LogFactory logFactory, LoggingConfiguration configuration) + internal SetupLoadConfigurationBuilder(LogFactory logFactory, LoggingConfiguration? configuration) { LogFactory = logFactory; - Configuration = configuration; + _configuration = configuration; } public LogFactory LogFactory { get; } @@ -51,6 +51,6 @@ public LoggingConfiguration Configuration set => _configuration = value; } - internal LoggingConfiguration _configuration; + internal LoggingConfiguration? _configuration; } } diff --git a/src/NLog/Internal/SimpleStringReader.cs b/src/NLog/Internal/SimpleStringReader.cs index c6f523cd52..a30522619f 100644 --- a/src/NLog/Internal/SimpleStringReader.cs +++ b/src/NLog/Internal/SimpleStringReader.cs @@ -79,7 +79,7 @@ internal string CurrentState return BuildCurrentState(done: "INVALID_CURRENT_STATE", current: char.MaxValue, todo: "INVALID_CURRENT_STATE"); } var done = Substring(0, Position); - var todo = ((Position < _text.Length - 1) ? Text.Substring(Position + 1) : ""); + var todo = ((Position < _text.Length - 1) ? Text.Substring(Position + 1) : string.Empty); return BuildCurrentState(done: done, current: current, todo: todo); } } diff --git a/src/NLog/Internal/SingleCallContinuation.cs b/src/NLog/Internal/SingleCallContinuation.cs index 10002dc4b6..6dcd3948b1 100644 --- a/src/NLog/Internal/SingleCallContinuation.cs +++ b/src/NLog/Internal/SingleCallContinuation.cs @@ -44,13 +44,13 @@ internal sealed class SingleCallContinuation { internal static readonly AsyncContinuation Completed = new SingleCallContinuation(null).CompletedFunction; - private AsyncContinuation _asyncContinuation; + private AsyncContinuation? _asyncContinuation; /// /// Initializes a new instance of the class. /// /// The asynchronous continuation. - public SingleCallContinuation(AsyncContinuation asyncContinuation) + public SingleCallContinuation(AsyncContinuation? asyncContinuation) { _asyncContinuation = asyncContinuation; } @@ -59,7 +59,7 @@ public SingleCallContinuation(AsyncContinuation asyncContinuation) /// Continuation function which implements the single-call guard. /// /// The exception. - public void Function(Exception exception) + public void Function(Exception? exception) { try { @@ -77,7 +77,7 @@ public void Function(Exception exception) } } - private void CompletedFunction(Exception exception) + private void CompletedFunction(Exception? exception) { // Completed, nothing to do } diff --git a/src/NLog/Internal/SingleItemOptimizedHashSet.cs b/src/NLog/Internal/SingleItemOptimizedHashSet.cs index 6f620ff7ca..ded0e5fe44 100644 --- a/src/NLog/Internal/SingleItemOptimizedHashSet.cs +++ b/src/NLog/Internal/SingleItemOptimizedHashSet.cs @@ -43,16 +43,16 @@ namespace NLog.Internal /// internal struct SingleItemOptimizedHashSet : ICollection { - private readonly T _singleItem; - private HashSet _hashset; - private readonly IEqualityComparer _comparer; + private readonly T? _singleItem; + private HashSet? _hashset; + private readonly IEqualityComparer? _comparer; private IEqualityComparer Comparer => _comparer ?? EqualityComparer.Default; public struct SingleItemScopedInsert : IDisposable { private readonly T _singleItem; - private readonly HashSet _hashset; + private readonly HashSet? _hashset; /// /// Insert single item on scope start, and remove on scope exit @@ -91,11 +91,11 @@ public void Dispose() } } - public int Count => _hashset?.Count ?? (EqualityComparer.Default.Equals(_singleItem, default(T)) ? 0 : 1); // Object Equals to default value + public int Count => _hashset?.Count ?? (EqualityComparer.Default.Equals(_singleItem, default(T)) ? 0 : 1); // Object Equals to default value public bool IsReadOnly => false; - public SingleItemOptimizedHashSet(T singleItem, SingleItemOptimizedHashSet existing, IEqualityComparer comparer = null) + public SingleItemOptimizedHashSet(T singleItem, SingleItemOptimizedHashSet existing, IEqualityComparer? comparer = null) { _comparer = existing._comparer ?? comparer ?? EqualityComparer.Default; if (existing._hashset != null) @@ -104,7 +104,7 @@ public SingleItemOptimizedHashSet(T singleItem, SingleItemOptimizedHashSet ex _hashset.Add(singleItem); _singleItem = default(T); } - else if (existing.Count == 1) + else if (existing.Count == 1 && existing._singleItem is not null) { _hashset = new HashSet(_comparer); _hashset.Add(existing._singleItem); @@ -131,7 +131,7 @@ public void Add(T item) else { var hashset = new HashSet(Comparer); - if (Count != 0) + if (Count == 1 && _singleItem is not null) { hashset.Add(_singleItem); } @@ -168,7 +168,7 @@ public bool Contains(T item) } else { - return Count == 1 && Comparer.Equals(_singleItem, item); + return Count == 1 && _singleItem is not null && Comparer.Equals(_singleItem, item); } } @@ -183,11 +183,12 @@ public bool Remove(T item) { return _hashset.Remove(item); } - else + else if (Count == 1 && _singleItem is not null && Comparer.Equals(_singleItem, item)) { _hashset = new HashSet(Comparer); - return Comparer.Equals(_singleItem, item); + return true; } + return false; } /// @@ -201,7 +202,7 @@ public void CopyTo(T[] array, int arrayIndex) { _hashset.CopyTo(array, arrayIndex); } - else if (Count == 1) + else if (Count == 1 && _singleItem is not null) { array[arrayIndex] = _singleItem; } @@ -225,7 +226,7 @@ public IEnumerator GetEnumerator() private IEnumerator SingleItemEnumerator() { - if (Count != 0) + if (Count == 1 && _singleItem is not null) yield return _singleItem; } diff --git a/src/NLog/Internal/SortHelpers.cs b/src/NLog/Internal/SortHelpers.cs index e75db7d41b..18c932a003 100644 --- a/src/NLog/Internal/SortHelpers.cs +++ b/src/NLog/Internal/SortHelpers.cs @@ -62,7 +62,7 @@ internal static class SortHelpers /// /// Dictionary where keys are unique input keys, and values are lists of . /// - public static Dictionary> BucketSort(this IEnumerable inputs, KeySelector keySelector) + public static Dictionary> BucketSort(this IEnumerable inputs, KeySelector keySelector) where TKey : notnull { var buckets = new Dictionary>(); @@ -91,7 +91,7 @@ public static Dictionary> BucketSort(this IEnum /// /// Dictionary where keys are unique input keys, and values are lists of . /// - public static ReadOnlySingleBucketDictionary> BucketSort(this IList inputs, KeySelector keySelector) + public static ReadOnlySingleBucketGroupBy> BucketSort(this IList inputs, KeySelector keySelector) where TKey : notnull { return BucketSort(inputs, keySelector, EqualityComparer.Default); } @@ -107,18 +107,17 @@ public static ReadOnlySingleBucketDictionary> BucketSort /// Dictionary where keys are unique input keys, and values are lists of . /// - public static ReadOnlySingleBucketDictionary> BucketSort(this IList inputs, KeySelector keySelector, IEqualityComparer keyComparer) + public static ReadOnlySingleBucketGroupBy> BucketSort(this IList inputs, KeySelector keySelector, IEqualityComparer keyComparer) where TKey : notnull { - Dictionary> buckets = null; - TKey singleBucketKey = default(TKey); - for (int i = 0; i < inputs.Count; i++) + if (inputs.Count == 0) + return new ReadOnlySingleBucketGroupBy>(singleBucket: null, keyComparer); + + Dictionary>? buckets = null; + TKey singleBucketKey = keySelector(inputs[0]); + for (int i = 1; i < inputs.Count; i++) { TKey keyValue = keySelector(inputs[i]); - if (i == 0) - { - singleBucketKey = keyValue; - } - else if (buckets is null) + if (buckets is null) { if (!keyComparer.Equals(singleBucketKey, keyValue)) { @@ -138,9 +137,9 @@ public static ReadOnlySingleBucketDictionary> BucketSort>(new KeyValuePair>(singleBucketKey, inputs), keyComparer); + return new ReadOnlySingleBucketGroupBy>(new KeyValuePair>(singleBucketKey, inputs), keyComparer); else - return new ReadOnlySingleBucketDictionary>(buckets, keyComparer); + return new ReadOnlySingleBucketGroupBy>(buckets, keyComparer); } private static Dictionary> CreateBucketDictionaryWithValue(IList inputs, IEqualityComparer keyComparer, int currentIndex, TKey firstBucketKey, TKey nextBucketKey) @@ -165,31 +164,39 @@ private static Dictionary> CreateBucketDictionaryWithValue /// The type of the key. /// The type of the value. - public struct ReadOnlySingleBucketDictionary : IDictionary + public +#if !NETFRAMEWORK + readonly +#endif + struct ReadOnlySingleBucketGroupBy : IEnumerable> where TKey : notnull { - KeyValuePair? _singleBucket; // Not readonly to avoid struct-copy, and to avoid VerificationException when medium-trust AppDomain - readonly Dictionary _multiBucket; - readonly IEqualityComparer _comparer; + private +#if !NETFRAMEWORK + readonly +#endif + KeyValuePair? _singleBucket; // Not readonly to avoid struct-copy, and to avoid VerificationException when medium-trust AppDomain + private readonly Dictionary? _multiBucket; + private readonly IEqualityComparer _comparer; public IEqualityComparer Comparer => _comparer; - public ReadOnlySingleBucketDictionary(KeyValuePair singleBucket) + public ReadOnlySingleBucketGroupBy(KeyValuePair singleBucket) : this(singleBucket, EqualityComparer.Default) { } - public ReadOnlySingleBucketDictionary(Dictionary multiBucket) + public ReadOnlySingleBucketGroupBy(Dictionary multiBucket) : this(multiBucket, EqualityComparer.Default) { } - public ReadOnlySingleBucketDictionary(KeyValuePair singleBucket, IEqualityComparer comparer) + public ReadOnlySingleBucketGroupBy(KeyValuePair? singleBucket, IEqualityComparer comparer) { _comparer = comparer; _multiBucket = null; _singleBucket = singleBucket; } - public ReadOnlySingleBucketDictionary(Dictionary multiBucket, IEqualityComparer comparer) + public ReadOnlySingleBucketGroupBy(Dictionary multiBucket, IEqualityComparer comparer) { _comparer = comparer; _multiBucket = multiBucket; @@ -197,58 +204,7 @@ public ReadOnlySingleBucketDictionary(Dictionary multiBucket, IEqu } /// - public int Count { get { if (_multiBucket != null) return _multiBucket.Count; else if (_singleBucket.HasValue) return 1; else return 0; } } - - /// - public ICollection Keys - { - get - { - if (_multiBucket != null) - return _multiBucket.Keys; - else if (_singleBucket.HasValue) - return new[] { _singleBucket.Value.Key }; - else - return ArrayHelper.Empty(); - } - } - - /// - public ICollection Values - { - get - { - if (_multiBucket != null) - return _multiBucket.Values; - else if (_singleBucket.HasValue) - return new TValue[] { _singleBucket.Value.Value }; - else - return ArrayHelper.Empty(); - } - } - - /// - public bool IsReadOnly => true; - - /// - /// Allows direct lookup of existing keys. If trying to access non-existing key exception is thrown. - /// Consider to use instead for better safety. - /// - /// Key value for lookup - /// Mapped value found - public TValue this[TKey key] - { - get - { - if (_multiBucket != null) - return _multiBucket[key]; - else if (_singleBucket.HasValue && _comparer.Equals(_singleBucket.Value.Key, key)) - return _singleBucket.Value.Value; - else - throw new KeyNotFoundException(); - } - set => throw new NotSupportedException("Readonly"); - } + public int Count => _multiBucket?.Count ?? (_singleBucket.HasValue ? 1 : 0); /// /// Non-Allocating struct-enumerator @@ -257,7 +213,7 @@ public struct Enumerator : IEnumerator> { bool _singleBucketFirstRead; KeyValuePair _singleBucket; // Not readonly to avoid struct-copy, and to avoid VerificationException when medium-trust AppDomain - readonly IEnumerator> _multiBuckets; + readonly IEnumerator>? _multiBuckets; internal Enumerator(Dictionary multiBucket) { @@ -278,7 +234,7 @@ public KeyValuePair Current get { if (_multiBuckets != null) - return new KeyValuePair(_multiBuckets.Current.Key, _multiBuckets.Current.Value); + return _multiBuckets.Current; else return new KeyValuePair(_singleBucket.Key, _singleBucket.Value); } @@ -300,7 +256,6 @@ public bool MoveNext() return false; else return _singleBucketFirstRead = true; - } public void Reset() @@ -333,86 +288,6 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } - - /// - public bool ContainsKey(TKey key) - { - if (_multiBucket != null) - return _multiBucket.ContainsKey(key); - else if (_singleBucket.HasValue) - return _comparer.Equals(_singleBucket.Value.Key, key); - else - return false; - } - - /// Will always throw, as dictionary is readonly - public void Add(TKey key, TValue value) - { - throw new NotSupportedException(); // Readonly - } - - /// Will always throw, as dictionary is readonly - public bool Remove(TKey key) - { - throw new NotSupportedException(); // Readonly - } - - /// - public bool TryGetValue(TKey key, out TValue value) - { - if (_multiBucket != null) - { - return _multiBucket.TryGetValue(key, out value); - } - else if (_singleBucket.HasValue && _comparer.Equals(_singleBucket.Value.Key, key)) - { - value = _singleBucket.Value.Value; - return true; - } - else - { - value = default(TValue); - return false; - } - } - - /// Will always throw, as dictionary is readonly - public void Add(KeyValuePair item) - { - throw new NotSupportedException(); // Readonly - } - - /// Will always throw, as dictionary is readonly - public void Clear() - { - throw new NotSupportedException(); // Readonly - } - - /// - public bool Contains(KeyValuePair item) - { - if (_multiBucket != null) - return ((IDictionary)_multiBucket).Contains(item); - else if (_singleBucket.HasValue) - return _comparer.Equals(_singleBucket.Value.Key, item.Key) && EqualityComparer.Default.Equals(_singleBucket.Value.Value, item.Value); - else - return false; - } - - /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (_multiBucket != null) - ((IDictionary)_multiBucket).CopyTo(array, arrayIndex); - else if (_singleBucket.HasValue) - array[arrayIndex] = _singleBucket.Value; - } - - /// Will always throw, as dictionary is readonly - public bool Remove(KeyValuePair item) - { - throw new NotSupportedException(); // Readonly - } } } } diff --git a/src/NLog/Internal/StackTraceUsageUtils.cs b/src/NLog/Internal/StackTraceUsageUtils.cs index bbcfededf4..dfa44911fa 100644 --- a/src/NLog/Internal/StackTraceUsageUtils.cs +++ b/src/NLog/Internal/StackTraceUsageUtils.cs @@ -36,7 +36,6 @@ namespace NLog.Internal using System; using System.Diagnostics; using System.Reflection; - using System.Runtime.CompilerServices; using NLog.Config; /// @@ -44,9 +43,9 @@ namespace NLog.Internal /// internal static class StackTraceUsageUtils { - private static readonly Assembly nlogAssembly = typeof(StackTraceUsageUtils).GetAssembly(); - private static readonly Assembly mscorlibAssembly = typeof(string).GetAssembly(); - private static readonly Assembly systemAssembly = typeof(Debug).GetAssembly(); + private static readonly Assembly nlogAssembly = typeof(StackTraceUsageUtils).Assembly; + private static readonly Assembly mscorlibAssembly = typeof(string).Assembly; + private static readonly Assembly systemAssembly = typeof(Debug).Assembly; public static StackTraceUsage GetStackTraceUsage(bool includeFileName, int skipFrames, bool captureStackTrace) { @@ -70,17 +69,13 @@ public static StackTraceUsage GetStackTraceUsage(bool includeFileName, int skipF public static int GetFrameCount(this StackTrace strackTrace) { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 return strackTrace.FrameCount; -#else - return strackTrace.GetFrames().Length; -#endif } public static string GetStackFrameMethodName(MethodBase method, bool includeMethodInfo, bool cleanAsyncMoveNext, bool cleanAnonymousDelegates) { if (method is null) - return null; + return string.Empty; string methodName = method.Name; @@ -118,7 +113,7 @@ public static string GetStackFrameMethodName(MethodBase method, bool includeMeth public static string GetStackFrameMethodClassName(MethodBase method, bool includeNameSpace, bool cleanAsyncMoveNext, bool cleanAnonymousDelegates) { if (method is null) - return null; + return string.Empty; var callerClassType = method.DeclaringType; if (cleanAsyncMoveNext @@ -131,7 +126,10 @@ public static string GetStackFrameMethodClassName(MethodBase method, bool includ callerClassType = callerClassType.DeclaringType; } - string className = includeNameSpace ? callerClassType?.FullName : callerClassType?.Name; + if (callerClassType is null) + return string.Empty; + + var className = includeNameSpace ? callerClassType.FullName : callerClassType.Name; if (cleanAnonymousDelegates && className?.IndexOf("<>", StringComparison.Ordinal) >= 0) { if (!includeNameSpace && callerClassType.DeclaringType != null && callerClassType.IsNested) @@ -155,12 +153,12 @@ public static string GetStackFrameMethodClassName(MethodBase method, bool includ className = string.IsNullOrEmpty(typeNamespace) ? className : string.Concat(typeNamespace, ".", className); } - return className; + return className ?? string.Empty; } - private static string GetNamespaceFromTypeAssembly(Type callerClassType) + private static string GetNamespaceFromTypeAssembly(Type? callerClassType) { - var classAssembly = callerClassType.GetAssembly(); + var classAssembly = callerClassType?.Assembly; if (classAssembly != null && classAssembly != mscorlibAssembly && classAssembly != systemAssembly) { var assemblyFullName = classAssembly.FullName; @@ -170,63 +168,22 @@ private static string GetNamespaceFromTypeAssembly(Type callerClassType) } } - return null; + return string.Empty; } [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow callsite logic", "IL2026")] - public static MethodBase GetStackMethod(StackFrame stackFrame) + public static MethodBase? GetStackMethod(StackFrame? stackFrame) { return stackFrame?.GetMethod(); } - /// - /// Gets the fully qualified name of the class invoking the calling method, including the - /// namespace but not the assembly. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - public static string GetClassFullName() - { - int framesToSkip = 2; - - string className = string.Empty; -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - var stackFrame = new StackFrame(framesToSkip, false); - className = GetClassFullName(stackFrame); -#else - var stackTrace = Environment.StackTrace; - var stackTraceLines = stackTrace.Replace("\r", "").SplitAndTrimTokens('\n'); - for (int i = 0; i < stackTraceLines.Length; ++i) - { - var callingClassAndMethod = stackTraceLines[i].Split(new[] { " ", "<>", "(", ")" }, StringSplitOptions.RemoveEmptyEntries)[1]; - int methodStartIndex = callingClassAndMethod.LastIndexOf(".", StringComparison.Ordinal); - if (methodStartIndex > 0) - { - // Trim method name. - var callingClass = callingClassAndMethod.Substring(0, methodStartIndex); - // Needed because of extra dot, for example if method was .ctor() - className = callingClass.TrimEnd('.'); - if (!className.StartsWith("System.Environment", StringComparison.Ordinal) && framesToSkip != 0) - { - i += framesToSkip - 1; - framesToSkip = 0; - continue; - } - if (!className.StartsWith("System.", StringComparison.Ordinal)) - break; - } - } -#endif - return className; - } - -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 /// /// Gets the fully qualified name of the class invoking the calling method, including the /// namespace but not the assembly. /// /// StackFrame from the calling method /// Fully qualified class name - public static string GetClassFullName(StackFrame stackFrame) + public static string GetClassFullName(StackFrame? stackFrame) { string className = LookupClassNameFromStackFrame(stackFrame); if (string.IsNullOrEmpty(className)) @@ -241,7 +198,6 @@ public static string GetClassFullName(StackFrame stackFrame) } return className; } -#endif private static string GetClassFullName(StackTrace stackTrace) { @@ -260,9 +216,9 @@ private static string GetClassFullName(StackTrace stackTrace) /// Returns the assembly from the provided StackFrame (If not internal assembly) /// /// Valid assembly, or null if assembly was internal - public static Assembly LookupAssemblyFromMethod(MethodBase method) + public static Assembly? LookupAssemblyFromMethod(MethodBase? method) { - var assembly = method?.DeclaringType?.GetAssembly() ?? method?.Module?.Assembly; + var assembly = method?.DeclaringType?.Assembly ?? method?.Module?.Assembly; // skip stack frame if the method declaring type assembly is from hidden assemblies list if (assembly == nlogAssembly) @@ -288,7 +244,7 @@ public static Assembly LookupAssemblyFromMethod(MethodBase method) /// /// /// Valid class name, or empty string if assembly was internal - public static string LookupClassNameFromStackFrame(StackFrame stackFrame) + public static string LookupClassNameFromStackFrame(StackFrame? stackFrame) { var method = GetStackMethod(stackFrame); if (method != null && LookupAssemblyFromMethod(method) != null) diff --git a/src/NLog/Internal/StringBuilderExt.cs b/src/NLog/Internal/StringBuilderExt.cs index bb203fd200..ac1d180970 100644 --- a/src/NLog/Internal/StringBuilderExt.cs +++ b/src/NLog/Internal/StringBuilderExt.cs @@ -31,14 +31,15 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Globalization; -using System.IO; -using System.Text; -using NLog.MessageTemplates; - namespace NLog.Internal { + using System; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using NLog.MessageTemplates; + /// /// Helpers for , which is used in e.g. layout renderers. /// @@ -52,14 +53,13 @@ internal static class StringBuilderExt /// format string. If @, then serialize the value with the Default JsonConverter. /// provider, for example culture /// NLog string.Format interface - public static void AppendFormattedValue(this StringBuilder builder, object value, string format, IFormatProvider formatProvider, IValueFormatter valueFormatter) + public static void AppendFormattedValue(this StringBuilder builder, object? value, string? format, IFormatProvider? formatProvider, IValueFormatter valueFormatter) { - string stringValue = value as string; - if (stringValue != null && string.IsNullOrEmpty(format)) + if (value is string stringValue && string.IsNullOrEmpty(format)) { - builder.Append(value); // Avoid automatic quotes + builder.Append(stringValue); // Avoid automatic quotes } - else if (format == MessageTemplates.ValueFormatter.FormatAsJson) + else if (ValueFormatter.FormatAsJson.Equals(format)) { valueFormatter.FormatValue(value, null, CaptureType.Serialize, formatProvider, builder); } @@ -76,6 +76,7 @@ public static void AppendFormattedValue(this StringBuilder builder, object value /// value to append public static void AppendInvariant(this StringBuilder builder, int value) { +#if NETFRAMEWORK // Deal with negative numbers if (value < 0) { @@ -87,6 +88,9 @@ public static void AppendInvariant(this StringBuilder builder, int value) { AppendInvariant(builder, (uint)value); } +#else + builder.Append(value); +#endif } /// @@ -98,6 +102,7 @@ public static void AppendInvariant(this StringBuilder builder, int value) /// value to append public static void AppendInvariant(this StringBuilder builder, uint value) { +#if NETFRAMEWORK if (value == 0) { builder.Append('0'); @@ -106,6 +111,9 @@ public static void AppendInvariant(this StringBuilder builder, uint value) int digitCount = CalculateDigitCount(value); ApppendValueWithDigitCount(builder, value, digitCount); +#else + builder.Append(value); +#endif } private static int CalculateDigitCount(uint value) @@ -343,7 +351,7 @@ private static bool CharArrayContains(char searchChar, char[] needles) /// /// Correct implementation of that also works when is not the same /// - /// True when content is the same + /// when content is the same public static bool EqualTo(this StringBuilder builder, StringBuilder other) { var builderLength = builder.Length; @@ -364,15 +372,16 @@ public static bool EqualTo(this StringBuilder builder, StringBuilder other) /// /// Compares the contents of a StringBuilder and a String /// - /// True when content is the same + /// when content is the same public static bool EqualTo(this StringBuilder builder, string other) { if (builder.Length != other.Length) return false; - for (int i = 0; i < other.Length; ++i) + int i = 0; + foreach (var chr in other) { - if (builder[i] != other[i]) + if (builder[i++] != chr) return false; } @@ -386,9 +395,17 @@ public static bool EqualTo(this StringBuilder builder, string other) /// the number internal static void Append2DigitsZeroPadded(this StringBuilder builder, int number) { - builder.Append((char)((number / 10) + '0')); - builder.Append((char)((number % 10) + '0')); + if (number < 0 || number >= _zeroPaddedDigits.Length) + { + builder.Append((char)((number / 10) + '0')); + builder.Append((char)((number % 10) + '0')); + } + else + { + builder.Append(_zeroPaddedDigits[number]); + } } + private static readonly string[] _zeroPaddedDigits = Enumerable.Range(0, 60).Select(i => i.ToString("D2")).ToArray(); /// /// Append a number and pad with 0 to 4 digits @@ -397,10 +414,19 @@ internal static void Append2DigitsZeroPadded(this StringBuilder builder, int num /// the number internal static void Append4DigitsZeroPadded(this StringBuilder builder, int number) { - builder.Append((char)(((number / 1000) % 10) + '0')); - builder.Append((char)(((number / 100) % 10) + '0')); - builder.Append((char)(((number / 10) % 10) + '0')); - builder.Append((char)(((number / 1) % 10) + '0')); +#if !NETFRAMEWORK + if (number > 999 && number < 10000) + { + builder.Append(number); + } + else +#endif + { + builder.Append((char)(((number / 1000) % 10) + '0')); + builder.Append((char)(((number / 100) % 10) + '0')); + builder.Append((char)(((number / 10) % 10) + '0')); + builder.Append((char)((number % 10) + '0')); + } } /// @@ -417,9 +443,11 @@ internal static void AppendNumericInvariant(this StringBuilder sb, IConvertible case TypeCode.Int64: { long int64 = value.ToInt64(CultureInfo.InvariantCulture); +#if NETFRAMEWORK if (int64 < int.MaxValue && int64 > int.MinValue) sb.AppendInvariant((int)int64); else +#endif sb.Append(int64); } break; @@ -428,16 +456,18 @@ internal static void AppendNumericInvariant(this StringBuilder sb, IConvertible case TypeCode.UInt64: { ulong uint64 = value.ToUInt64(CultureInfo.InvariantCulture); +#if NETFRAMEWORK if (uint64 < uint.MaxValue) sb.AppendInvariant((uint)uint64); else +#endif sb.Append(uint64); } break; case TypeCode.Single: { float floatValue = value.ToSingle(CultureInfo.InvariantCulture); -#if NETSTANDARD +#if !NETFRAMEWORK if (!float.IsNaN(floatValue) && !float.IsInfinity(floatValue) && value is IFormattable formattable) { AppendDecimalInvariant(sb, formattable, "{0:R}"); @@ -452,7 +482,7 @@ internal static void AppendNumericInvariant(this StringBuilder sb, IConvertible case TypeCode.Double: { double doubleValue = value.ToDouble(CultureInfo.InvariantCulture); -#if NETSTANDARD +#if !NETFRAMEWORK if (!double.IsNaN(doubleValue) && !double.IsInfinity(doubleValue) && value is IFormattable formattable) { AppendDecimalInvariant(sb, formattable, "{0:R}"); @@ -466,7 +496,7 @@ internal static void AppendNumericInvariant(this StringBuilder sb, IConvertible break; case TypeCode.Decimal: { -#if NETSTANDARD +#if !NETFRAMEWORK if (value is IFormattable formattable) { AppendDecimalInvariant(sb, formattable, "{0}"); @@ -484,7 +514,7 @@ internal static void AppendNumericInvariant(this StringBuilder sb, IConvertible } } -#if NETSTANDARD +#if !NETFRAMEWORK private static void AppendDecimalInvariant(StringBuilder sb, IFormattable formattable, string format) { int orgLength = sb.Length; diff --git a/src/NLog/Internal/StringBuilderPool.cs b/src/NLog/Internal/StringBuilderPool.cs index d7e7ef6c16..aeae59d677 100644 --- a/src/NLog/Internal/StringBuilderPool.cs +++ b/src/NLog/Internal/StringBuilderPool.cs @@ -39,8 +39,8 @@ namespace NLog.Internal internal sealed class StringBuilderPool { - private StringBuilder _fastPool; - private readonly StringBuilder[] _slowPool; + private StringBuilder? _fastPool; + private readonly StringBuilder?[] _slowPool; private readonly int _maxBuilderCapacity; /// @@ -66,7 +66,7 @@ public StringBuilderPool(int poolCapacity, int initialBuilderCapacity = 1024, in /// Allow return to pool public ItemHolder Acquire() { - StringBuilder item = _fastPool; + StringBuilder? item = _fastPool; if (item is null || item != Interlocked.CompareExchange(ref _fastPool, null, item)) { for (int i = 0; i < _slowPool.Length; i++) @@ -120,13 +120,17 @@ private void Release(StringBuilder stringBuilder, int poolIndex) /// /// Keeps track of acquired pool item /// - public struct ItemHolder : IDisposable + public +#if !NETFRAMEWORK + readonly +#endif + struct ItemHolder : IDisposable { public readonly StringBuilder Item; - readonly StringBuilderPool _owner; + readonly StringBuilderPool? _owner; readonly int _poolIndex; - public ItemHolder(StringBuilder stringBuilder, StringBuilderPool owner, int poolIndex) + public ItemHolder(StringBuilder stringBuilder, StringBuilderPool? owner, int poolIndex) { Item = stringBuilder; _owner = owner; diff --git a/src/NLog/Internal/StringHelpers.cs b/src/NLog/Internal/StringHelpers.cs index 7d2bcca24d..73ca308b62 100644 --- a/src/NLog/Internal/StringHelpers.cs +++ b/src/NLog/Internal/StringHelpers.cs @@ -31,14 +31,14 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using JetBrains.Annotations; - namespace NLog.Internal { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using JetBrains.Annotations; + /// /// Helpers for . /// @@ -50,14 +50,14 @@ internal static class StringHelpers /// /// [ContractAnnotation("value:null => true")] - internal static bool IsNullOrWhiteSpace(string value) + internal static bool IsNullOrWhiteSpace(string? value) { #if !NET35 return string.IsNullOrWhiteSpace(value); #else if (value is null) return true; if (value.Length == 0) return true; - return String.IsNullOrEmpty(value.Trim()); + return string.IsNullOrEmpty(value.Trim()); #endif } @@ -84,12 +84,8 @@ internal static string[] SplitAndTrimTokens(this string value, char delimiter) /// /// Replace string with /// - /// - /// - /// - /// /// The same reference of nothing has been replaced. - public static string Replace([NotNull] string str, [NotNull] string oldValue, string newValue, StringComparison comparison) + public static string Replace(string str, string oldValue, string newValue, StringComparison comparison, bool wholeWords = false) { Guard.ThrowIfNull(str); @@ -101,28 +97,33 @@ public static string Replace([NotNull] string str, [NotNull] string oldValue, st Guard.ThrowIfNullOrEmpty(oldValue); - StringBuilder sb = null; + StringBuilder? sb = null; int previousIndex = 0; int index = str.IndexOf(oldValue, comparison); while (index != -1) { - sb = sb ?? new StringBuilder(str.Length); - - if (previousIndex >= str.Length) + if (!wholeWords ||IsWholeWord(str, oldValue, index)) { - // for cases that 2 chars is one symbol - break; + sb = sb ?? new StringBuilder(str.Length); + if (previousIndex >= str.Length) + { + // for cases that 2 chars is one symbol + break; + } + sb.Append(str, previousIndex, index - previousIndex); + sb.Append(newValue); + index += oldValue.Length; + previousIndex = index; + if (index >= str.Length) + { + // for cases that 2 chars is one symbol + break; + } } - sb.Append(str, previousIndex, index - previousIndex); - sb.Append(newValue); - index += oldValue.Length; - - previousIndex = index; - if (index >= str.Length) + else { - // for cases that 2 chars is one symbol - break; + index += oldValue.Length; } index = str.IndexOf(oldValue, index, comparison); @@ -142,6 +143,11 @@ public static string Replace([NotNull] string str, [NotNull] string oldValue, st return sb.ToString(); } + public static bool IsWholeWord(string str, string token, int index) + { + return (index + token.Length == str.Length || !char.IsLetterOrDigit(str[index + token.Length]) && (index == 0 || !char.IsLetterOrDigit(str[index - 1]))); + } + /// Concatenates all the elements of a string array, using the specified separator between each element. /// The string to use as a separator. is included in the returned string only if has more than one element. /// An collection that contains the elements to concatenate. @@ -157,7 +163,7 @@ internal static string Join(string separator, IEnumerable values) #endif } - internal static bool IsNullOrEmptyString(object objectValue) + internal static bool IsNullOrEmptyString(object? objectValue) { return objectValue is null || ReferenceEquals(string.Empty, objectValue) || (objectValue is string stringValue && stringValue.Length == 0); } diff --git a/src/NLog/Internal/StringSplitter.cs b/src/NLog/Internal/StringSplitter.cs index 4989708295..70e56348d9 100644 --- a/src/NLog/Internal/StringSplitter.cs +++ b/src/NLog/Internal/StringSplitter.cs @@ -31,12 +31,12 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Collections.Generic; -using System.Text; - namespace NLog.Internal { + using System; + using System.Collections.Generic; + using System.Text; + /// /// Split a string /// diff --git a/src/NLog/Internal/TargetWithFilterChain.cs b/src/NLog/Internal/TargetWithFilterChain.cs index 21b5e54440..80c42d5050 100644 --- a/src/NLog/Internal/TargetWithFilterChain.cs +++ b/src/NLog/Internal/TargetWithFilterChain.cs @@ -50,7 +50,7 @@ internal sealed class TargetWithFilterChain : ITargetWithFilterChain private static TargetWithFilterChain[] CreateLoggerConfiguration() => new TargetWithFilterChain[LogLevel.MaxLevel.Ordinal + 2]; // +2 to include LogLevel.Off - private MruCache _callSiteClassNameCache; + private MruCache? _callSiteClassNameCache; /// /// Initializes a new instance of the class. @@ -82,7 +82,7 @@ public TargetWithFilterChain(Target target, IList filterChain, FilterRes /// /// The next item in the chain. /// This is for example the 'target2' logger in writeTo='target1,target2' - public TargetWithFilterChain NextInChain { get; set; } + public TargetWithFilterChain? NextInChain { get; set; } /// /// Gets the stack trace usage. @@ -117,11 +117,8 @@ internal StackTraceUsage PrecalculateStackTraceUsage() return stackTraceUsage; } - static internal TargetWithFilterChain[] BuildLoggerConfiguration(string loggerName, List loggingRules, LogLevel globalLogLevel) + static internal TargetWithFilterChain[] BuildLoggerConfiguration(string loggerName, LoggingRule[] loggingRules, LogLevel globalLogLevel) { - if (loggingRules is null || loggingRules.Count == 0 || LogLevel.Off.Equals(globalLogLevel)) - return TargetWithFilterChain.NoTargetsByLevel; - TargetWithFilterChain[] targetsByLevel = TargetWithFilterChain.CreateLoggerConfiguration(); TargetWithFilterChain[] lastTargetsByLevel = TargetWithFilterChain.CreateLoggerConfiguration(); bool[] suppressedLevels = new bool[LogLevel.MaxLevel.Ordinal + 1]; @@ -130,9 +127,9 @@ static internal TargetWithFilterChain[] BuildLoggerConfiguration(string loggerNa return targetsFound ? targetsByLevel : TargetWithFilterChain.NoTargetsByLevel; } - static private bool GetTargetsByLevelForLogger(string name, List loggingRules, LogLevel globalLogLevel, TargetWithFilterChain[] targetsByLevel, TargetWithFilterChain[] lastTargetsByLevel, bool[] suppressedLevels) + static private bool GetTargetsByLevelForLogger(string name, LoggingRule[] loggingRules, LogLevel globalLogLevel, TargetWithFilterChain[] targetsByLevel, TargetWithFilterChain[] lastTargetsByLevel, bool[] suppressedLevels) { - IList>> finalMinLevelWithFilters = null; + IList>>? finalMinLevelWithFilters = null; bool targetsFound = false; foreach (LoggingRule rule in loggingRules) { @@ -166,7 +163,7 @@ static private bool GetTargetsByLevelForLogger(string name, List lo if (finalMinLevelWithFilters?.Count > 0) { var finalMinLevelFilters = finalMinLevelWithFilters[i]; - if (finalMinLevelFilters.Value?.Count > 0) + if (finalMinLevelFilters.Value?.Count > 0 && finalMinLevelFilters.Key.HasValue) { targetsByLevel[i] = tfc = AppendFinalMinLevelFilters(tfc, finalMinLevelFilters.Value, finalMinLevelFilters.Key.Value); } @@ -183,7 +180,7 @@ private static bool LoggingRuleHasFinalMinLevelFilters(LoggingRule rule) return rule.FinalMinLevel != LogLevel.Off && rule.Filters.Count != 0 && rule.Targets.Count == 0; } - private static void CollectFinalMinLevelFiltersFromRule(LoggingRule rule, ref IList>> finalMinLevelWithFilters) + private static void CollectFinalMinLevelFiltersFromRule(LoggingRule rule, ref IList>>? finalMinLevelWithFilters) { var finalMinLevel = rule.FinalMinLevel; if (finalMinLevel is null) @@ -195,10 +192,10 @@ private static void CollectFinalMinLevelFiltersFromRule(LoggingRule rule, ref IL if (i < finalMinLevel.Ordinal) continue; - if (finalMinLevelWithFilters[i].Key.HasValue && finalMinLevelWithFilters[i].Key.Value != rule.FilterDefaultAction) + var newFilterResult = finalMinLevelWithFilters[i].Key ?? rule.FilterDefaultAction; + if (newFilterResult != rule.FilterDefaultAction) continue; - var newFilterResult = finalMinLevelWithFilters[i].Key ?? rule.FilterDefaultAction; var newFilterChain = finalMinLevelWithFilters[i].Value?.Count > 0 ? System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Concat(finalMinLevelWithFilters[i].Value, rule.Filters)) : rule.Filters; finalMinLevelWithFilters[i] = new KeyValuePair>(newFilterResult, newFilterChain); } @@ -263,7 +260,7 @@ private static bool AddTargetsFromLoggingRule(LoggingRule rule, string loggerNam return targetsFound; } - private static bool SuppressLogLevel(LoggingRule rule, bool[] ruleLogLevels, LogLevel finalMinLevel, LogLevel globalLogLevel, int logLevelOrdinal, ref bool suppressedLevels) + private static bool SuppressLogLevel(LoggingRule rule, bool[] ruleLogLevels, LogLevel? finalMinLevel, LogLevel globalLogLevel, int logLevelOrdinal, ref bool suppressedLevels) { if (logLevelOrdinal < globalLogLevel.Ordinal) { @@ -295,14 +292,14 @@ private static bool SuppressLogLevel(LoggingRule rule, bool[] ruleLogLevels, Log return false; } - private static TargetWithFilterChain CreateTargetChainFromLoggingRule(LoggingRule rule, Target target, TargetWithFilterChain existingTargets) + private static TargetWithFilterChain? CreateTargetChainFromLoggingRule(LoggingRule rule, Target target, TargetWithFilterChain existingTargets) { var filterChain = rule.Filters.Count == 0 ? ArrayHelper.Empty() : rule.Filters; var newTarget = new TargetWithFilterChain(target, filterChain, rule.FilterDefaultAction); if (existingTargets != null && newTarget.FilterChain.Count == 0) { - for (TargetWithFilterChain afc = existingTargets; afc != null; afc = afc.NextInChain) + for (TargetWithFilterChain? afc = existingTargets; afc != null; afc = afc.NextInChain) { if (ReferenceEquals(target, afc.Target) && afc.FilterChain.Count == 0) { @@ -347,7 +344,7 @@ internal bool TryRememberCallSiteClassName(LogEventInfo logEvent) if (string.IsNullOrEmpty(logEvent.CallSiteInformation?.CallerFilePath)) return false; - string className = logEvent.CallSiteInformation.GetCallerClassName(null, true, true, true); + var className = logEvent.CallSiteInformation?.GetCallerClassName(null, true, true, true); if (string.IsNullOrEmpty(className)) return false; @@ -356,16 +353,13 @@ internal bool TryRememberCallSiteClassName(LogEventInfo logEvent) string internClassName = logEvent.LoggerName == className ? logEvent.LoggerName : -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 string.Intern(className); // Single string-reference for all logging-locations for the same class -#else - className; -#endif + CallSiteKey callSiteKey = new CallSiteKey(logEvent.CallerMemberName, logEvent.CallerFilePath, logEvent.CallerLineNumber); return _callSiteClassNameCache.TryAddValue(callSiteKey, internClassName); } - internal bool TryLookupCallSiteClassName(LogEventInfo logEvent, out string callSiteClassName) + internal bool TryLookupCallSiteClassName(LogEventInfo logEvent, out string? callSiteClassName) { callSiteClassName = logEvent.CallSiteInformation?.CallerClassName; if (!string.IsNullOrEmpty(callSiteClassName)) @@ -385,9 +379,13 @@ public void WriteToLoggerTargets(Type loggerType, LogEventInfo logEvent, LogFact LoggerImpl.Write(loggerType, this, logEvent, logFactory); } - struct CallSiteKey : IEquatable + private +#if !NETFRAMEWORK + readonly +#endif + struct CallSiteKey : IEquatable { - public CallSiteKey(string methodName, string fileSourceName, int fileSourceLineNumber) + public CallSiteKey(string? methodName, string? fileSourceName, int fileSourceLineNumber) { MethodName = methodName ?? string.Empty; FileSourceName = fileSourceName ?? string.Empty; @@ -398,29 +396,16 @@ public CallSiteKey(string methodName, string fileSourceName, int fileSourceLineN public readonly string FileSourceName; public readonly int FileSourceLineNumber; - /// - /// Serves as a hash function for a particular type. - /// public override int GetHashCode() { return MethodName.GetHashCode() ^ FileSourceName.GetHashCode() ^ FileSourceLineNumber; } - /// - /// Determines if two objects are equal in value. - /// - /// Other object to compare to. - /// True if objects are equal, false otherwise. public override bool Equals(object obj) { return obj is CallSiteKey key && Equals(key); } - /// - /// Determines if two objects of the same type are equal in value. - /// - /// Other object to compare to. - /// True if objects are equal, false otherwise. public bool Equals(CallSiteKey other) { return FileSourceLineNumber == other.FileSourceLineNumber diff --git a/src/NLog/Internal/ThreadSafeDictionary.cs b/src/NLog/Internal/ThreadSafeDictionary.cs index cb99eeaa7f..cc1ec1a514 100644 --- a/src/NLog/Internal/ThreadSafeDictionary.cs +++ b/src/NLog/Internal/ThreadSafeDictionary.cs @@ -43,7 +43,7 @@ internal class ThreadSafeDictionary : IDictionary { private readonly object _lockObject = new object(); private Dictionary _dict; - private Dictionary _dictReadOnly; // Reset cache on change + private Dictionary? _dictReadOnly; // Reset cache on change public ThreadSafeDictionary() : this(EqualityComparer.Default) diff --git a/src/NLog/Internal/TimeoutContinuation.cs b/src/NLog/Internal/TimeoutContinuation.cs index 07739073be..3b14ffee9a 100644 --- a/src/NLog/Internal/TimeoutContinuation.cs +++ b/src/NLog/Internal/TimeoutContinuation.cs @@ -40,10 +40,11 @@ namespace NLog.Internal /// /// Wraps with a timeout. /// + [Obsolete("Marked obsolete on NLog 6.0")] internal sealed class TimeoutContinuation : IDisposable { - private AsyncContinuation _asyncContinuation; - private Timer _timeoutTimer; + private AsyncContinuation? _asyncContinuation; + private Timer? _timeoutTimer; /// /// Initializes a new instance of the class. @@ -60,7 +61,7 @@ public TimeoutContinuation(AsyncContinuation asyncContinuation, TimeSpan timeout /// Continuation function which implements the timeout logic. /// /// The exception. - public void Function(Exception exception) + public void Function(Exception? exception) { try { diff --git a/src/NLog/Internal/UrlHelper.cs b/src/NLog/Internal/UrlHelper.cs index e65b802221..6206b66fc8 100644 --- a/src/NLog/Internal/UrlHelper.cs +++ b/src/NLog/Internal/UrlHelper.cs @@ -77,8 +77,8 @@ public static void EscapeDataEncode(string source, StringBuilder target, EscapeE bool isNLogLegacy = Contains(options, EscapeEncodingOptions.NLogLegacy); #pragma warning restore CS0618 // Type or member is obsolete - char[] charArray = null; - byte[] byteArray = null; + char[]? charArray = null; + byte[]? byteArray = null; char[] hexChars = isLowerCaseHex ? hexLowerChars : hexUpperChars; for (int i = 0; i < source.Length; ++i) diff --git a/src/NLog/Internal/XmlHelper.cs b/src/NLog/Internal/XmlHelper.cs index 73c47e6b9f..34e3469a8f 100644 --- a/src/NLog/Internal/XmlHelper.cs +++ b/src/NLog/Internal/XmlHelper.cs @@ -36,20 +36,51 @@ namespace NLog.Internal using System; using System.Globalization; using System.Text; - using System.Xml; /// /// Helper class for XML /// internal static class XmlHelper { - // found on https://stackoverflow.com/questions/397250/unicode-regex-invalid-xml-characters/961504#961504 - // filters control characters but allows only properly-formed surrogate sequences -#if NET35 || NETSTANDARD1_3 || NETSTANDARD1_5 - private static readonly System.Text.RegularExpressions.Regex InvalidXmlChars = new System.Text.RegularExpressions.Regex( - @"(? '\u001f' && chr < HIGH_SURROGATE_START) || ExoticIsXmlChar(chr); + } + + private static bool ExoticIsXmlChar(char chr) + { + if (chr < '\u0020') + return chr == '\u0009' || chr == '\u000a' || chr == '\u000d'; + + if (XmlConvertIsHighSurrogate(chr) || XmlConvertIsLowSurrogate(chr)) + return false; + + if (chr == '\ufffe' || chr == '\uffff') + return false; + + return true; + } + + public static bool XmlConvertIsHighSurrogate(char chr) + { + return chr >= HIGH_SURROGATE_START && chr <= HIGH_SURROGATE_END; + } + + public static bool XmlConvertIsLowSurrogate(char chr) + { + return chr >= LOW_SURROGATE_START && chr <= LOW_SURROGATE_END; + } + + public static bool XmlConvertIsXmlSurrogatePair(char lowChar, char highChar) + { + return XmlConvertIsHighSurrogate(highChar) && XmlConvertIsLowSurrogate(lowChar); + } /// /// removes any unusual unicode characters that can't be encoded into XML @@ -59,14 +90,13 @@ private static string RemoveInvalidXmlChars(string text) if (string.IsNullOrEmpty(text)) return string.Empty; -#if !NET35 && !NETSTANDARD1_3 && !NETSTANDARD1_5 int length = text.Length; for (int i = 0; i < length; ++i) { char ch = text[i]; - if (!XmlConvert.IsXmlChar(ch)) + if (!XmlConvertIsXmlChar(ch)) { - if (i + 1 < text.Length && XmlConvert.IsXmlSurrogatePair(text[i + 1], ch)) + if (i + 1 < text.Length && XmlConvertIsXmlSurrogatePair(text[i + 1], ch)) { ++i; } @@ -76,13 +106,10 @@ private static string RemoveInvalidXmlChars(string text) } } } + return text; -#else - return InvalidXmlChars.Replace(text, string.Empty); -#endif } -#if !NET35 && !NETSTANDARD1_3 && !NETSTANDARD1_5 /// /// Cleans string of any invalid XML chars found /// @@ -94,14 +121,13 @@ private static string CreateValidXmlString(string text) for (int i = 0; i < text.Length; ++i) { char ch = text[i]; - if (XmlConvert.IsXmlChar(ch)) + if (XmlConvertIsXmlChar(ch)) { sb.Append(ch); } } return sb.ToString(); } -#endif internal static void PerformXmlEscapeWhenNeeded(StringBuilder builder, int startPos, bool xmlEncodeNewlines) { @@ -138,7 +164,7 @@ private static bool RequiresXmlEscape(StringBuilder target, int startPos, bool x private static readonly char[] XmlEscapeChars = new char[] { '<', '>', '&', '\'', '"' }; private static readonly char[] XmlEscapeNewlineChars = new char[] { '<', '>', '&', '\'', '"', '\r', '\n' }; - internal static string EscapeXmlString(string text, bool xmlEncodeNewlines, StringBuilder result = null) + internal static string EscapeXmlString(string text, bool xmlEncodeNewlines, StringBuilder? result = null) { if (result is null && SmallAndNoEscapeNeeded(text, xmlEncodeNewlines)) { @@ -190,7 +216,7 @@ internal static string EscapeXmlString(string text, bool xmlEncodeNewlines, Stri } } - return result is null ? sb.ToString() : null; + return result is null ? sb.ToString() : string.Empty; } /// @@ -209,7 +235,7 @@ private static bool SmallAndNoEscapeNeeded(string text, bool xmlEncodeNewlines) /// /// Object value /// Object value converted to string - internal static string XmlConvertToStringSafe(object value) + internal static string XmlConvertToStringSafe(object? value) { return XmlConvertToString(value, true); } @@ -219,36 +245,125 @@ internal static string XmlConvertToStringSafe(object value) /// /// Object value /// Object value converted to string - internal static string XmlConvertToString(object value) + internal static string XmlConvertToString(object? value) { return value is string stringValue ? stringValue : XmlConvertToString(value, false); } internal static string XmlConvertToString(float value) { - if (float.IsNaN(value)) - return XmlConvert.ToString(value); + if (float.IsInfinity(value) || float.IsNaN(value)) + return Convert.ToString(value, CultureInfo.InvariantCulture); + else + return EnsureDecimalPlace(value.ToString("R", NumberFormatInfo.InvariantInfo)); + } - if (float.IsInfinity(value)) + internal static string XmlConvertToString(double value) + { + if (double.IsInfinity(value) || double.IsNaN(value)) return Convert.ToString(value, CultureInfo.InvariantCulture); + else + return EnsureDecimalPlace(value.ToString("R", NumberFormatInfo.InvariantInfo)); + } - return EnsureDecimalPlace(XmlConvert.ToString(value)); + internal static string XmlConvertToString(decimal value) + { + return EnsureDecimalPlace(value.ToString(null, NumberFormatInfo.InvariantInfo)); } - internal static string XmlConvertToString(double value) + /// + /// Converts DateTime to ISO 8601 format in UTC timezone. + /// + internal static string XmlConvertToString(DateTime value) { - if (double.IsNaN(value)) - return XmlConvert.ToString(value); + if (value.Kind == DateTimeKind.Unspecified) + value = new DateTime(value.Ticks, DateTimeKind.Utc); + else + value = value.ToUniversalTime(); + return value.ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK", CultureInfo.InvariantCulture); + } - if (double.IsInfinity(value)) - return Convert.ToString(value, CultureInfo.InvariantCulture); + /// + /// Converts object value to invariant format (understood by JavaScript) + /// + /// Object value + /// Object TypeCode + /// Check and remove unusual unicode characters from the result string. + /// Object value converted to string + internal static string XmlConvertToString(IConvertible? value, TypeCode objTypeCode, bool safeConversion = false) + { + if (objTypeCode == TypeCode.Empty || value is null) + { + return "null"; + } + + switch (objTypeCode) + { + case TypeCode.Boolean: + return value.ToBoolean(CultureInfo.InvariantCulture) ? "true" : "false"; // boolean as lowercase + case TypeCode.Byte: + return value.ToByte(CultureInfo.InvariantCulture).ToString(null, NumberFormatInfo.InvariantInfo); + case TypeCode.SByte: + return value.ToSByte(CultureInfo.InvariantCulture).ToString(null, NumberFormatInfo.InvariantInfo); + case TypeCode.Int16: + return value.ToInt16(CultureInfo.InvariantCulture).ToString(null, NumberFormatInfo.InvariantInfo); + case TypeCode.Int32: + return value.ToInt32(CultureInfo.InvariantCulture).ToString(null, NumberFormatInfo.InvariantInfo); + case TypeCode.Int64: + return value.ToInt64(CultureInfo.InvariantCulture).ToString(null, NumberFormatInfo.InvariantInfo); + case TypeCode.UInt16: + return value.ToUInt16(CultureInfo.InvariantCulture).ToString(null, NumberFormatInfo.InvariantInfo); + case TypeCode.UInt32: + return value.ToUInt32(CultureInfo.InvariantCulture).ToString(null, NumberFormatInfo.InvariantInfo); + case TypeCode.UInt64: + return value.ToUInt64(CultureInfo.InvariantCulture).ToString(null, NumberFormatInfo.InvariantInfo); + case TypeCode.Single: + return XmlConvertToString(value.ToSingle(CultureInfo.InvariantCulture)); + case TypeCode.Double: + return XmlConvertToString(value.ToDouble(CultureInfo.InvariantCulture)); + case TypeCode.Decimal: + return XmlConvertToString(value.ToDecimal(CultureInfo.InvariantCulture)); + case TypeCode.DateTime: + return XmlConvertToString(value.ToDateTime(CultureInfo.InvariantCulture)); + case TypeCode.Char: + return safeConversion ? RemoveInvalidXmlChars(value.ToString(CultureInfo.InvariantCulture)) : value.ToString(CultureInfo.InvariantCulture); + case TypeCode.String: + return safeConversion ? RemoveInvalidXmlChars(value.ToString(CultureInfo.InvariantCulture)) : value.ToString(CultureInfo.InvariantCulture); + default: + return XmlConvertToStringInvariant(value, safeConversion); + } + } + + private static string XmlConvertToString(object? value, bool safeConversion) + { + try + { + var convertibleValue = value as IConvertible; + var objTypeCode = convertibleValue?.GetTypeCode() ?? (value is null ? TypeCode.Empty : TypeCode.Object); + if (objTypeCode != TypeCode.Object) + { + return XmlConvertToString(convertibleValue, objTypeCode, safeConversion); + } - return EnsureDecimalPlace(XmlConvert.ToString(value)); + return XmlConvertToStringInvariant(value, safeConversion); + } + catch + { + return string.Empty; + } } - internal static string XmlConvertToString(decimal value) + private static string XmlConvertToStringInvariant(object? value, bool safeConversion) { - return EnsureDecimalPlace(XmlConvert.ToString(value)); + try + { + string valueString = Convert.ToString(value, CultureInfo.InvariantCulture); + return safeConversion ? RemoveInvalidXmlChars(valueString) : valueString; + } + catch + { + return string.Empty; + } } /// @@ -268,7 +383,7 @@ internal static string XmlConvertToElementName(string xmlElementName) bool allowNamespace = true; - StringBuilder sb = null; + StringBuilder? sb = null; for (int i = 0; i < xmlElementName.Length; ++i) { char chr = xmlElementName[i]; @@ -336,123 +451,6 @@ StringBuilder CreateStringBuilder(string orgValue, int i) } } - private static string XmlConvertToString(object value, bool safeConversion) - { - try - { - var convertibleValue = value as IConvertible; - var objTypeCode = convertibleValue?.GetTypeCode() ?? (value is null ? TypeCode.Empty : TypeCode.Object); - if (objTypeCode != TypeCode.Object) - { - return XmlConvertToString(convertibleValue, objTypeCode, safeConversion); - } - - return XmlConvertToStringInvariant(value, safeConversion); - } - catch - { - return safeConversion ? string.Empty : null; - } - } - - private static string XmlConvertToStringInvariant(object value, bool safeConversion) - { - try - { - string valueString = Convert.ToString(value, CultureInfo.InvariantCulture); - return safeConversion ? RemoveInvalidXmlChars(valueString) : valueString; - } - catch - { - return safeConversion ? string.Empty : null; - } - } - - /// - /// Converts object value to invariant format (understood by JavaScript) - /// - /// Object value - /// Object TypeCode - /// Check and remove unusual unicode characters from the result string. - /// Object value converted to string - internal static string XmlConvertToString(IConvertible value, TypeCode objTypeCode, bool safeConversion = false) - { - if (objTypeCode == TypeCode.Empty || value is null) - { - return "null"; - } - - switch (objTypeCode) - { - case TypeCode.Boolean: - return XmlConvert.ToString(value.ToBoolean(CultureInfo.InvariantCulture)); // boolean as lowercase - case TypeCode.Byte: - return XmlConvert.ToString(value.ToByte(CultureInfo.InvariantCulture)); - case TypeCode.SByte: - return XmlConvert.ToString(value.ToSByte(CultureInfo.InvariantCulture)); - case TypeCode.Int16: - return XmlConvert.ToString(value.ToInt16(CultureInfo.InvariantCulture)); - case TypeCode.Int32: - return XmlConvert.ToString(value.ToInt32(CultureInfo.InvariantCulture)); - case TypeCode.Int64: - return XmlConvert.ToString(value.ToInt64(CultureInfo.InvariantCulture)); - case TypeCode.UInt16: - return XmlConvert.ToString(value.ToUInt16(CultureInfo.InvariantCulture)); - case TypeCode.UInt32: - return XmlConvert.ToString(value.ToUInt32(CultureInfo.InvariantCulture)); - case TypeCode.UInt64: - return XmlConvert.ToString(value.ToUInt64(CultureInfo.InvariantCulture)); - case TypeCode.Single: - return XmlConvertToString(value.ToSingle(CultureInfo.InvariantCulture)); - case TypeCode.Double: - return XmlConvertToString(value.ToDouble(CultureInfo.InvariantCulture)); - case TypeCode.Decimal: - return XmlConvertToString(value.ToDecimal(CultureInfo.InvariantCulture)); - case TypeCode.DateTime: - return XmlConvert.ToString(value.ToDateTime(CultureInfo.InvariantCulture), XmlDateTimeSerializationMode.Utc); - case TypeCode.Char: - return XmlConvert.ToString(value.ToChar(CultureInfo.InvariantCulture)); - case TypeCode.String: - return safeConversion ? RemoveInvalidXmlChars(value.ToString(CultureInfo.InvariantCulture)) : value.ToString(CultureInfo.InvariantCulture); - default: - return XmlConvertToStringInvariant(value, safeConversion); - } - } - - /// - /// Safe version of WriteAttributeString - /// - /// - /// - /// - public static void WriteAttributeSafeString(this XmlWriter writer, string localName, string value) - { - writer.WriteAttributeString(localName, RemoveInvalidXmlChars(value)); - } - - /// - /// Safe version of WriteElementSafeString - /// - /// - /// - /// - /// - /// - public static void WriteElementSafeString(this XmlWriter writer, string prefix, string localName, string ns, string value) - { - writer.WriteElementString(prefix, localName, ns, RemoveInvalidXmlChars(value)); - } - - /// - /// Safe version of WriteCData - /// - /// - /// - public static void WriteSafeCData(this XmlWriter writer, string value) - { - writer.WriteCData(RemoveInvalidXmlChars(value)); - } - private static string EnsureDecimalPlace(string text) { if (text.IndexOf('.') != -1 || text.IndexOfAny(DecimalScientificExponent) != -1) @@ -479,5 +477,46 @@ private static string EnsureDecimalPlace(string text) } private static readonly char[] DecimalScientificExponent = new[] { 'e', 'E' }; + + public static void RemoveInvalidXmlIfNeeded(StringBuilder builder, int orgLength) + { + for (int i = orgLength; i < builder.Length; ++i) + { + if (!XmlConvertIsXmlChar(builder[i])) + { + var text = builder.ToString(i, builder.Length - i); + builder.Length = i; + text = RemoveInvalidXmlChars(text); + builder.Append(text); + break; + } + } + } + + public static void EscapeCDataIfNeeded(StringBuilder builder, int orgLength) + { + for (int i = orgLength; i < builder.Length; ++i) + { + if (builder[i] == ']' && i + 2 < builder.Length && builder[i + 1] == ']' && builder[i + 2] == '>') + { + var text = builder.ToString(i, builder.Length - i); + builder.Length = i; + text = text.Replace("]]>", "]]]]>"); + builder.Append(text); + break; + } + } + } + + public static string EscapeCData(string text) + { + if (string.IsNullOrEmpty(text)) + return ""; + + if (text.Contains("]]>")) + text = text.Replace("]]>", "]]]]>"); + + return $""; + } } } diff --git a/src/NLog/Internal/XmlParser.cs b/src/NLog/Internal/XmlParser.cs new file mode 100644 index 0000000000..d11fdb1176 --- /dev/null +++ b/src/NLog/Internal/XmlParser.cs @@ -0,0 +1,757 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Internal +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using NLog.Config; + + /// + /// A minimal XML reader, because .NET System.Xml.XmlReader doesn't work with AOT + /// + internal sealed class XmlParser + { + private readonly CharEnumerator _xmlSource; + private readonly StringBuilder _stringBuilder = new StringBuilder(); + + public XmlParser(TextReader xmlSource) + { + _xmlSource = new CharEnumerator(xmlSource); + } + + public XmlParser(string xmlSource) + { + _xmlSource = new CharEnumerator(new StringReader(xmlSource)); + } + + public XmlParserElement LoadDocument(out IList? processingInstructions) + { + try + { + TryReadProcessingInstructions(out processingInstructions); + + if (!TryReadStartElement(out var rootName, out var rootAttributes)) + throw new XmlParserException("Invalid XML document. Cannot parse root start-tag"); + + Stack stack = new Stack(); + + var currentRoot = new XmlParserElement(rootName ?? string.Empty, rootAttributes); + stack.Push(currentRoot); + + bool stillReading = true; + + while (stillReading) + { + stillReading = false; + + if (TryReadEndElement(currentRoot.Name)) + { + stillReading = true; + stack.Pop(); + if (stack.Count == 0) + break; + + currentRoot = stack.Peek(); + } + + try + { + if (TryReadInnerText(out var innerText)) + { + stillReading = true; + currentRoot.InnerText += innerText; + } + + if (TryReadStartElement(out var elementName, out var elementAttributes)) + { + stillReading = true; + currentRoot = new XmlParserElement(elementName ?? string.Empty, elementAttributes); + stack.Peek().AddChild(currentRoot); + stack.Push(currentRoot); + } + } + catch (XmlParserException ex) + { + throw new XmlParserException(ex.Message + $" - Start-tag: {currentRoot.Name}"); + } + } + + if (!stillReading) + throw new XmlParserException($"Invalid XML document. Cannot parse end-tag: {currentRoot.Name}"); + + SkipWhiteSpaces(); + while (_xmlSource.Peek() == '!' && _xmlSource.Current == '<') + { + _xmlSource.MoveNext(); + SkipXmlComment(); + } + if (_xmlSource.MoveNext()) + throw new XmlParserException($"Invalid XML document. Unexpected characters after end-tag: {currentRoot.Name}"); + + return currentRoot; + } + catch (XmlParserException ex) + { + throw new XmlParserException(ex.Message + $" - Line: {_xmlSource.LineNumber}"); + } + } + + public bool TryReadProcessingInstructions(out IList? processingInstructions) + { + SkipWhiteSpaces(); + + processingInstructions = null; + + while (_xmlSource.Current == '<') + { + if (_xmlSource.Peek() == '!') + { + _xmlSource.MoveNext(); + SkipXmlComment(); + continue; + } + if (_xmlSource.Peek() != '?') + break; + + if (!TryBeginReadStartElement(out var instructionName, processingInstruction: true)) + throw new XmlParserException("Invalid XML document. Cannot parse XML processing instruction"); + + if (instructionName is null || string.IsNullOrEmpty(instructionName) || instructionName.Length == 1 || instructionName[0] != '?') + throw new XmlParserException("Invalid XML document. Cannot parse XML processing instruction"); + + instructionName = instructionName.Substring(1); + + TryReadAttributes(out var instructionAttributes, expectsProcessingInstruction: true); + + if (!SkipChar('?')) + throw new XmlParserException($"Invalid XML document. Cannot parse XML processing instruction: {instructionName}"); + if (!SkipChar('>')) + throw new XmlParserException($"Invalid XML document. Cannot parse XML processing instruction: {instructionName}"); + + var xmlInstruction = new XmlParserElement(instructionName, instructionAttributes); + processingInstructions = processingInstructions ?? new List(); + processingInstructions.Add(xmlInstruction); + + SkipWhiteSpaces(); + } + + return processingInstructions != null; + } + + /// + /// Reads a start element. + /// + /// if start element was found. + /// Something unexpected has failed. + public bool TryReadStartElement(out string? name, out List>? attributes) + { + SkipWhiteSpaces(); + + if (TryBeginReadStartElement(out name)) + { + try + { + TryReadAttributes(out attributes); + SkipChar('>'); + } + catch (XmlParserException ex) + { + throw new XmlParserException(ex.Message + $" - Cannot parse attributes for Start-tag: {name}"); + } + return true; + } + + name = default; + attributes = default; + return false; + } + + /// + /// Skips an end element. + /// + /// The name of the element to skip. + /// if an end element was skipped; otherwise, . + /// Something unexpected has failed. + public bool TryReadEndElement(string name) + { + _ = SkipWhiteSpaces(); + + if (_xmlSource.Current == '<' && _xmlSource.Peek() != '/') + return false; + + if (_xmlSource.Current == '/' && _xmlSource.Peek() == '>') + return SkipChar('/') && SkipChar('>'); // Self-closing element + + if (!SkipChar('<')) + return false; + + if (!SkipChar('/')) + throw new XmlParserException($"Invalid XML document. Cannot parse end-tag: {name}"); + + for (var i = 0; i < name.Length; i++) + { + var chr = _xmlSource.Current; + if (chr != name[i]) + throw new XmlParserException($"Invalid XML document. Cannot parse end-tag: {name}"); + + if (!_xmlSource.MoveNext()) + throw new XmlParserException($"Invalid XML document. Cannot parse end-tag: {name}"); + } + + if (!SkipChar('>')) + throw new XmlParserException($"Invalid XML document. Cannot parse end-tag: {name}"); + + return true; + } + + /// + /// Reads content of an element. + /// + /// The content of the element. + /// Something unexpected has failed. + public bool TryReadInnerText(out string innerText) + { + var currentChar = _xmlSource.Current; + + SkipWhiteSpaces(); + + innerText = ReadUntilChar('<', includeSpaces: true); + + while (_xmlSource.Current == '<' && _xmlSource.Peek() == '!') + { + _xmlSource.MoveNext(); + currentChar = _xmlSource.Current; + if (_xmlSource.Peek() == '-') + { + // + SkipXmlComment(); + } + else if (_xmlSource.Peek() == '[') + { + // + innerText += ReadCDATA(); + } + else + { + throw new XmlParserException($"Invalid XML document. Cannot parse XML comment"); + } + + innerText += ReadUntilChar('<', includeSpaces: true); + } + + SkipWhiteSpaces(); + if (string.IsNullOrEmpty(innerText) && _xmlSource.Current == '<') + return currentChar != '<'; + else + return true; + } + + private string ReadCDATA() + { + string contentValue; + if (!SkipCDATA()) + throw new XmlParserException("Invalid XML document. Cannot parse XML CDATA"); + + _stringBuilder.ClearBuilder(); + + do + { + if (_xmlSource.Current == ']' && _xmlSource.Peek() == ']') + { + _xmlSource.MoveNext(); + if (_xmlSource.Peek() == '>') + { + _xmlSource.MoveNext(); + _xmlSource.MoveNext(); + break; + } + + _stringBuilder.Append(']'); + } + + _stringBuilder.Append(_xmlSource.Current); + } while (_xmlSource.MoveNext()); + + contentValue = _stringBuilder.ToString(); + SkipWhiteSpaces(); + return contentValue; + } + + private bool SkipCDATA() + { + if (!SkipChar('!')) + return false; + if (!SkipChar('[')) + return false; + if (!SkipChar('C')) + return false; + if (!SkipChar('D')) + return false; + if (!SkipChar('A')) + return false; + if (!SkipChar('T')) + return false; + if (!SkipChar('A')) + return false; + if (!SkipChar('[')) + return false; + return true; + } + + private void SkipXmlComment() + { + if (!SkipChar('!') || !SkipChar('-') || !SkipChar('-')) + throw new XmlParserException("Invalid XML document. Cannot parse XML comment"); + + while (_xmlSource.MoveNext()) + { + if (!SkipChar('-')) + continue; + + if (SkipChar('-') && SkipChar('>')) + break; + } + + SkipWhiteSpaces(); + } + + /// Something unexpected has failed. + private bool TryReadAttributes(out List>? attributes, bool expectsProcessingInstruction = false) + { + SkipWhiteSpaces(); + + attributes = null; + + while (_xmlSource.Current != '>' && _xmlSource.Current != '/' && (!expectsProcessingInstruction || _xmlSource.Current != '?')) + { + var attName = ReadUntilChar('=').Trim(); + if (string.IsNullOrEmpty(attName)) + throw new XmlParserException("Invalid XML document. Cannot parse XML attribute"); + + if (!SkipChar('=')) + throw new XmlParserException("Invalid XML document. Cannot parse XML attribute"); + + var isApostrophe = false; + + SkipWhiteSpaces(); + + if (!SkipChar('"')) + { + if (SkipChar('\'')) + { + isApostrophe = true; + } + else + { + throw new XmlParserException($"Invalid XML document. Cannot parse XML attribute: {attName}"); + } + } + + try + { + var attValue = ReadUntilChar(isApostrophe ? '\'' : '"', includeSpaces: true); + _xmlSource.MoveNext(); + + attributes = attributes ?? new List>(); + attributes.Add(new KeyValuePair(attName, attValue)); + + SkipWhiteSpaces(); + } + catch (XmlParserException ex) + { + throw new XmlParserException(ex.Message + $" - XML attribute: {attName}"); + } + } + + return attributes != null; + } + + /// + /// Consumer of this method should handle safe position. + /// + /// Something unexpected has failed. + private bool TryBeginReadStartElement(out string? name, bool processingInstruction = false) + { + if (_xmlSource.Current != '<' || _xmlSource.Peek() == '/' || _xmlSource.Peek() == '!') + { + name = default; + return false; + } + + _xmlSource.MoveNext(); + + SkipWhiteSpaces(); + + _stringBuilder.ClearBuilder(); + + do + { + var chr = _xmlSource.Current; + if (CharIsSpace(chr) || chr == '/' || chr == '>') + { + break; + } + + if (processingInstruction && chr == '?') + { + if (_stringBuilder.Length != 0) + throw new XmlParserException($"Invalid XML document. Cannot parse XML start-tag with character: {chr}"); + } + else if (!IsValidXmlNameChar(chr)) + { + throw new XmlParserException($"Invalid XML document. Cannot parse XML start-tag with character: {chr}"); + } + + _stringBuilder.Append(chr); + } while (_xmlSource.MoveNext()); + + name = _stringBuilder.ToString(); + if (string.IsNullOrEmpty(name)) + throw new XmlParserException($"Invalid XML document. Cannot parse XML start-tag"); + + return true; + } + + private bool SkipChar(char c) + { + if (_xmlSource.Current != c) + { + return false; + } + + _xmlSource.MoveNext(); + return true; + } + + private bool SkipWhiteSpaces() + { + var skipped = false; + while (!_xmlSource.EndOfFile && CharIsSpace(_xmlSource.Current) && _xmlSource.MoveNext()) + { + skipped = true; + } + return skipped; + } + + /// Something unexpected has failed. + private string ReadUntilChar(char expectedChar, bool includeSpaces = false) + { + _stringBuilder.ClearBuilder(); + + bool trimEnd = false; + + do + { + var chr = _xmlSource.Current; + if (chr == expectedChar) + { + break; + } + + if (!includeSpaces && CharIsSpace(chr)) + { + SkipWhiteSpaces(); + if (_xmlSource.Current == expectedChar) + break; + throw new XmlParserException($"Invalid XML document. Cannot parse attribute-name with white-space"); + } + else if (!includeSpaces && !IsValidXmlNameChar(chr)) + { + throw new XmlParserException($"Invalid XML document. Cannot parse attribute-name with character: {chr}"); + } + + if (chr == '<' && (!includeSpaces || expectedChar == '<')) + throw new XmlParserException($"Invalid XML document. Cannot parse value with '<', maybe encode to <"); + + if (includeSpaces && chr == '&') + { + _xmlSource.MoveNext(); + if (_xmlSource.Current == '#' && char.IsDigit(_xmlSource.Peek())) + { + int unicode = TryParseUnicodeValue(); + if (unicode != 0) + _stringBuilder.Append((char)unicode); + } + else if (_xmlSource.Current == '#' && (_xmlSource.Peek() == 'x' || _xmlSource.Peek() == 'X')) + { + _xmlSource.MoveNext(); + int unicode = TryParseUnicodeValueHex(); + if (unicode != 0) + _stringBuilder.Append((char)unicode); + } + else if (TryParseSpecialXmlToken(out var specialToken)) + { + _stringBuilder.Append(specialToken); + } + else + { + _stringBuilder.Append('&'); + if (_xmlSource.Current == expectedChar) + break; + _stringBuilder.Append(_xmlSource.Current); + } + } + else + { + if (includeSpaces && expectedChar == '<') + { + if (_stringBuilder.Length == 0 && CharIsSpace(chr)) + continue; + + trimEnd = !trimEnd && CharIsSpace(chr); + } + _stringBuilder.Append(chr); + } + } while (_xmlSource.MoveNext()); + + var value = _stringBuilder.ToString(); + return trimEnd ? value.TrimEnd(ArrayHelper.Empty()) : value; + } + + private static bool IsValidXmlNameChar(char chr) + { + if (char.IsLetter(chr) || char.IsDigit(chr)) + return true; + + switch (chr) + { + case '_': + case '-': + case '.': + case ':': + return true; + default: + return false; + } + } + + private int TryParseUnicodeValue() + { + int unicode = '\0'; + while (_xmlSource.MoveNext()) + { + if (_xmlSource.Current == ';') + break; + + if (_xmlSource.Current < '0' || _xmlSource.Current > '9') + throw new XmlParserException("Invalid XML document. Cannot parse unicode-char digit-value"); + + unicode *= 10; + unicode += _xmlSource.Current - '0'; + } + + if (unicode >= '\uffff') + throw new XmlParserException("Invalid XML document. Unicode value exceeds maximum allowed value"); + + return unicode; + } + + private int TryParseUnicodeValueHex() + { + int unicode = '\0'; + while (_xmlSource.MoveNext()) + { + if (_xmlSource.Current == ';') + break; + + unicode *= 16; + if ("abcdef".Contains(char.ToLower(_xmlSource.Current))) + unicode += int.Parse(_xmlSource.Current.ToString(), System.Globalization.NumberStyles.HexNumber); + else if (_xmlSource.Current < '0' || _xmlSource.Current > '9') + throw new XmlParserException("Invalid XML document. Cannot parse unicode-char hex-value"); + else + unicode += _xmlSource.Current - '0'; + } + + if (unicode >= '\uffff') + throw new XmlParserException("Invalid XML document. Unicode value exceeds maximum allowed value"); + + return unicode; + } + + private bool TryParseSpecialXmlToken(out string? xmlToken) + { + foreach (var token in _specialTokens) + { + if (_xmlSource.Current == token.Key[0] && _xmlSource.Peek() == token.Key[1]) + { + foreach (var tokenChr in token.Key) + if (!SkipChar(tokenChr)) + throw new XmlParserException($"Invalid XML document. Cannot parse special token: {token.Key}"); + if (_xmlSource.Current != ';') + throw new XmlParserException($"Invalid XML document. Cannot parse special token: {token.Key}"); + xmlToken = token.Value; + return true; + } + } + + xmlToken = null; + return false; + } + + private static readonly Dictionary _specialTokens = new Dictionary() + { + { "amp", "&" }, + { "AMP", "&" }, + { "apos", "\'" }, + { "APOS", "\'" }, + { "quot", "\"" }, + { "QUOT", "\"" }, + { "lt", "<" }, + { "LT", "<" }, + { "gt", ">" }, + { "GT", ">" }, + }; + + private static bool CharIsSpace(char c) + { + switch (c) + { + case ' ': + case '\t': + case '\r': + case '\n': + return true; + default: + return char.IsWhiteSpace(c); + } + } + + public sealed class XmlParserElement + { + public string Name { get; set; } + public string? InnerText { get; set; } + public IList Children => _children ?? ArrayHelper.Empty(); + private IList? _children; + public IList> Attributes => _attributes ?? ArrayHelper.Empty>(); + private readonly IList>? _attributes; + + public XmlParserElement(string name, IList>? attributes) + { + Name = name; + _attributes = attributes; + } + + public void AddChild(XmlParserElement child) + { + if (_children is null) + _children = new List(); + _children.Add(child); + } + } + + private sealed class CharEnumerator : IEnumerator + { + private readonly TextReader _xmlSource; + private int _lineNumber; + private char _current; + private char? _peek; + private bool _endOfFile; + + public CharEnumerator(TextReader xmlSource) + { + _xmlSource = xmlSource; + var current = xmlSource.Read(); + _current = current < 0 ? '\0' : (char)current; + _lineNumber = current == '\n' ? 2 : 1; + } + + public char Current + { + get + { + if (_endOfFile) + throw new XmlParserException($"Invalid XML document. Unexpected end of document."); + return _current; + } + } + + public int LineNumber => _lineNumber; + + object IEnumerator.Current => Current; + + public bool EndOfFile => _endOfFile; + + public bool MoveNext() + { + if (_peek.HasValue) + { + _current = _peek.Value; + if (_current == '\n') + ++_lineNumber; + _peek = null; + return true; + } + + var current = _xmlSource.Read(); + if (current < 0) + { + _endOfFile = true; + return false; + } + + _current = (char)current; + if (_current == '\n') + ++_lineNumber; + return true; + } + + public char Peek() + { + if (_peek.HasValue) + return _peek.Value; + + var current = _xmlSource.Read(); + if (current < 0) + return '\0'; + _peek = (char)current; + return _peek.Value; + } + + void IEnumerator.Reset() + { + // NOSONAR: Nothing to reset + } + + void IDisposable.Dispose() + { + // NOSONAR: Nothing to dispose + } + } + } +} diff --git a/src/NLog/LayoutRenderers/AllEventPropertiesLayoutRenderer.cs b/src/NLog/LayoutRenderers/AllEventPropertiesLayoutRenderer.cs index e2170c1ffb..bb83f6f8b8 100644 --- a/src/NLog/LayoutRenderers/AllEventPropertiesLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/AllEventPropertiesLayoutRenderer.cs @@ -53,37 +53,47 @@ namespace NLog.LayoutRenderers public class AllEventPropertiesLayoutRenderer : LayoutRenderer { private string _format; - private string _beforeKey; - private string _afterKey; - private string _afterValue; + private string? _beforeKey; + private string? _afterKey; + private string? _afterValue; /// /// Initializes a new instance of the class. /// public AllEventPropertiesLayoutRenderer() { - Separator = ", "; - Format = "[key]=[value]"; + _format = Format = "[key]=[value]"; Exclude = new HashSet(StringComparer.OrdinalIgnoreCase); } /// /// Gets or sets string that will be used to separate key/value pairs. /// + /// Default: , /// - public string Separator { get; set; } + public string Separator + { + get => _separatorOriginal ?? _separator; + set + { + _separatorOriginal = value; + _separator = Layouts.SimpleLayout.Evaluate(value, LoggingConfiguration, throwConfigExceptions: false); + } + } + private string _separator = ", "; + private string _separatorOriginal = ", "; /// - /// Get or set if empty values should be included. - /// - /// A value is empty when null or in case of a string, null or empty string. + /// Gets or sets whether empty property-values should be included in the output. /// + /// Default: . Empty value is either null or empty string /// public bool IncludeEmptyValues { get; set; } /// /// Gets or sets whether to include the contents of the properties-dictionary. /// + /// Default: /// public bool IncludeScopeProperties { get; set; } @@ -98,10 +108,10 @@ public AllEventPropertiesLayoutRenderer() #endif /// - /// Enables capture of ScopeContext-properties from active thread context + /// Disables to capture ScopeContext-properties from active thread context /// - public LayoutRenderer FixScopeContext => IncludeScopeProperties ? _fixScopeContext : null; - private static readonly LayoutRenderer _fixScopeContext = new ScopeContextPropertyLayoutRenderer() { Item = string.Empty }; + public LayoutRenderer? DisableThreadAgnostic => IncludeScopeProperties ? _disableThreadAgnostic : null; + private static readonly LayoutRenderer _disableThreadAgnostic = new FuncLayoutRenderer(string.Empty, (evt, cfg) => string.Empty); /// /// Gets or sets how key/value pairs will be formatted. @@ -139,9 +149,18 @@ public string Format /// /// Gets or sets the culture used for rendering. /// + /// Default: /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + if (_separatorOriginal != null) + _separator = Layouts.SimpleLayout.Evaluate(_separatorOriginal, LoggingConfiguration); + } + /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { @@ -155,7 +174,7 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) bool includeSeparator = false; if (logEvent.HasProperties) { - using (var propertyEnumerator = logEvent.CreateOrUpdatePropertiesInternal().GetPropertyEnumerator()) + using (var propertyEnumerator = logEvent.CreatePropertiesInternal().GetPropertyEnumerator()) { while (propertyEnumerator.MoveNext()) { @@ -184,9 +203,9 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) } } - private bool AppendProperty(StringBuilder builder, object propertyKey, object propertyValue, string propertyFormat, IFormatProvider formatProvider, bool includeSeparator, bool checkForExclude, bool nonStandardFormat) + private bool AppendProperty(StringBuilder builder, object propertyKey, object? propertyValue, string? propertyFormat, IFormatProvider? formatProvider, bool includeSeparator, bool checkForExclude, bool nonStandardFormat) { - if (!IncludeEmptyValues && IsEmptyPropertyValue(propertyValue)) + if (!IncludeEmptyValues && StringHelpers.IsNullOrEmptyString(propertyValue)) return false; if (checkForExclude && Exclude.Contains(propertyKey as string ?? string.Empty)) @@ -194,7 +213,7 @@ private bool AppendProperty(StringBuilder builder, object propertyKey, object pr if (includeSeparator) { - builder.Append(Separator); + builder.Append(_separator); } if (nonStandardFormat) @@ -216,15 +235,5 @@ private bool AppendProperty(StringBuilder builder, object propertyKey, object pr return true; } - - private static bool IsEmptyPropertyValue(object value) - { - if (value is string s) - { - return string.IsNullOrEmpty(s); - } - - return value is null; - } } } diff --git a/src/NLog/LayoutRenderers/AppDomainLayoutRenderer.cs b/src/NLog/LayoutRenderers/AppDomainLayoutRenderer.cs index 0ebff4b138..1bb698f2ae 100644 --- a/src/NLog/LayoutRenderers/AppDomainLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/AppDomainLayoutRenderer.cs @@ -76,10 +76,16 @@ internal AppDomainLayoutRenderer(IAppEnvironment appEnvironment) } /// - /// Format string. Possible values: "Short", "Long" or custom like {0} {1}. Default "Long" - /// The first parameter is the AppDomain.Id, the second the second the AppDomain.FriendlyName - /// This string is used in + /// Gets or sets format-string for displaying details. + /// This string is used in where + /// first parameter is and second is /// + /// + /// Default: Long . How alias names are mapped: + /// Short = {0:00} + /// Long = {0:0000}:{1} + /// Friendly = {1} + /// /// [DefaultParameter] public string Format { get; set; } = LongFormatCode; @@ -98,7 +104,7 @@ protected override void CloseLayoutRenderer() base.CloseLayoutRenderer(); } - private string _assemblyName; + private string? _assemblyName; /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) diff --git a/src/NLog/LayoutRenderers/AppSettingLayoutRenderer.cs b/src/NLog/LayoutRenderers/AppSettingLayoutRenderer.cs index 040319334c..9f61cde897 100644 --- a/src/NLog/LayoutRenderers/AppSettingLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/AppSettingLayoutRenderer.cs @@ -55,15 +55,15 @@ namespace NLog.LayoutRenderers [ThreadAgnostic] public sealed class AppSettingLayoutRenderer : LayoutRenderer, IStringValueRenderer { - private string _connectionStringName = null; + private string? _connectionStringName = null; /// /// The AppSetting item-name /// + /// [Required] Default: /// - [RequiredParameter] [DefaultParameter] - public string Item { get; set; } + public string Item { get; set; } = string.Empty; /// /// Obsolete and replaced by with NLog v4.6. @@ -77,17 +77,23 @@ public sealed class AppSettingLayoutRenderer : LayoutRenderer, IStringValueRende /// /// The default value to render if the AppSetting value is null. /// + /// Default: /// - public string Default { get; set; } + public string Default { get; set; } = string.Empty; internal IConfigurationManager ConfigurationManager { get; set; } = new ConfigurationManager(); /// protected override void InitializeLayoutRenderer() { + base.InitializeLayoutRenderer(); + string connectionStringSection = "ConnectionStrings."; _connectionStringName = Item?.TrimStart().StartsWith(connectionStringSection, StringComparison.OrdinalIgnoreCase) == true ? Item.TrimStart().Substring(connectionStringSection.Length) : null; + + if (string.IsNullOrEmpty(Item)) + throw new NLogConfigurationException("AppSetting-LayoutRenderer Item-property must be assigned. Lookup blank value not supported."); } /// @@ -103,9 +109,9 @@ private string GetStringValue() if (string.IsNullOrEmpty(Item)) return Default; - string value = _connectionStringName != null ? - ConfigurationManager.LookupConnectionString(_connectionStringName)?.ConnectionString : - ConfigurationManager.AppSettings[Item]; + var value = _connectionStringName is null ? + ConfigurationManager.AppSettings[Item] : + ConfigurationManager.LookupConnectionString(_connectionStringName)?.ConnectionString; return value ?? Default ?? string.Empty; } diff --git a/src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs b/src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs index ac3d10d145..1a061beece 100644 --- a/src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs @@ -56,35 +56,32 @@ public class AssemblyVersionLayoutRenderer : LayoutRenderer /// /// The (full) name of the assembly. If null, using the entry assembly. /// + /// Default: /// [DefaultParameter] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; /// /// Gets or sets the type of assembly version to retrieve. /// - /// - /// Some version type and platform combinations are not fully supported. - /// - UWP earlier than .NET Standard 1.5: Value for is always returned unless the parameter is specified. - /// + /// Default: /// public AssemblyVersionType Type { get; set; } = AssemblyVersionType.Assembly; /// /// The default value to render if the Version is not available /// + /// Default: /// public string Default { get => _default ?? GenerateDefaultValue(); set => _default = value; } - private string _default; + private string? _default; /// /// Gets or sets the custom format of the assembly version output. /// /// - /// Supported placeholders are 'major', 'minor', 'build' and 'revision'. - /// The default .NET template for version numbers is 'major.minor.build.revision'. See - /// https://docs.microsoft.com/en-gb/dotnet/api/system.version?view=netframework-4.7.2#remarks - /// for details. + /// Default: major.minor.build.revision . + /// Supported placeholders are 'major', 'minor', 'build' and 'revision'. For more details /// /// public string Format @@ -103,14 +100,7 @@ protected override void InitializeLayoutRenderer() base.InitializeLayoutRenderer(); } - /// - protected override void CloseLayoutRenderer() - { - _assemblyVersion = null; - base.CloseLayoutRenderer(); - } - - private string _assemblyVersion; + private string? _assemblyVersion; /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) @@ -125,18 +115,18 @@ private string ApplyFormatToVersion(string version) { if (version is null) { - return _default; + return Default; } else if (StringHelpers.IsNullOrWhiteSpace(version)) { - return _default ?? GenerateDefaultValue(); + return Default; } else if (version == "0.0.0.0" && _default != null) { - return _default; + return Default; } - if (Format.Equals(DefaultFormat, StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(version)) + if (Format.Equals(DefaultFormat, StringComparison.OrdinalIgnoreCase)) { return version; } @@ -164,13 +154,13 @@ private string GetVersion() switch (Type) { case AssemblyVersionType.File: - return assembly?.GetFirstCustomAttribute()?.Version; + return assembly?.GetFirstCustomAttribute()?.Version ?? string.Empty; case AssemblyVersionType.Informational: - return assembly?.GetFirstCustomAttribute()?.InformationalVersion; + return assembly?.GetFirstCustomAttribute()?.InformationalVersion ?? string.Empty; default: - return assembly?.GetName().Version?.ToString(); + return assembly?.GetName()?.Version?.ToString() ?? string.Empty; } } catch (Exception ex) @@ -178,7 +168,7 @@ private string GetVersion() NLog.Common.InternalLogger.Warn(ex, "${assembly-version} - Failed to load assembly {0}", Name); if (ex.MustBeRethrown()) throw; - return null; + return string.Empty; } } @@ -189,11 +179,7 @@ protected virtual System.Reflection.Assembly GetAssembly() { if (string.IsNullOrEmpty(Name)) { -#if !NETSTANDARD1_3 return System.Reflection.Assembly.GetEntryAssembly(); -#else - return null; -#endif } else { diff --git a/src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs b/src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs index 1928ef5404..37d5dfbf2c 100644 --- a/src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs @@ -52,26 +52,23 @@ namespace NLog.LayoutRenderers [ThreadAgnostic] public class BaseDirLayoutRenderer : LayoutRenderer { - private readonly string _baseDir; + private readonly string _appDomainDirectory; + private string _baseDir; -#if !NETSTANDARD1_3 - /// - /// cached - /// - private string _processDir; private readonly IAppEnvironment _appEnvironment; -#endif /// /// Use base dir of current process. Alternative one can just use ${processdir} /// + /// Default: /// public bool ProcessDir { get; set; } /// /// Fallback to the base dir of current process, when AppDomain.BaseDirectory is Temp-Path (.NET Core 3 - Single File Publish) /// - /// + /// Default: + /// public bool FixTempDir { get; set; } /// @@ -86,47 +83,53 @@ public BaseDirLayoutRenderer() : this(LogFactory.DefaultAppEnvironment) /// internal BaseDirLayoutRenderer(IAppEnvironment appEnvironment) { - _baseDir = appEnvironment.AppDomainBaseDirectory; -#if !NETSTANDARD1_3 + _baseDir = _appDomainDirectory = appEnvironment.AppDomainBaseDirectory; _appEnvironment = appEnvironment; -#endif } /// - /// Gets or sets the name of the file to be Path.Combine()'d with the base directory. + /// Gets or sets the name of the file to be Path.Combine()'d with the directory name. /// + /// Default: /// - public string File { get; set; } + public string File { get; set; } = string.Empty; /// - /// Gets or sets the name of the directory to be Path.Combine()'d with the base directory. + /// Gets or sets the name of the directory to be Path.Combine()'d with the directory name. /// + /// Default: /// - public string Dir { get; set; } + public string Dir { get; set; } = string.Empty; /// - protected override void Append(StringBuilder builder, LogEventInfo logEvent) + protected override void InitializeLayoutRenderer() { - var dir = _baseDir; -#if !NETSTANDARD1_3 + base.InitializeLayoutRenderer(); + + _baseDir = _appDomainDirectory; + if (ProcessDir) { - dir = _processDir ?? (_processDir = GetProcessDir()); + var processDir = GetProcessDir(); + if (!string.IsNullOrEmpty(processDir)) + _baseDir = processDir; } else if (FixTempDir) { - dir = _processDir ?? (_processDir = GetFixedTempBaseDir(_baseDir)); + var fixTempDir = GetFixedTempBaseDir(_appDomainDirectory); + if (!string.IsNullOrEmpty(fixTempDir)) + _baseDir = fixTempDir; } -#endif + _baseDir = AppEnvironmentWrapper.FixFilePathWithLongUNC(_baseDir); + _baseDir = PathHelpers.CombinePaths(_baseDir, Dir, File); + } - if (dir != null) - { - var path = PathHelpers.CombinePaths(dir, Dir, File); - builder.Append(path); - } + /// + protected override void Append(StringBuilder builder, LogEventInfo logEvent) + { + builder.Append(_baseDir); } -#if !NETSTANDARD1_3 private string GetFixedTempBaseDir(string baseDir) { try @@ -153,7 +156,6 @@ private string GetProcessDir() { return Path.GetDirectoryName(_appEnvironment.CurrentProcessFilePath); } -#endif } } diff --git a/src/NLog/LayoutRenderers/CallSiteFileNameLayoutRenderer.cs b/src/NLog/LayoutRenderers/CallSiteFileNameLayoutRenderer.cs index 8872b3c544..199dc29aa0 100644 --- a/src/NLog/LayoutRenderers/CallSiteFileNameLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/CallSiteFileNameLayoutRenderer.cs @@ -52,18 +52,21 @@ public class CallSiteFileNameLayoutRenderer : LayoutRenderer, IUsesStackTrace, I /// /// Gets or sets a value indicating whether to include source file path. /// + /// Default: /// public bool IncludeSourcePath { get; set; } = true; /// /// Gets or sets the number of frames to skip. /// + /// Default: /// public int SkipFrames { get; set; } /// /// Logger should capture StackTrace, if it was not provided manually /// + /// Default: /// public bool CaptureStackTrace { get; set; } = true; diff --git a/src/NLog/LayoutRenderers/CallSiteLayoutRenderer.cs b/src/NLog/LayoutRenderers/CallSiteLayoutRenderer.cs index 621c41dc87..1bd740126d 100644 --- a/src/NLog/LayoutRenderers/CallSiteLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/CallSiteLayoutRenderer.cs @@ -52,24 +52,28 @@ public class CallSiteLayoutRenderer : LayoutRenderer, IUsesStackTrace /// /// Gets or sets a value indicating whether to render the class name. /// + /// Default: /// public bool ClassName { get; set; } = true; /// /// Gets or sets a value indicating whether to render the include the namespace with . /// + /// Default: /// public bool IncludeNamespace { get; set; } = true; /// /// Gets or sets a value indicating whether to render the method name. /// + /// Default: /// public bool MethodName { get; set; } = true; /// /// Gets or sets a value indicating whether the method name will be cleaned up if it is detected as an anonymous delegate. /// + /// Default: /// public bool CleanNamesOfAnonymousDelegates { get; set; } = true; @@ -77,30 +81,35 @@ public class CallSiteLayoutRenderer : LayoutRenderer, IUsesStackTrace /// Gets or sets a value indicating whether the method and class names will be cleaned up if it is detected as an async continuation /// (everything after an await-statement inside of an async method). /// + /// Default: /// public bool CleanNamesOfAsyncContinuations { get; set; } = true; /// /// Gets or sets the number of frames to skip. /// + /// Default: /// public int SkipFrames { get; set; } /// /// Gets or sets a value indicating whether to render the source file name and line number. /// + /// Default: /// public bool FileName { get; set; } /// /// Gets or sets a value indicating whether to include source file path. /// + /// Default: /// public bool IncludeSourcePath { get; set; } = true; /// /// Logger should capture StackTrace, if it was not provided manually /// + /// Default: /// public bool CaptureStackTrace { get; set; } = true; @@ -132,13 +141,13 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) if (ClassName) { - string className = logEventCallSize.GetCallerClassName(method, IncludeNamespace, CleanNamesOfAsyncContinuations, CleanNamesOfAnonymousDelegates); + var className = logEventCallSize.GetCallerClassName(method, IncludeNamespace, CleanNamesOfAsyncContinuations, CleanNamesOfAnonymousDelegates); builder.Append(string.IsNullOrEmpty(className) ? "" : className); } if (MethodName) { - string methodName = logEventCallSize.GetCallerMethodName(method, false, CleanNamesOfAsyncContinuations, CleanNamesOfAnonymousDelegates); + var methodName = logEventCallSize.GetCallerMethodName(method, false, CleanNamesOfAsyncContinuations, CleanNamesOfAnonymousDelegates); if (ClassName) { builder.Append('.'); @@ -178,13 +187,12 @@ private void AppendFileName(StringBuilder builder, string fileName, int lineNumb [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow callsite logic", "IL2026")] private void AppendExceptionCallSite(StringBuilder builder, LogEventInfo logEvent) { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 var targetSite = logEvent?.Exception?.TargetSite; if (targetSite != null) { if (ClassName) { - var className = StackTraceUsageUtils.GetStackFrameMethodClassName(targetSite, true, CleanNamesOfAsyncContinuations, CleanNamesOfAnonymousDelegates) ?? string.Empty; + var className = StackTraceUsageUtils.GetStackFrameMethodClassName(targetSite, true, CleanNamesOfAsyncContinuations, CleanNamesOfAnonymousDelegates); builder.Append(className); } @@ -194,12 +202,11 @@ private void AppendExceptionCallSite(StringBuilder builder, LogEventInfo logEven { builder.Append('.'); } - var methodName = StackTraceUsageUtils.GetStackFrameMethodName(targetSite, false, CleanNamesOfAsyncContinuations, CleanNamesOfAnonymousDelegates) ?? string.Empty; + var methodName = StackTraceUsageUtils.GetStackFrameMethodName(targetSite, false, CleanNamesOfAsyncContinuations, CleanNamesOfAnonymousDelegates); builder.Append(methodName); } } else -#endif { if (ClassName || FileName) { diff --git a/src/NLog/LayoutRenderers/CallSiteLineNumberLayoutRenderer.cs b/src/NLog/LayoutRenderers/CallSiteLineNumberLayoutRenderer.cs index bbded1ad8e..7cb321d173 100644 --- a/src/NLog/LayoutRenderers/CallSiteLineNumberLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/CallSiteLineNumberLayoutRenderer.cs @@ -51,12 +51,14 @@ public class CallSiteLineNumberLayoutRenderer : LayoutRenderer, IUsesStackTrace, /// /// Gets or sets the number of frames to skip. /// + /// Default: /// public int SkipFrames { get; set; } /// /// Logger should capture StackTrace, if it was not provided manually /// + /// Default: /// public bool CaptureStackTrace { get; set; } = true; @@ -71,7 +73,7 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) builder.AppendInvariant(lineNumber.Value); } - bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) + bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object? value) { value = GetLineNumber(logEvent); return true; diff --git a/src/NLog/LayoutRenderers/CounterLayoutRenderer.cs b/src/NLog/LayoutRenderers/CounterLayoutRenderer.cs index c8ada25bd9..2900ee8146 100644 --- a/src/NLog/LayoutRenderers/CounterLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/CounterLayoutRenderer.cs @@ -34,8 +34,8 @@ namespace NLog.LayoutRenderers { using System; - using System.Collections.Generic; using System.Text; + using System.Threading; using NLog.Config; using NLog.Internal; using NLog.Layouts; @@ -51,67 +51,103 @@ namespace NLog.LayoutRenderers [ThreadAgnostic] public class CounterLayoutRenderer : LayoutRenderer, IRawValue { - private static Dictionary sequences = new Dictionary(StringComparer.Ordinal); + private sealed class GlobalSequence + { + private long _value; + public string Name { get; } + + public GlobalSequence(string sequenceName, long initialValue) + { + Name = sequenceName; + _value = initialValue; + } + + public long NextValue(int increment) => Interlocked.Add(ref _value, increment); + }; +#if NET35 + private static readonly System.Collections.Generic.Dictionary Sequences = new System.Collections.Generic.Dictionary(StringComparer.Ordinal); +#else + private static readonly System.Collections.Concurrent.ConcurrentDictionary Sequences = new System.Collections.Concurrent.ConcurrentDictionary(StringComparer.Ordinal); +#endif + private static GlobalSequence? _firstSequence; /// /// Gets or sets the initial value of the counter. /// + /// Default: /// - public long Value { get; set; } = 1; + public long Value { get => _value; set => _value = value; } + private long _value; /// - /// Gets or sets the value to be added to the counter after each layout rendering. + /// Gets or sets the value for incrementing the counter for every layout rendering. /// + /// Default: /// public int Increment { get; set; } = 1; /// /// Gets or sets the name of the sequence. Different named sequences can have individual values. /// + /// Default: /// - public Layout Sequence { get; set; } + public Layout? Sequence { get; set; } /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { var v = GetNextValue(logEvent); +#if NETFRAMEWORK if (v < int.MaxValue && v > int.MinValue) builder.AppendInvariant((int)v); else +#endif builder.Append(v); } private long GetNextValue(LogEventInfo logEvent) { - long v; - if (Sequence is null) { - v = Value; - Value += Increment; - } - else - { - v = GetNextSequenceValue(Sequence.Render(logEvent), Value, Increment); + return Interlocked.Add(ref _value, Increment); } - return v; + var sequenceName = Sequence.Render(logEvent); + return GetNextGlobalValue(sequenceName); } - private static long GetNextSequenceValue(string sequenceName, long defaultValue, int increment) + private long GetNextGlobalValue(string sequenceName) { - lock (sequences) + var globalSequence = _firstSequence; + if (globalSequence is null) { - if (!sequences.TryGetValue(sequenceName, out var val)) + globalSequence = new GlobalSequence(sequenceName, Value); + Interlocked.CompareExchange(ref _firstSequence, globalSequence, null); + globalSequence = _firstSequence; + } + if (globalSequence.Name.Equals(sequenceName)) + return globalSequence.NextValue(Increment); + +#if NET35 + lock (Sequences) + { + if (!Sequences.TryGetValue(sequenceName, out globalSequence)) { - val = defaultValue; + globalSequence = new GlobalSequence(sequenceName, Value); + Sequences[sequenceName] = globalSequence; + } + } +#else + if (!Sequences.TryGetValue(sequenceName, out globalSequence)) + { + globalSequence = new GlobalSequence(sequenceName, Value); + if (!Sequences.TryAdd(sequenceName, globalSequence)) + { + Sequences.TryGetValue(sequenceName, out globalSequence); } - - var retVal = val; - val += increment; - sequences[sequenceName] = val; - return retVal; } +#endif + return globalSequence.NextValue(Increment); } bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) diff --git a/src/NLog/LayoutRenderers/CurrentDirLayoutRenderer.cs b/src/NLog/LayoutRenderers/CurrentDirLayoutRenderer.cs index e009d26f55..69330a784d 100644 --- a/src/NLog/LayoutRenderers/CurrentDirLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/CurrentDirLayoutRenderer.cs @@ -50,16 +50,18 @@ namespace NLog.LayoutRenderers public class CurrentDirLayoutRenderer : LayoutRenderer, IStringValueRenderer { /// - /// Gets or sets the name of the file to be Path.Combine()'d with the current directory. + /// Gets or sets the name of the file to be Path.Combine()'d with the directory name. /// + /// Default: /// - public string File { get; set; } + public string File { get; set; } = string.Empty; /// - /// Gets or sets the name of the directory to be Path.Combine()'d with the current directory. + /// Gets or sets the name of the directory to be Path.Combine()'d with the directory name. /// + /// Default: /// - public string Dir { get; set; } + public string Dir { get; set; } = string.Empty; /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) diff --git a/src/NLog/LayoutRenderers/DateLayoutRenderer.cs b/src/NLog/LayoutRenderers/DateLayoutRenderer.cs index d286cedc2d..c25a95d334 100644 --- a/src/NLog/LayoutRenderers/DateLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/DateLayoutRenderer.cs @@ -55,18 +55,20 @@ public class DateLayoutRenderer : LayoutRenderer, IRawValue, IStringValueRendere /// public DateLayoutRenderer() { - Format = "yyyy/MM/dd HH:mm:ss.fff"; + _format = Format = "yyyy/MM/dd HH:mm:ss.fff"; } /// /// Gets or sets the culture used for rendering. /// + /// Default: /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; /// /// Gets or sets the date format. Can be any argument accepted by DateTime.ToString(format). /// + /// Default: yyyy/MM/dd HH:mm:ss.fff /// [DefaultParameter] public string Format @@ -85,12 +87,13 @@ public string Format private string _format; private const string _lowTimeResolutionChars = "YyMDdHh"; - private CachedDateFormatted _cachedDateFormatted = null; + private CachedDateFormatted? _cachedDateFormatted = null; private bool _utcRoundRoundTrip; /// /// Gets or sets a value indicating whether to output UTC time instead of local time. /// + /// Default: /// public bool UniversalTime { @@ -126,7 +129,7 @@ bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue(GetValue(logEvent), GetFormatProvider(logEvent, Culture)); - private string GetStringValue(DateTime timestamp, IFormatProvider formatProvider) + private string GetStringValue(DateTime timestamp, IFormatProvider? formatProvider) { var cachedDateFormatted = _cachedDateFormatted; if (!ReferenceEquals(formatProvider, CultureInfo.InvariantCulture)) diff --git a/src/NLog/LayoutRenderers/EnvironmentLayoutRenderer.cs b/src/NLog/LayoutRenderers/EnvironmentLayoutRenderer.cs index ca9c54b639..5f53e150b2 100644 --- a/src/NLog/LayoutRenderers/EnvironmentLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/EnvironmentLayoutRenderer.cs @@ -52,26 +52,36 @@ public class EnvironmentLayoutRenderer : LayoutRenderer, IStringValueRenderer /// /// Gets or sets the name of the environment variable. /// + /// [Required] Default: /// - [RequiredParameter] [DefaultParameter] - public string Variable { get; set; } + public string Variable { get; set; } = string.Empty; /// /// Gets or sets the default value to be used when the environment variable is not set. /// + /// Default: /// - public string Default { get; set; } + public string Default { get; set; } = string.Empty; private System.Collections.Generic.KeyValuePair _cachedValue; + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + + if (string.IsNullOrEmpty(Variable)) + throw new NLogConfigurationException("Environment-LayoutRenderer Variable-property must be assigned. Lookup blank value not supported."); + } + /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { GetSimpleLayout()?.Render(logEvent, builder); } - string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) { var simpleLayout = GetSimpleLayout(); if (simpleLayout is null) @@ -81,7 +91,7 @@ string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) return null; } - private SimpleLayout GetSimpleLayout() + private SimpleLayout? GetSimpleLayout() { if (Variable != null) { diff --git a/src/NLog/LayoutRenderers/EnvironmentUserLayoutRenderer.cs b/src/NLog/LayoutRenderers/EnvironmentUserLayoutRenderer.cs index d11c4e27dd..a4e617ac6e 100644 --- a/src/NLog/LayoutRenderers/EnvironmentUserLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/EnvironmentUserLayoutRenderer.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.LayoutRenderers { using System; @@ -53,18 +51,21 @@ public class EnvironmentUserLayoutRenderer : LayoutRenderer, IStringValueRendere /// /// Gets or sets a value indicating whether username should be included. /// + /// Default: /// public bool UserName { get; set; } = true; /// /// Gets or sets a value indicating whether domain name should be included. /// + /// Default: /// public bool Domain { get; set; } /// /// Gets or sets the default value to be used when the User is not set. /// + /// Default: UserUnknown /// public string DefaultUser { get; set; } = "UserUnknown"; @@ -72,6 +73,7 @@ public class EnvironmentUserLayoutRenderer : LayoutRenderer, IStringValueRendere /// Gets or sets the default value to be used when the Domain is not set. /// /// + /// Default: DomainUnknown public string DefaultDomain { get; set; } = "DomainUnknown"; /// @@ -104,7 +106,7 @@ string GetDomainName() return GetValueSafe(() => Environment.UserDomainName, DefaultDomain); } - private string GetValueSafe(Func getValue, string defaultValue) + private static string GetValueSafe(Func getValue, string defaultValue) { try { @@ -119,5 +121,3 @@ private string GetValueSafe(Func getValue, string defaultValue) } } } - -#endif diff --git a/src/NLog/LayoutRenderers/EventPropertiesLayoutRenderer.cs b/src/NLog/LayoutRenderers/EventPropertiesLayoutRenderer.cs index 78c58679d4..01bc1cc648 100644 --- a/src/NLog/LayoutRenderers/EventPropertiesLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/EventPropertiesLayoutRenderer.cs @@ -53,43 +53,48 @@ namespace NLog.LayoutRenderers public class EventPropertiesLayoutRenderer : LayoutRenderer, IRawValue, IStringValueRenderer { private ObjectReflectionCache ObjectReflectionCache => _objectReflectionCache ?? (_objectReflectionCache = new ObjectReflectionCache(LoggingConfiguration.GetServiceProvider())); - private ObjectReflectionCache _objectReflectionCache; + private ObjectReflectionCache? _objectReflectionCache; private ObjectPropertyPath _objectPropertyPath; /// /// Gets or sets the name of the item. /// + /// [Required] Default: /// - [RequiredParameter] [DefaultParameter] - public string Item { get => _item?.ToString(); set => _item = (value != null && IgnoreCase) ? new PropertiesDictionary.IgnoreCasePropertyKey(value) : (object)value; } - private object _item; + public string Item { get => _item?.ToString() ?? string.Empty; set => _item = (value is null || !IgnoreCase) ? (value ?? string.Empty) : new PropertiesDictionary.IgnoreCasePropertyKey(value); } + private object _item = string.Empty; /// /// Format string for conversion from object to string. /// + /// Default: /// - public string Format { get; set; } + public string? Format { get; set; } /// /// Gets or sets the culture used for rendering. /// + /// Default: /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; /// /// Gets or sets the object-property-navigation-path for lookup of nested property /// + /// Default: /// public string ObjectPath { - get => _objectPropertyPath.Value; + get => _objectPropertyPath.Value ?? string.Empty; set => _objectPropertyPath.Value = value; } /// /// Gets or sets whether to perform case-sensitive property-name lookup /// + /// Default: + /// public bool IgnoreCase { get => _ignoreCase; @@ -98,31 +103,51 @@ public bool IgnoreCase if (value != _ignoreCase) { _ignoreCase = value; - Item = _item?.ToString(); + Item = _item?.ToString() ?? string.Empty; } } } private bool _ignoreCase = true; + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + + if (StringHelpers.IsNullOrEmptyString(_item)) + throw new NLogConfigurationException("EventProperty-LayoutRenderer Item-property must be assigned. Lookup blank value not supported."); + } + /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { if (TryGetValue(logEvent, out var value)) { - var formatProvider = GetFormatProvider(logEvent, Culture); - builder.AppendFormattedValue(value, Format, formatProvider, ValueFormatter); + AppendFormattedValue(builder, logEvent, value, Format, Culture); } } - bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) + bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object? value) { TryGetValue(logEvent, out value); return true; } - string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue(logEvent); + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) + { + if (!MessageTemplates.ValueFormatter.FormatAsJson.Equals(Format)) + { + if (TryGetValue(logEvent, out var value)) + { + string stringValue = FormatHelper.TryFormatToString(value, Format, GetFormatProvider(logEvent, Culture)); + return stringValue; + } + return string.Empty; + } + return null; + } - private bool TryGetValue(LogEventInfo logEvent, out object value) + private bool TryGetValue(LogEventInfo logEvent, out object? value) { value = null; @@ -146,19 +171,5 @@ private bool TryGetValue(LogEventInfo logEvent, out object value) return true; } - - private string GetStringValue(LogEventInfo logEvent) - { - if (Format != MessageTemplates.ValueFormatter.FormatAsJson) - { - if (TryGetValue(logEvent, out var value)) - { - string stringValue = FormatHelper.TryFormatToString(value, Format, GetFormatProvider(logEvent, Culture)); - return stringValue; - } - return string.Empty; - } - return null; - } } } diff --git a/src/NLog/LayoutRenderers/ExceptionDataLayoutRenderer.cs b/src/NLog/LayoutRenderers/ExceptionDataLayoutRenderer.cs index 06204d6770..97ab39b9aa 100644 --- a/src/NLog/LayoutRenderers/ExceptionDataLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ExceptionDataLayoutRenderer.cs @@ -37,7 +37,6 @@ namespace NLog.LayoutRenderers using System.Globalization; using System.Text; using NLog.Config; - using NLog.Internal; /// /// Render information of @@ -56,30 +55,42 @@ public class ExceptionDataLayoutRenderer : LayoutRenderer /// /// Gets or sets the key to search the exception Data for /// + /// [Required] Default: /// [DefaultParameter] - [RequiredParameter] - public string Item { get; set; } + public string Item { get; set; } = string.Empty; /// /// Gets or sets whether to render innermost Exception from /// + /// Default: /// public bool BaseException { get; set; } /// /// Format string for conversion from object to string. /// + /// Default: /// - public string Format { get; set; } + public string? Format { get; set; } /// /// Gets or sets the culture used for rendering. /// + /// Default: /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; - private Exception GetTopException(LogEventInfo logEvent) + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + + if (string.IsNullOrEmpty(Item)) + throw new NLogConfigurationException("ExceptionData-LayoutRenderer Item-property must be assigned. Lookup blank value not supported."); + } + + private Exception? GetTopException(LogEventInfo logEvent) { return BaseException ? logEvent.Exception?.GetBaseException() : logEvent.Exception; } @@ -87,14 +98,13 @@ private Exception GetTopException(LogEventInfo logEvent) /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { - Exception primaryException = GetTopException(logEvent); + var primaryException = GetTopException(logEvent); if (primaryException != null) { var value = primaryException.Data[Item]; if (value != null) { - var formatProvider = GetFormatProvider(logEvent, Culture); - builder.AppendFormattedValue(value, Format, formatProvider, ValueFormatter); + AppendFormattedValue(builder, logEvent, value, Format, Culture); } } } diff --git a/src/NLog/LayoutRenderers/ExceptionLayoutRenderer.cs b/src/NLog/LayoutRenderers/ExceptionLayoutRenderer.cs index 565016f038..bb8503c126 100644 --- a/src/NLog/LayoutRenderers/ExceptionLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ExceptionLayoutRenderer.cs @@ -52,9 +52,6 @@ namespace NLog.LayoutRenderers [ThreadAgnostic] public class ExceptionLayoutRenderer : LayoutRenderer, IRawValue { - private string _format; - private string _innerFormat = string.Empty; - private static readonly Dictionary _formatsMapping = new Dictionary(StringComparer.OrdinalIgnoreCase) { {"MESSAGE",ExceptionRenderingFormat.Message}, @@ -71,10 +68,10 @@ public class ExceptionLayoutRenderer : LayoutRenderer, IRawValue {"PROPERTIES",ExceptionRenderingFormat.Properties}, }; - private static readonly Dictionary> _renderingfunctions = new Dictionary>() + private static readonly Dictionary> _renderingfunctions = new Dictionary>() { - {ExceptionRenderingFormat.Message, (layout, sb, ex, aggex) => layout.AppendMessage(sb, ex)}, - {ExceptionRenderingFormat.Type, (layout, sb, ex, aggex) => layout.AppendType(sb, ex)}, + { ExceptionRenderingFormat.Message, (layout, sb, ex, aggex) => layout.AppendMessage(sb, ex)}, + { ExceptionRenderingFormat.Type, (layout, sb, ex, aggex) => layout.AppendType(sb, ex)}, { ExceptionRenderingFormat.ShortType, (layout, sb, ex, aggex) => layout.AppendShortType(sb, ex)}, { ExceptionRenderingFormat.ToString, (layout, sb, ex, aggex) => layout.AppendToString(sb, ex)}, { ExceptionRenderingFormat.Method, (layout, sb, ex, aggex) => layout.AppendMethod(sb, ex)}, @@ -95,87 +92,113 @@ public class ExceptionLayoutRenderer : LayoutRenderer, IRawValue nameof(Exception.Message), nameof(Exception.Source), nameof(Exception.StackTrace), - "TargetSite",// Not available on NETSTANDARD1_3 OR NETSTANDARD1_5 + nameof(Exception.TargetSite), }, StringComparer.Ordinal); private ObjectReflectionCache ObjectReflectionCache => _objectReflectionCache ?? (_objectReflectionCache = new ObjectReflectionCache(LoggingConfiguration.GetServiceProvider())); - private ObjectReflectionCache _objectReflectionCache; + private ObjectReflectionCache? _objectReflectionCache; + + private List _formats = new List(); + private List? _innerFormats; /// /// Initializes a new instance of the class. /// public ExceptionLayoutRenderer() { - Format = "TOSTRING,DATA"; - } + _format = "TOSTRING,DATA"; + } /// /// Gets or sets the format of the output. Must be a comma-separated list of exception /// properties: Message, Type, ShortType, ToString, Method, StackTrace. /// This parameter value is case-insensitive. /// - /// - /// + /// [Required] Default: ToString,Data /// [DefaultParameter] public string Format { get => _format; - set { _format = value; - Formats = CompileFormat(value, nameof(Format)); + _formats.Clear(); } } + private string _format; /// /// Gets or sets the format of the output of inner exceptions. Must be a comma-separated list of exception /// properties: Message, Type, ShortType, ToString, Method, StackTrace. /// This parameter value is case-insensitive. /// + /// Default: /// - public string InnerFormat + public string? InnerFormat { get => _innerFormat; - set { _innerFormat = value; - InnerFormats = CompileFormat(value, nameof(InnerFormat)); + _innerFormats = null; } } + private string? _innerFormat; /// /// Gets or sets the separator used to concatenate parts specified in the Format. /// + /// Default: /// - public string Separator { get => _seperator; set => _seperator = new NLog.Layouts.SimpleLayout(value).Render(LogEventInfo.CreateNullEvent()); } - private string _seperator = " "; + public string Separator + { + get => _separatorOriginal ?? _separator; + set + { + _separatorOriginal = value; + _separator = Layouts.SimpleLayout.Evaluate(value, LoggingConfiguration, throwConfigExceptions: false); + } + } + private string _separator = " "; + private string _separatorOriginal = " "; /// /// Gets or sets the separator used to concatenate exception data specified in the Format. /// + /// Default: ; /// - public string ExceptionDataSeparator { get => _exceptionDataSeparator; set => _exceptionDataSeparator = new NLog.Layouts.SimpleLayout(value).Render(LogEventInfo.CreateNullEvent()); } + public string ExceptionDataSeparator + { + get => _exceptionDataSeparatorOriginal ?? _exceptionDataSeparator; + set + { + _exceptionDataSeparatorOriginal = value; + _exceptionDataSeparator = Layouts.SimpleLayout.Evaluate(value, LoggingConfiguration, throwConfigExceptions: false); + } + } private string _exceptionDataSeparator = ";"; + private string _exceptionDataSeparatorOriginal = ";"; /// /// Gets or sets the maximum number of inner exceptions to include in the output. /// By default inner exceptions are not enabled for compatibility with NLog 1.0. /// + /// Default: /// public int MaxInnerExceptionLevel { get; set; } /// /// Gets or sets the separator between inner exceptions. /// + /// Default: /// - public string InnerExceptionSeparator { get; set; } = EnvironmentHelper.NewLine; + public string InnerExceptionSeparator { get; set; } = Environment.NewLine; /// /// Gets or sets whether to render innermost Exception from /// + /// Default: /// public bool BaseException { get; set; } @@ -183,11 +206,13 @@ public string InnerFormat /// /// Gets or sets whether to collapse exception tree using /// + /// Default: /// #else /// /// Gets or sets whether to collapse exception tree using AggregateException.Flatten() /// + /// Default: /// #endif public bool FlattenException { get; set; } = true; @@ -196,37 +221,37 @@ public string InnerFormat /// Gets the formats of the output of inner exceptions to be rendered in target. /// /// - public List Formats - { - get; - private set; - } + public IEnumerable Formats => _formats; - /// - /// Gets the formats of the output to be rendered in target. - /// - /// - public List InnerFormats - { - get; - private set; - } - - bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) + bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object? value) { value = GetTopException(logEvent); return true; } - private Exception GetTopException(LogEventInfo logEvent) + private Exception? GetTopException(LogEventInfo logEvent) { return BaseException ? logEvent.Exception?.GetBaseException() : logEvent.Exception; } + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + + _formats = CompileFormat(Format, nameof(Format)); + _innerFormats = InnerFormat is null ? null : CompileFormat(InnerFormat, nameof(InnerFormat)); + + if (_separatorOriginal != null) + _separator = Layouts.SimpleLayout.Evaluate(_separatorOriginal, LoggingConfiguration); + if (_exceptionDataSeparatorOriginal != null) + _exceptionDataSeparator = Layouts.SimpleLayout.Evaluate(_exceptionDataSeparatorOriginal, LoggingConfiguration); + } + /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { - Exception primaryException = GetTopException(logEvent); + var primaryException = GetTopException(logEvent); if (primaryException != null) { int currentLevel = 0; @@ -235,7 +260,7 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) if (logEvent.Exception is AggregateException aggregateException) { primaryException = FlattenException ? GetPrimaryException(aggregateException) : aggregateException; - AppendException(primaryException, Formats, builder, aggregateException); + AppendException(primaryException, _formats, builder, aggregateException); if (currentLevel < MaxInnerExceptionLevel) { currentLevel = AppendInnerExceptionTree(primaryException, currentLevel, builder); @@ -248,7 +273,7 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) else #endif { - AppendException(primaryException, Formats, builder); + AppendException(primaryException, _formats, builder); if (currentLevel < MaxInnerExceptionLevel) { AppendInnerExceptionTree(primaryException, currentLevel, builder); @@ -319,10 +344,10 @@ private void AppendInnerException(Exception currentException, StringBuilder buil { // separate inner exceptions builder.Append(InnerExceptionSeparator); - AppendException(currentException, InnerFormats ?? Formats, builder); + AppendException(currentException, _innerFormats ?? _formats, builder); } - private void AppendException(Exception currentException, List renderFormats, StringBuilder builder, Exception aggregateException = null) + private void AppendException(Exception currentException, List renderFormats, StringBuilder builder, Exception? aggregateException = null) { int currentLength = builder.Length; foreach (ExceptionRenderingFormat renderingFormat in renderFormats) @@ -335,7 +360,7 @@ private void AppendException(Exception currentException, List @@ -385,7 +415,17 @@ protected virtual void AppendMethod(StringBuilder sb, Exception ex) /// The Exception whose stack trace should be appended. protected virtual void AppendStackTrace(StringBuilder sb, Exception ex) { - sb.Append(ex.StackTrace); + try + { + sb.Append(ex.StackTrace); + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "Exception-LayoutRenderer Could not output StackTrace for Exception: {0}", ex.GetType()); + sb.Append(ex.GetType().ToString()); + sb.Append(" StackTrace-property threw "); + sb.Append(exception.GetType().ToString()); + } } /// @@ -395,20 +435,25 @@ protected virtual void AppendStackTrace(StringBuilder sb, Exception ex) /// The Exception whose call to ToString() should be appended. protected virtual void AppendToString(StringBuilder sb, Exception ex) { + string exceptionMessage = string.Empty; + Exception? innerException = null; try { + exceptionMessage = ex.Message; + innerException = ex.InnerException; sb.Append(ex.ToString()); } catch (Exception exception) { - var message = - $"Exception in {typeof(ExceptionLayoutRenderer).FullName}.AppendToString(): {exception.GetType().FullName}."; - sb.Append("NLog message: "); - sb.Append(message); - InternalLogger.Warn(exception, message); + InternalLogger.Warn(exception, "Exception-LayoutRenderer Could not output ToString for Exception: {0}", ex.GetType()); + sb.Append($"{ex.GetType()}: {exceptionMessage}"); + if (innerException != null) + { + sb.AppendLine(); + AppendToString(sb, innerException); + } } - } /// @@ -418,7 +463,7 @@ protected virtual void AppendToString(StringBuilder sb, Exception ex) /// The Exception whose type should be appended. protected virtual void AppendType(StringBuilder sb, Exception ex) { - sb.Append(ex.GetType().FullName); + sb.Append(ex.GetType().ToString()); } /// @@ -438,7 +483,17 @@ protected virtual void AppendShortType(StringBuilder sb, Exception ex) /// The Exception whose source should be appended. protected virtual void AppendSource(StringBuilder sb, Exception ex) { - sb.Append(ex.Source); + try + { + sb.Append(ex.Source); + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "Exception-LayoutRenderer Could not output Source for Exception: {0}", ex.GetType()); + sb.Append(ex.GetType().ToString()); + sb.Append(" Source-property threw "); + sb.Append(exception.GetType().ToString()); + } } /// @@ -458,12 +513,12 @@ protected virtual void AppendHResult(StringBuilder sb, Exception ex) #endif } - private void AppendData(StringBuilder builder, Exception ex, Exception aggregateException) + private void AppendData(StringBuilder builder, Exception ex, Exception? aggregateException) { if (aggregateException?.Data?.Count > 0 && !ReferenceEquals(ex, aggregateException)) { AppendData(builder, aggregateException); - builder.Append(Separator); + builder.Append(_separator); } AppendData(builder, ex); } @@ -481,8 +536,16 @@ protected virtual void AppendData(StringBuilder sb, Exception ex) foreach (var key in ex.Data.Keys) { sb.Append(separator); - sb.AppendFormat("{0}: {1}", key, ex.Data[key]); - separator = ExceptionDataSeparator; + try + { + sb.AppendFormat("{0}: ", key); + separator = _exceptionDataSeparator; + sb.AppendFormat("{0}", ex.Data[key]); + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "Exception-LayoutRenderer Could not output Data-collection for Exception: {0}", ex.GetType()); + } } } } @@ -511,20 +574,29 @@ protected virtual void AppendProperties(StringBuilder sb, Exception ex) if (ExcludeDefaultProperties.Contains(property.Name)) continue; - var propertyValue = property.Value?.ToString(); - if (string.IsNullOrEmpty(propertyValue)) - continue; + try + { + var propertyValue = property.Value?.ToString(); + if (string.IsNullOrEmpty(propertyValue)) + continue; - sb.Append(separator); - sb.AppendFormat("{0}: {1}", property.Name, propertyValue); - separator = ExceptionDataSeparator; + sb.Append(separator); + sb.Append(property.Name); + separator = _exceptionDataSeparator; + sb.Append(": "); + sb.AppendFormat("{0}", propertyValue); + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "Exception-LayoutRenderer Could not output Property-collection for Exception: {0}", ex.GetType()); + } } } /// /// Split the string and then compile into list of Rendering formats. /// - private static List CompileFormat(string formatSpecifier, string propertyName) + private List CompileFormat(string formatSpecifier, string propertyName) { List formats = new List(); string[] parts = formatSpecifier.SplitAndTrimTokens(','); @@ -538,68 +610,12 @@ private static List CompileFormat(string formatSpecifi } else { - InternalLogger.Warn("Exception-LayoutRenderer assigned unknown {0}: {1}", propertyName, s); // TODO Delay parsing to Initialize and check ThrowConfigExceptions + NLogConfigurationException ex = new NLogConfigurationException($"Exception-LayoutRenderer assigned unknown {propertyName}: {s}"); + if (ex.MustBeRethrown() || (LoggingConfiguration?.LogFactory?.ThrowConfigExceptions ?? LoggingConfiguration?.LogFactory?.ThrowExceptions) == true) + throw ex; } } return formats; } - -#if NETSTANDARD1_3 || NETSTANDARD1_5 - /// - /// Find name of method on stracktrace. - /// - /// Full stracktrace - /// - protected static string ParseMethodNameFromStackTrace(string stackTrace) - { - if (string.IsNullOrEmpty(stackTrace)) - return string.Empty; - - // get the first line of the stack trace - string stackFrameLine; - - int p = stackTrace.IndexOfAny(new[] { '\r', '\n' }); - if (p >= 0) - { - stackFrameLine = stackTrace.Substring(0, p); - } - else - { - stackFrameLine = stackTrace; - } - - // stack trace is composed of lines which look like this - // - // at NLog.UnitTests.LayoutRenderers.ExceptionTests.GenericClass`3.Method2[T1,T2,T3](T1 aaa, T2 b, T3 o, Int32 i, DateTime now, Nullable`1 gfff, List`1[] something) - // - // "at " prefix can be localized so we cannot hard-code it but it's followed by a space, class name (which does not have a space in it) and opening parenthesis - int lastSpace = -1; - int startPos = 0; - int endPos = stackFrameLine.Length; - - for (int i = 0; i < stackFrameLine.Length; ++i) - { - switch (stackFrameLine[i]) - { - case ' ': - lastSpace = i; - break; - - case '(': - startPos = lastSpace + 1; - break; - - case ')': - endPos = i + 1; - - // end the loop - i = stackFrameLine.Length; - break; - } - } - - return stackTrace.Substring(startPos, endPos - startPos); - } -#endif } } diff --git a/src/NLog/LayoutRenderers/FileContentsLayoutRenderer.cs b/src/NLog/LayoutRenderers/FileContentsLayoutRenderer.cs deleted file mode 100644 index 857f8abd30..0000000000 --- a/src/NLog/LayoutRenderers/FileContentsLayoutRenderer.cs +++ /dev/null @@ -1,129 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.LayoutRenderers -{ - using System; - using System.IO; - using System.Text; - using NLog.Common; - using NLog.Config; - using NLog.Internal; - using NLog.Layouts; - - /// - /// Renders contents of the specified file. - /// - /// - /// See NLog Wiki - /// - /// Documentation on NLog Wiki - [LayoutRenderer("file-contents")] - public class FileContentsLayoutRenderer : LayoutRenderer - { - private readonly object _lockObject = new object(); - - private string _lastFileName; - private string _currentFileContents; - - /// - /// Initializes a new instance of the class. - /// - public FileContentsLayoutRenderer() - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - Encoding = Encoding.Default; -#else - Encoding = Encoding.UTF8; -#endif - _lastFileName = string.Empty; - } - - /// - /// Gets or sets the name of the file. - /// - /// - [DefaultParameter] - public Layout FileName { get; set; } - - /// - /// Gets or sets the encoding used in the file. - /// - /// The encoding. - /// - public Encoding Encoding { get; set; } - - /// - protected override void Append(StringBuilder builder, LogEventInfo logEvent) - { - lock (_lockObject) - { - string fileName = FileName.Render(logEvent); - - if (fileName != _lastFileName) - { - _currentFileContents = ReadFileContents(fileName); - _lastFileName = fileName; - } - } - - builder.Append(_currentFileContents); - } - - private string ReadFileContents(string fileName) - { - try - { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - using (var reader = new StreamReader(fileName, Encoding)) - { - return reader.ReadToEnd(); - } -#else - return File.ReadAllText(fileName, Encoding); -#endif - } - catch (Exception exception) - { - InternalLogger.Error(exception, "Cannot read file contents of '{0}'.", fileName); - - if (exception.MustBeRethrown()) - { - throw; - } - - return string.Empty; - } - } - } -} diff --git a/src/NLog/LayoutRenderers/FuncLayoutRenderer.cs b/src/NLog/LayoutRenderers/FuncLayoutRenderer.cs index f948c55ccd..cddf3e602e 100644 --- a/src/NLog/LayoutRenderers/FuncLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/FuncLayoutRenderer.cs @@ -44,7 +44,7 @@ namespace NLog.LayoutRenderers /// public class FuncLayoutRenderer : LayoutRenderer, IStringValueRenderer { - private readonly Func _renderMethod; + private readonly Func _renderMethod; /// /// Initializes a new instance of the class. @@ -53,6 +53,7 @@ public class FuncLayoutRenderer : LayoutRenderer, IStringValueRenderer protected FuncLayoutRenderer(string layoutRendererName) { LayoutRendererName = layoutRendererName; + _renderMethod = (evt, cfg) => string.Empty; } /// @@ -60,7 +61,7 @@ protected FuncLayoutRenderer(string layoutRendererName) /// /// Name without ${}. /// Method that renders the layout. - public FuncLayoutRenderer(string layoutRendererName, Func renderMethod) + public FuncLayoutRenderer(string layoutRendererName, Func renderMethod) { _renderMethod = Guard.ThrowIfNull(renderMethod); LayoutRendererName = layoutRendererName; @@ -69,24 +70,25 @@ public FuncLayoutRenderer(string layoutRendererName, Func /// Name used in config without ${}. E.g. "test" could be used as "${test}". /// - public string LayoutRendererName { get; set; } + public string LayoutRendererName { get; } /// /// Method that renders the layout. - /// - /// This public property will be removed in NLog 5. /// - public Func RenderMethod => _renderMethod; + [Obsolete("Public API-property was a mistake. Marked obsolete with NLog v6.0")] + public Func RenderMethod => _renderMethod; /// /// Format string for conversion from object to string. /// + /// Default: /// - public string Format { get; set; } + public string? Format { get; set; } /// /// Gets or sets the culture used for rendering. /// + /// Default: /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; @@ -94,18 +96,15 @@ public FuncLayoutRenderer(string layoutRendererName, Func GetStringValue(logEvent); - - private string GetStringValue(LogEventInfo logEvent) + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) { - if (Format != MessageTemplates.ValueFormatter.FormatAsJson) + if (!MessageTemplates.ValueFormatter.FormatAsJson.Equals(Format)) { - object value = RenderValue(logEvent); - string stringValue = FormatHelper.TryFormatToString(value, Format, GetFormatProvider(logEvent, null)); + var value = RenderValue(logEvent); + string stringValue = FormatHelper.TryFormatToString(value, Format, GetFormatProvider(logEvent, Culture)); return stringValue; } return null; @@ -116,9 +115,9 @@ private string GetStringValue(LogEventInfo logEvent) /// /// The logging event. /// The value. - protected virtual object RenderValue(LogEventInfo logEvent) + protected virtual object? RenderValue(LogEventInfo logEvent) { - return _renderMethod?.Invoke(logEvent, LoggingConfiguration); + return _renderMethod.Invoke(logEvent, LoggingConfiguration); } } } diff --git a/src/NLog/LayoutRenderers/FuncThreadAgnosticLayoutRenderer.cs b/src/NLog/LayoutRenderers/FuncThreadAgnosticLayoutRenderer.cs index 1bddc0d911..82a8b5b6e4 100644 --- a/src/NLog/LayoutRenderers/FuncThreadAgnosticLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/FuncThreadAgnosticLayoutRenderer.cs @@ -48,12 +48,12 @@ internal sealed class FuncThreadAgnosticLayoutRenderer : FuncLayoutRenderer, IRa /// /// Name without ${}. /// Method that renders the layout. - public FuncThreadAgnosticLayoutRenderer(string layoutRendererName, Func renderMethod) + public FuncThreadAgnosticLayoutRenderer(string layoutRendererName, Func renderMethod) : base(layoutRendererName, renderMethod) { } - bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) + bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object? value) { value = RenderValue(logEvent); return true; diff --git a/src/NLog/LayoutRenderers/GarbageCollectorInfoLayoutRenderer.cs b/src/NLog/LayoutRenderers/GarbageCollectorInfoLayoutRenderer.cs index 56f6375c29..58f2e4cc55 100644 --- a/src/NLog/LayoutRenderers/GarbageCollectorInfoLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/GarbageCollectorInfoLayoutRenderer.cs @@ -50,6 +50,7 @@ public class GarbageCollectorInfoLayoutRenderer : LayoutRenderer /// /// Gets or sets the property to retrieve. /// + /// Default: /// public GarbageCollectorProperty Property { get; set; } = GarbageCollectorProperty.TotalMemory; diff --git a/src/NLog/LayoutRenderers/GdcLayoutRenderer.cs b/src/NLog/LayoutRenderers/GdcLayoutRenderer.cs index d79c26b74a..f17342fdc1 100644 --- a/src/NLog/LayoutRenderers/GdcLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/GdcLayoutRenderer.cs @@ -49,59 +49,67 @@ namespace NLog.LayoutRenderers [ThreadAgnostic] public class GdcLayoutRenderer : LayoutRenderer, IRawValue, IStringValueRenderer { - private CachedLookup _cachedLookup = new CachedLookup(null, null); + private CachedLookup _cachedLookup = new CachedLookup(string.Empty, null); /// /// Gets or sets the name of the item. /// + /// [Required] Default: /// - [RequiredParameter] [DefaultParameter] - public string Item { get; set; } + public string Item { get; set; } = string.Empty; /// /// Format string for conversion from object to string. /// + /// Default: /// - public string Format { get; set; } + public string? Format { get; set; } /// /// Gets or sets the culture used for rendering. /// + /// Default: /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + + if (string.IsNullOrEmpty(Item)) + throw new NLogConfigurationException("Gdc-LayoutRenderer Item-property must be assigned. Lookup blank value not supported."); + } + /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { - object value = GetValue(); + var value = GetValue(); if (value != null || !string.IsNullOrEmpty(Format)) { - var formatProvider = GetFormatProvider(logEvent, Culture); - builder.AppendFormattedValue(value, Format, formatProvider, ValueFormatter); + AppendFormattedValue(builder, logEvent, value, Format, Culture); } } - bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) + bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object? value) { value = GetValue(); return true; } - string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue(logEvent); - - private string GetStringValue(LogEventInfo logEvent) + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) { - if (Format != MessageTemplates.ValueFormatter.FormatAsJson) + if (!MessageTemplates.ValueFormatter.FormatAsJson.Equals(Format)) { - object value = GetValue(); + var value = GetValue(); string stringValue = FormatHelper.TryFormatToString(value, Format, GetFormatProvider(logEvent, Culture)); return stringValue; } return null; } - private object GetValue() + private object? GetValue() { var cachedLookup = _cachedLookup; var cachedDictionary = GlobalDiagnosticsContext.GetReadOnlyDict(); @@ -118,9 +126,9 @@ private object GetValue() private sealed class CachedLookup { internal readonly object CachedDictionary; - internal readonly object CachedItemValue; + internal readonly object? CachedItemValue; - public CachedLookup(object cachedDictionary, object cachedItemValue) + public CachedLookup(object cachedDictionary, object? cachedItemValue) { CachedDictionary = cachedDictionary; CachedItemValue = cachedItemValue; diff --git a/src/NLog/LayoutRenderers/GuidLayoutRenderer.cs b/src/NLog/LayoutRenderers/GuidLayoutRenderer.cs index 572ce164c8..92d4abd117 100644 --- a/src/NLog/LayoutRenderers/GuidLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/GuidLayoutRenderer.cs @@ -52,12 +52,14 @@ public class GuidLayoutRenderer : LayoutRenderer, IRawValue, IStringValueRendere /// /// Gets or sets the GUID format as accepted by Guid.ToString() method. /// + /// Default: N /// public string Format { get; set; } = "N"; /// /// Generate the Guid from the NLog LogEvent (Will be the same for all targets) /// + /// Default: /// public bool GeneratedFromLogEvent { get; set; } @@ -97,7 +99,9 @@ private Guid GetValue(LogEventInfo logEvent) byte i = (byte)((zeroDateTicks >> 16) & 0xFF); byte j = (byte)((zeroDateTicks >> 8) & 0xFF); byte k = (byte)(zeroDateTicks & 0XFF); +#pragma warning disable CS0618 // Type or member is obsolete guid = new Guid(logEvent.SequenceID, b, c, d, e, f, g, h, i, j, k); +#pragma warning restore CS0618 // Type or member is obsolete } else { diff --git a/src/NLog/LayoutRenderers/HostNameLayoutRenderer.cs b/src/NLog/LayoutRenderers/HostNameLayoutRenderer.cs index d7301b9cd0..28caf85f2b 100644 --- a/src/NLog/LayoutRenderers/HostNameLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/HostNameLayoutRenderer.cs @@ -51,7 +51,7 @@ namespace NLog.LayoutRenderers [ThreadAgnostic] public class HostNameLayoutRenderer : LayoutRenderer { - private string _hostName; + private string? _hostName; /// protected override void InitializeLayoutRenderer() @@ -78,16 +78,15 @@ protected override void InitializeLayoutRenderer() } /// - /// Gets the host name and falls back to computer name if not available + /// Resolves the hostname from environment-variables with fallback to /// private static string GetHostName() { return TryLookupValue(() => Environment.GetEnvironmentVariable("HOSTNAME"), "HOSTNAME") - ?? TryLookupValue(() => System.Net.Dns.GetHostName(), "DnsHostName") -#if !NETSTANDARD1_3 + ?? TryLookupValue(() => Environment.GetEnvironmentVariable("COMPUTERNAME"), "COMPUTERNAME") + ?? TryLookupValue(() => Environment.GetEnvironmentVariable("MACHINENAME"), "MACHINENAME") ?? TryLookupValue(() => Environment.MachineName, "MachineName") -#endif - ?? TryLookupValue(() => Environment.GetEnvironmentVariable("MACHINENAME"), "MachineName"); + ?? string.Empty; } /// @@ -96,11 +95,11 @@ private static string GetHostName() /// The lookup function. /// Type of the lookup. /// - private static string TryLookupValue(Func lookupFunc, string lookupType) + private static string? TryLookupValue(Func lookupFunc, string lookupType) { try { - string lookupValue = lookupFunc()?.Trim(); + var lookupValue = lookupFunc()?.Trim(); return string.IsNullOrEmpty(lookupValue) ? null : lookupValue; } catch (Exception ex) diff --git a/src/NLog/LayoutRenderers/IdentityLayoutRenderer.cs b/src/NLog/LayoutRenderers/IdentityLayoutRenderer.cs index f74c172829..20d621d523 100644 --- a/src/NLog/LayoutRenderers/IdentityLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/IdentityLayoutRenderer.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.LayoutRenderers { using System.Security.Principal; @@ -52,31 +50,35 @@ public class IdentityLayoutRenderer : LayoutRenderer /// Gets or sets the separator to be used when concatenating /// parts of identity information. /// + /// Default: : /// public string Separator { get; set; } = ":"; /// /// Gets or sets a value indicating whether to render Thread.CurrentPrincipal.Identity.Name. /// + /// Default: /// public bool Name { get; set; } = true; /// /// Gets or sets a value indicating whether to render Thread.CurrentPrincipal.Identity.AuthenticationType. /// + /// Default: /// public bool AuthType { get; set; } = true; /// /// Gets or sets a value indicating whether to render Thread.CurrentPrincipal.Identity.IsAuthenticated. /// + /// Default: /// public bool IsAuthenticated { get; set; } = true; /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { - IIdentity identity = GetValue(); + var identity = GetValue(); if (identity != null) { string separator = string.Empty; @@ -102,15 +104,12 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) builder.Append(identity.Name); } } - } - private static IIdentity GetValue() + private static IIdentity? GetValue() { var currentPrincipal = System.Threading.Thread.CurrentPrincipal; return currentPrincipal?.Identity; } } } - -#endif diff --git a/src/NLog/LayoutRenderers/InstallContextLayoutRenderer.cs b/src/NLog/LayoutRenderers/InstallContextLayoutRenderer.cs index ee8fc21cf6..69d0548685 100644 --- a/src/NLog/LayoutRenderers/InstallContextLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/InstallContextLayoutRenderer.cs @@ -50,10 +50,10 @@ public class InstallContextLayoutRenderer : LayoutRenderer /// /// Gets or sets the name of the parameter. /// + /// [Required] Default: /// - [RequiredParameter] [DefaultParameter] - public string Parameter { get; set; } + public string Parameter { get; set; } = string.Empty; /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) @@ -66,7 +66,7 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) } } - private object GetValue(LogEventInfo logEvent) + private object? GetValue(LogEventInfo logEvent) { return !logEvent.Properties.TryGetValue(Parameter, out var value) ? null : value; } diff --git a/src/NLog/LayoutRenderers/LayoutRenderer.cs b/src/NLog/LayoutRenderers/LayoutRenderer.cs index b740de120f..d0aaf82f3c 100644 --- a/src/NLog/LayoutRenderers/LayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/LayoutRenderer.cs @@ -49,12 +49,12 @@ namespace NLog.LayoutRenderers public abstract class LayoutRenderer : ISupportsInitialize, IRenderable { private bool _isInitialized; - private IValueFormatter _valueFormatter; + private IValueFormatter? _valueFormatter; /// /// Gets the logging configuration this target is part of. /// - protected LoggingConfiguration LoggingConfiguration { get; private set; } + protected LoggingConfiguration? LoggingConfiguration { get; private set; } /// /// Value formatter @@ -88,7 +88,7 @@ public string Render(LogEventInfo logEvent) } /// - void ISupportsInitialize.Initialize(LoggingConfiguration configuration) + void ISupportsInitialize.Initialize(LoggingConfiguration? configuration) { Initialize(configuration); } @@ -103,7 +103,7 @@ void ISupportsInitialize.Close() /// Initializes this instance. /// /// The configuration. - internal void Initialize(LoggingConfiguration configuration) + internal void Initialize(LoggingConfiguration? configuration) { if (LoggingConfiguration is null) LoggingConfiguration = configuration; @@ -118,7 +118,6 @@ private void Initialize() { try { - PropertyHelper.CheckRequiredParameters(ConfigurationItemFactory.Default, this); InitializeLayoutRenderer(); } catch (Exception ex) @@ -156,18 +155,18 @@ internal void Close() /// The layout render output is appended to builder internal void RenderAppendBuilder(LogEventInfo logEvent, StringBuilder builder) { - if (!_isInitialized) - { - Initialize(); - } - try { + if (!_isInitialized) + { + Initialize(); + } + Append(builder, logEvent); } catch (Exception exception) { - InternalLogger.Warn(exception, "Exception in layout renderer."); + InternalLogger.Warn(exception, "Exception in '{0}.Append()'", GetType()); if (exception.MustBeRethrown()) { @@ -183,6 +182,19 @@ internal void RenderAppendBuilder(LogEventInfo logEvent, StringBuilder builder) /// Logging event. protected abstract void Append(StringBuilder builder, LogEventInfo logEvent); + internal void AppendFormattedValue(StringBuilder builder, LogEventInfo logEvent, object? value, string? format, CultureInfo? culture) + { + if (format is null && value is string stringValue) + { + builder.Append(stringValue); + } + else + { + var formatProvider = GetFormatProvider(logEvent, culture); + builder.AppendFormattedValue(value, format, formatProvider, ValueFormatter); + } + } + /// /// Initializes the layout renderer. /// @@ -203,9 +215,9 @@ protected virtual void CloseLayoutRenderer() /// LogEvent with culture /// Culture in on Layout level /// - protected IFormatProvider GetFormatProvider(LogEventInfo logEvent, IFormatProvider layoutCulture = null) + protected IFormatProvider? GetFormatProvider(LogEventInfo logEvent, IFormatProvider? layoutCulture = null) { - return logEvent.FormatProvider ?? layoutCulture ?? LoggingConfiguration?.DefaultCultureInfo; + return layoutCulture ?? logEvent.FormatProvider ?? LoggingConfiguration?.DefaultCultureInfo; } /// @@ -217,9 +229,9 @@ protected IFormatProvider GetFormatProvider(LogEventInfo logEvent, IFormatProvid /// /// is preferred /// - protected CultureInfo GetCulture(LogEventInfo logEvent, CultureInfo layoutCulture = null) + protected CultureInfo GetCulture(LogEventInfo logEvent, CultureInfo? layoutCulture = null) { - return logEvent.FormatProvider as CultureInfo ?? layoutCulture ?? LoggingConfiguration?.DefaultCultureInfo; + return layoutCulture ?? logEvent.FormatProvider as CultureInfo ?? LoggingConfiguration?.DefaultCultureInfo ?? CultureInfo.CurrentCulture; } /// @@ -280,7 +292,7 @@ public static void Register(string name, Func func) /// Callback that returns the value for the layout renderer. [Obsolete("Instead use LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] [EditorBrowsable(EditorBrowsableState.Never)] - public static void Register(string name, Func func) + public static void Register(string name, Func func) { Guard.ThrowIfNull(func); var layoutRenderer = new FuncLayoutRenderer(name, func); diff --git a/src/NLog/LayoutRenderers/LevelLayoutRenderer.cs b/src/NLog/LayoutRenderers/LevelLayoutRenderer.cs index eb65c8fcb2..5cb4b71e87 100644 --- a/src/NLog/LayoutRenderers/LevelLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/LevelLayoutRenderer.cs @@ -64,13 +64,14 @@ public class LevelLayoutRenderer : LayoutRenderer, IRawValue, IStringValueRender /// /// Gets or sets a value indicating the output format of the level. /// + /// Default: /// public LevelFormat Format { get; set; } = LevelFormat.Name; /// /// Gets or sets a value indicating whether upper case conversion should be applied. /// - /// A value of true if upper case conversion should be applied otherwise, false. + /// Default: /// public bool Uppercase { get; set; } @@ -100,14 +101,11 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) private static string GetUpperCaseString(LogLevel level) { - try - { - return _upperCaseMapper[level.Ordinal]; - } - catch (IndexOutOfRangeException) - { + var ordinal = level.Ordinal; + if (ordinal < 0 || ordinal >= _upperCaseMapper.Length) return level.ToString().ToUpperInvariant(); - } + else + return _upperCaseMapper[ordinal]; } private string GetFullNameString(LogLevel level) @@ -141,7 +139,7 @@ bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) return true; } - string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) { if (Format == LevelFormat.Name) { diff --git a/src/NLog/LayoutRenderers/LiteralLayoutRenderer.cs b/src/NLog/LayoutRenderers/LiteralLayoutRenderer.cs index 9f11c85937..1156fee2a0 100644 --- a/src/NLog/LayoutRenderers/LiteralLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/LiteralLayoutRenderer.cs @@ -73,7 +73,7 @@ public LiteralLayoutRenderer(string text) /// /// [DefaultParameter] - public string Text { get; set; } + public string Text { get; set; } = string.Empty; /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) diff --git a/src/NLog/LayoutRenderers/LiteralWithRawValueLayoutRenderer.cs b/src/NLog/LayoutRenderers/LiteralWithRawValueLayoutRenderer.cs index 11beba35c9..52eaaabc6b 100644 --- a/src/NLog/LayoutRenderers/LiteralWithRawValueLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/LiteralWithRawValueLayoutRenderer.cs @@ -43,7 +43,7 @@ namespace NLog.LayoutRenderers internal sealed class LiteralWithRawValueLayoutRenderer : LiteralLayoutRenderer, IRawValue { private readonly bool _rawValueSuccess; - private readonly object _rawValue; + private readonly object? _rawValue; /// /// Initializes a new instance of the class. @@ -52,14 +52,14 @@ internal sealed class LiteralWithRawValueLayoutRenderer : LiteralLayoutRenderer, /// /// Fixed raw value /// This is used by the layout compiler. - public LiteralWithRawValueLayoutRenderer(string text, bool rawValueSuccess, object rawValue) + public LiteralWithRawValueLayoutRenderer(string text, bool rawValueSuccess, object? rawValue) { _rawValueSuccess = rawValueSuccess; _rawValue = rawValue; Text = text; } - bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) + bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object? value) { value = _rawValue; return _rawValueSuccess; diff --git a/src/NLog/LayoutRenderers/Log4JXmlEventLayoutRenderer.cs b/src/NLog/LayoutRenderers/Log4JXmlEventLayoutRenderer.cs deleted file mode 100644 index 4ba5e6b5b3..0000000000 --- a/src/NLog/LayoutRenderers/Log4JXmlEventLayoutRenderer.cs +++ /dev/null @@ -1,473 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.LayoutRenderers -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Globalization; - using System.Reflection; - using System.Text; - using System.Xml; - using NLog.Common; - using NLog.Config; - using NLog.Internal; - using NLog.Internal.Fakeables; - using NLog.Layouts; - using NLog.Targets; - - /// - /// XML event description compatible with log4j, Chainsaw and NLogViewer. - /// - /// - /// See NLog Wiki - /// - /// Documentation on NLog Wiki - [LayoutRenderer("log4jxmlevent")] - public class Log4JXmlEventLayoutRenderer : LayoutRenderer, IUsesStackTrace, IIncludeContext - { - private static readonly DateTime log4jDateBase = new DateTime(1970, 1, 1); - - private static readonly string dummyNamespace = "http://nlog-project.org/dummynamespace/" + Guid.NewGuid(); - private static readonly string dummyNamespaceRemover = " xmlns:log4j=\"" + dummyNamespace + "\""; - - private static readonly string dummyNLogNamespace = "http://nlog-project.org/dummynamespace/" + Guid.NewGuid(); - private static readonly string dummyNLogNamespaceRemover = " xmlns:nlog=\"" + dummyNLogNamespace + "\""; - - private readonly ScopeContextNestedStatesLayoutRenderer _scopeNestedLayoutRenderer = new ScopeContextNestedStatesLayoutRenderer(); - - /// - /// Initializes a new instance of the class. - /// - public Log4JXmlEventLayoutRenderer() : this(LogFactory.DefaultAppEnvironment) - { - } - - /// - /// Initializes a new instance of the class. - /// - internal Log4JXmlEventLayoutRenderer(IAppEnvironment appEnvironment) - { - -#if NETSTANDARD1_3 - AppInfo = "NetCore Application"; -#else - AppInfo = string.Format( - CultureInfo.InvariantCulture, - "{0}({1})", - appEnvironment.AppDomainFriendlyName, - appEnvironment.CurrentProcessId); -#endif - - Parameters = new List(); - - try - { - _machineName = XmlHelper.XmlConvertToStringSafe(EnvironmentHelper.GetMachineName()); - if (string.IsNullOrEmpty(_machineName)) - { - InternalLogger.Info("MachineName is not available."); - } - } - catch (Exception exception) - { - InternalLogger.Error(exception, "Error getting machine name."); - if (exception.MustBeRethrown()) - { - throw; - } - - _machineName = string.Empty; - } - } - - /// - protected override void InitializeLayoutRenderer() - { - base.InitializeLayoutRenderer(); - - _xmlWriterSettings = new XmlWriterSettings - { - Indent = IndentXml, - ConformanceLevel = ConformanceLevel.Fragment, -#if !NET35 - NamespaceHandling = NamespaceHandling.OmitDuplicates, -#endif - IndentChars = " ", - }; - } - - /// - /// Gets or sets a value indicating whether to include NLog-specific extensions to log4j schema. - /// - /// - public bool IncludeNLogData { get; set; } - - /// - /// Gets or sets a value indicating whether the XML should use spaces for indentation. - /// - /// - public bool IndentXml { get; set; } - - /// - /// Gets or sets the log4j:event logger-xml-attribute. Default: ${logger} - /// - /// - public Layout LoggerName { get; set; } - - /// - /// Gets or sets the log4j:event message-xml-element. Default: ${message} - /// - /// - public Layout FormattedMessage { get; set; } - - /// - /// Gets or sets the log4j:event log4japp-xml-element. By default it's the friendly name of the current AppDomain. - /// - /// - public Layout AppInfo { get; set; } - - /// - /// Gets or sets a value indicating whether to include call site (class and method name) in the information sent over the network. - /// - /// - public bool IncludeCallSite { get; set; } - - /// - /// Gets or sets a value indicating whether to include source info (file name and line number) in the information sent over the network. - /// - /// - public bool IncludeSourceInfo { get; set; } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Gets or sets a value indicating whether to include contents of the dictionary. - /// - /// - [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IncludeMdc { get => _includeMdc ?? false; set => _includeMdc = value; } - private bool? _includeMdc; - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Gets or sets a value indicating whether to include contents of the dictionary. - /// - /// - [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IncludeMdlc { get => _includeMdlc ?? false; set => _includeMdlc = value; } - private bool? _includeMdlc; - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Gets or sets a value indicating whether to include contents of the stack. - /// - /// - [Obsolete("Replaced by IncludeNdc. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IncludeNdlc { get => _includeNdlc ?? false; set => _includeNdlc = value; } - private bool? _includeNdlc; - - /// - /// Gets or sets whether to include log4j:NDC in output from nested context. - /// - /// - public bool IncludeNdc { get => _includeNdc ?? false; set => _includeNdc = value; } - private bool? _includeNdc; - - /// - /// Gets or sets whether to include the contents of the properties-dictionary. - /// - /// - public bool IncludeScopeProperties { get => _includeScopeProperties ?? (_includeMdlc == true || _includeMdc == true); set => _includeScopeProperties = value; } - private bool? _includeScopeProperties; - - /// - /// Gets or sets whether to include log4j:NDC in output from nested context. - /// - /// - public bool IncludeScopeNested { get => _includeScopeNested ?? (_includeNdlc == true || _includeNdc == true); set => _includeScopeNested = value; } - private bool? _includeScopeNested; - - /// - /// Gets or sets the stack separator for log4j:NDC in output from nested context. - /// - /// - public string ScopeNestedSeparator - { - get => _scopeNestedLayoutRenderer.Separator; - set => _scopeNestedLayoutRenderer.Separator = value; - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Gets or sets the stack separator for log4j:NDC in output from nested context. - /// - /// - [Obsolete("Replaced by NdcItemSeparator. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public string NdlcItemSeparator { get => ScopeNestedSeparator; set => ScopeNestedSeparator = value; } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Gets or sets the option to include all properties from the log events - /// - /// - [Obsolete("Replaced by IncludeEventProperties. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IncludeAllProperties { get => IncludeEventProperties; set => IncludeEventProperties = value; } - - /// - /// Gets or sets the option to include all properties from the log events - /// - /// - public bool IncludeEventProperties { get; set; } = true; - - /// - /// Gets or sets the stack separator for log4j:NDC in output from nested context. - /// - /// - public string NdcItemSeparator { get => ScopeNestedSeparator; set => ScopeNestedSeparator = value; } - - /// - /// Gets or sets whether the log4j:throwable xml-element should be written as CDATA - /// - /// - public bool WriteThrowableCData { get; set; } - - private readonly string _machineName; - - private XmlWriterSettings _xmlWriterSettings; - - /// - StackTraceUsage IUsesStackTrace.StackTraceUsage => (IncludeCallSite || IncludeSourceInfo) ? (StackTraceUsageUtils.GetStackTraceUsage(IncludeSourceInfo, 0, true) | StackTraceUsage.WithCallSiteClassName) : StackTraceUsage.None; - - internal IList Parameters { get; set; } - - /// - protected override void Append(StringBuilder builder, LogEventInfo logEvent) - { - StringBuilder sb = new StringBuilder(); - using (XmlWriter xtw = XmlWriter.Create(sb, _xmlWriterSettings)) - { - xtw.WriteStartElement("log4j", "event", dummyNamespace); - bool includeNLogCallsite = (IncludeCallSite || IncludeSourceInfo) && logEvent.CallSiteInformation != null; - if (includeNLogCallsite && IncludeNLogData) - { - xtw.WriteAttributeString("xmlns", "nlog", null, dummyNLogNamespace); - } - xtw.WriteAttributeSafeString("logger", LoggerName?.Render(logEvent) ?? logEvent.LoggerName); - xtw.WriteAttributeString("level", logEvent.Level.Name.ToUpperInvariant()); - xtw.WriteAttributeString("timestamp", Convert.ToString((long)(logEvent.TimeStamp.ToUniversalTime() - log4jDateBase).TotalMilliseconds, CultureInfo.InvariantCulture)); - xtw.WriteAttributeString("thread", AsyncHelpers.GetManagedThreadId().ToString(CultureInfo.InvariantCulture)); - - xtw.WriteElementSafeString("log4j", "message", dummyNamespace, FormattedMessage?.Render(logEvent) ?? logEvent.FormattedMessage); - if (logEvent.Exception != null) - { - if (WriteThrowableCData) - { - // CDATA correctly preserves newlines and indention, but not all viewers support this - xtw.WriteStartElement("log4j", "throwable", dummyNamespace); - xtw.WriteSafeCData(logEvent.Exception.ToString()); - xtw.WriteEndElement(); - } - else - { - xtw.WriteElementSafeString("log4j", "throwable", dummyNamespace, logEvent.Exception.ToString()); - } - } - - AppendScopeContextNestedStates(xtw, logEvent); - - if (includeNLogCallsite) - { - AppendCallSite(logEvent, xtw); - } - - xtw.WriteStartElement("log4j", "properties", dummyNamespace); - - AppendScopeContextProperties(xtw); - - if (IncludeEventProperties) - { - AppendDataProperties("log4j", dummyNamespace, xtw, logEvent); - } - - AppendParameters(logEvent, xtw); - - var appInfo = XmlHelper.XmlConvertToStringSafe(AppInfo?.Render(logEvent) ?? string.Empty); - AppendDataProperty(xtw, "log4japp", appInfo, dummyNamespace); - - AppendDataProperty(xtw, "log4jmachinename", _machineName, dummyNamespace); - - xtw.WriteEndElement(); // properties - - xtw.WriteEndElement(); // event - xtw.Flush(); - - // get rid of 'nlog' and 'log4j' namespace declarations - sb.Replace(dummyNamespaceRemover, string.Empty); - if (includeNLogCallsite && IncludeNLogData) - { - sb.Replace(dummyNLogNamespaceRemover, string.Empty); - } - sb.CopyTo(builder); // StringBuilder.Replace is not good when reusing the StringBuilder - } - } - - private void AppendScopeContextProperties(XmlWriter xtw) - { - if (IncludeScopeProperties) - { - using (var scopeEnumerator = ScopeContext.GetAllPropertiesEnumerator()) - { - while (scopeEnumerator.MoveNext()) - { - var scopeProperty = scopeEnumerator.Current; - - string propertyKey = XmlHelper.XmlConvertToStringSafe(scopeProperty.Key); - if (string.IsNullOrEmpty(propertyKey)) - continue; - - string propertyValue = XmlHelper.XmlConvertToStringSafe(scopeProperty.Value); - if (propertyValue is null) - continue; - - AppendDataProperty(xtw, propertyKey, propertyValue, dummyNamespace); - } - } - } - } - - private void AppendScopeContextNestedStates(XmlWriter xtw, LogEventInfo logEvent) - { - if (IncludeScopeNested) - { - var nestedStates = _scopeNestedLayoutRenderer.Render(logEvent); - //NDLC and NDC should be in the same element - xtw.WriteElementSafeString("log4j", "NDC", dummyNamespace, nestedStates); - } - } - - private void AppendParameters(LogEventInfo logEvent, XmlWriter xtw) - { - for (int i = 0; i < Parameters?.Count; ++i) - { - var parameter = Parameters[i]; - - string parameterName = parameter?.Name; // property-setter has ensured safe xml-string - if (string.IsNullOrEmpty(parameterName)) - continue; - - var parameterValue = XmlHelper.XmlConvertToStringSafe(parameter.Layout?.Render(logEvent) ?? string.Empty); - if (!parameter.IncludeEmptyValue && string.IsNullOrEmpty(parameterValue)) - continue; - - AppendDataProperty(xtw, parameterName, parameterValue, dummyNamespace); - } - } - - private void AppendCallSite(LogEventInfo logEvent, XmlWriter xtw) - { - MethodBase methodBase = logEvent.CallSiteInformation.GetCallerStackFrameMethod(0); - string callerClassName = logEvent.CallSiteInformation.GetCallerClassName(methodBase, true, true, true); - string callerMethodName = logEvent.CallSiteInformation.GetCallerMethodName(methodBase, true, true, true); - - xtw.WriteStartElement("log4j", "locationInfo", dummyNamespace); - if (!string.IsNullOrEmpty(callerClassName)) - { - xtw.WriteAttributeSafeString("class", callerClassName); - } - - xtw.WriteAttributeSafeString("method", callerMethodName); - - if (IncludeSourceInfo) - { - xtw.WriteAttributeSafeString("file", logEvent.CallSiteInformation.GetCallerFilePath(0)); - xtw.WriteAttributeString("line", logEvent.CallSiteInformation.GetCallerLineNumber(0).ToString(CultureInfo.InvariantCulture)); - } - - xtw.WriteEndElement(); - - if (IncludeNLogData) - { - xtw.WriteElementSafeString("nlog", "eventSequenceNumber", dummyNLogNamespace, logEvent.SequenceID.ToString(CultureInfo.InvariantCulture)); - xtw.WriteStartElement("nlog", "locationInfo", dummyNLogNamespace); - var type = methodBase?.DeclaringType; - if (type != null) - { - xtw.WriteAttributeSafeString("assembly", type.GetAssembly().FullName); - } - xtw.WriteEndElement(); - - xtw.WriteStartElement("nlog", "properties", dummyNLogNamespace); - AppendDataProperties("nlog", dummyNLogNamespace, xtw, logEvent); - xtw.WriteEndElement(); - } - } - - private static void AppendDataProperties(string prefix, string propertiesNamespace, XmlWriter xtw, LogEventInfo logEvent) - { - if (logEvent.HasProperties) - { - foreach (var contextProperty in logEvent.Properties) - { - string propertyKey = XmlHelper.XmlConvertToStringSafe(contextProperty.Key); - if (string.IsNullOrEmpty(propertyKey)) - continue; - - string propertyValue = XmlHelper.XmlConvertToStringSafe(contextProperty.Value); - if (propertyValue is null) - continue; - - AppendDataProperty(xtw, propertyKey, propertyValue, propertiesNamespace, prefix); - } - } - } - - private static void AppendDataProperty(XmlWriter xtw, string propertyKey, string propertyValue, string propertiesNamespace, string prefix = "log4j") - { - xtw.WriteStartElement(prefix, "data", propertiesNamespace); - xtw.WriteAttributeString("name", propertyKey); - xtw.WriteAttributeString("value", propertyValue); - xtw.WriteEndElement(); - } - } -} diff --git a/src/NLog/LayoutRenderers/LoggerNameLayoutRenderer.cs b/src/NLog/LayoutRenderers/LoggerNameLayoutRenderer.cs index 0043b38d9c..dc4ced0285 100644 --- a/src/NLog/LayoutRenderers/LoggerNameLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/LoggerNameLayoutRenderer.cs @@ -52,62 +52,67 @@ public class LoggerNameLayoutRenderer : LayoutRenderer, IStringValueRenderer /// /// Gets or sets a value indicating whether to render short logger name (the part after the trailing dot character). /// + /// Default: /// public bool ShortName { get; set; } /// /// Gets or sets a value indicating whether to render prefix of logger name (the part before the trailing dot character). /// + /// Default: + /// public bool PrefixName { get; set; } /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { + var loggerName = logEvent.LoggerName; if (ShortName) { - int lastDot = TryGetLastDotForShortName(logEvent); + int lastDot = TryGetLastDotForShortName(loggerName); if (lastDot >= 0) { - builder.Append(logEvent.LoggerName, lastDot + 1, logEvent.LoggerName.Length - lastDot - 1); + builder.Append(loggerName, lastDot + 1, loggerName.Length - lastDot - 1); return; } } else if (PrefixName) { - int lastDot = TryGetLastDotForShortName(logEvent); + int lastDot = TryGetLastDotForShortName(loggerName); if (lastDot > 0) { - builder.Append(logEvent.LoggerName, 0, lastDot); + builder.Append(loggerName, 0, lastDot); return; } } - builder.Append(logEvent.LoggerName); + builder.Append(loggerName); } string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) { + var loggerName = logEvent.LoggerName; if (ShortName) { - int lastDot = TryGetLastDotForShortName(logEvent); + int lastDot = TryGetLastDotForShortName(loggerName); if (lastDot >= 0) { - return logEvent.LoggerName.Substring(lastDot + 1); + return loggerName.Substring(lastDot + 1); } } else if (PrefixName) { - int lastDot = TryGetLastDotForShortName(logEvent); + int lastDot = TryGetLastDotForShortName(loggerName); if (lastDot > 0) { - return logEvent.LoggerName.Substring(0, lastDot); + return loggerName.Substring(0, lastDot); } } - return logEvent.LoggerName ?? string.Empty; + return loggerName ?? string.Empty; } - private int TryGetLastDotForShortName(LogEventInfo logEvent) + private static int TryGetLastDotForShortName(string loggerName) { - return logEvent.LoggerName?.LastIndexOf('.') ?? -1; + return loggerName?.LastIndexOf('.') ?? -1; } } } diff --git a/src/NLog/LayoutRenderers/LongDateLayoutRenderer.cs b/src/NLog/LayoutRenderers/LongDateLayoutRenderer.cs index 9a1dcbba1f..90a7dc4f2d 100644 --- a/src/NLog/LayoutRenderers/LongDateLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/LongDateLayoutRenderer.cs @@ -52,6 +52,7 @@ public class LongDateLayoutRenderer : LayoutRenderer, IRawValue /// /// Gets or sets a value indicating whether to output UTC time instead of local time. /// + /// Default: /// public bool UniversalTime { get => _universalTime ?? false; set => _universalTime = value; } private bool? _universalTime; @@ -89,10 +90,7 @@ private DateTime GetValue(LogEventInfo logEvent) DateTime timestamp = logEvent.TimeStamp; if (_universalTime.HasValue) { - if (_universalTime.Value) - timestamp = timestamp.ToUniversalTime(); - else - timestamp = timestamp.ToLocalTime(); + return _universalTime.Value ? timestamp.ToUniversalTime() : timestamp.ToLocalTime(); } return timestamp; } diff --git a/src/NLog/LayoutRenderers/MachineNameLayoutRenderer.cs b/src/NLog/LayoutRenderers/MachineNameLayoutRenderer.cs index 4e9ecef707..1363ece185 100644 --- a/src/NLog/LayoutRenderers/MachineNameLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/MachineNameLayoutRenderer.cs @@ -51,7 +51,7 @@ namespace NLog.LayoutRenderers [ThreadAgnostic] public class MachineNameLayoutRenderer : LayoutRenderer { - private string _machineName; + private string? _machineName; /// protected override void InitializeLayoutRenderer() diff --git a/src/NLog/LayoutRenderers/MdcLayoutRenderer.cs b/src/NLog/LayoutRenderers/MdcLayoutRenderer.cs deleted file mode 100644 index cca833e0cb..0000000000 --- a/src/NLog/LayoutRenderers/MdcLayoutRenderer.cs +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.LayoutRenderers -{ - using System; - using System.Text; - using NLog.Config; - using NLog.Internal; - - /// - /// Obsolete and replaced by with NLog v5. - /// Render Mapped Diagnostics Logical (MDC) from - /// - /// - /// See NLog Wiki - /// - /// Documentation on NLog Wiki - [LayoutRenderer("mdc")] - [Obsolete("Replaced by ScopeContextPropertyLayoutRenderer ${scopeproperty}. Marked obsolete on NLog 5.0")] - public class MdcLayoutRenderer : LayoutRenderer, IStringValueRenderer - { - /// - /// Gets or sets the name of the item. - /// - /// - [RequiredParameter] - [DefaultParameter] - public string Item { get; set; } - - /// - /// Format string for conversion from object to string. - /// - /// - public string Format { get; set; } - - /// - protected override void Append(StringBuilder builder, LogEventInfo logEvent) - { - var value = GetValue(); - var formatProvider = GetFormatProvider(logEvent, null); - builder.AppendFormattedValue(value, Format, formatProvider, ValueFormatter); - } - - string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue(logEvent); - - private string GetStringValue(LogEventInfo logEvent) - { - if (Format != MessageTemplates.ValueFormatter.FormatAsJson) - { - object value = GetValue(); - string stringValue = FormatHelper.TryFormatToString(value, Format, GetFormatProvider(logEvent, null)); - return stringValue; - } - return null; - } - - private object GetValue() - { - //don't use MappedDiagnosticsContext.Get to ensure we are not locking the Factory (indirect by LogManager.Configuration). - return MappedDiagnosticsContext.GetObject(Item); - } - } -} diff --git a/src/NLog/LayoutRenderers/MdlcLayoutRenderer.cs b/src/NLog/LayoutRenderers/MdlcLayoutRenderer.cs deleted file mode 100644 index faaecf2c7b..0000000000 --- a/src/NLog/LayoutRenderers/MdlcLayoutRenderer.cs +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.LayoutRenderers -{ - using System; - using System.Text; - using NLog.Config; - using NLog.Internal; - - /// - /// Obsolete and replaced by with NLog v5. - /// Render Mapped Diagnostics Logical Context (MDLC) from - /// - /// - /// See NLog Wiki - /// - /// Documentation on NLog Wiki - [LayoutRenderer("mdlc")] - [Obsolete("Replaced by ScopeContextPropertyLayoutRenderer ${scopeproperty}. Marked obsolete on NLog 5.0")] - public class MdlcLayoutRenderer : LayoutRenderer, IStringValueRenderer - { - /// - /// Gets or sets the name of the item. - /// - /// - [RequiredParameter] - [DefaultParameter] - public string Item { get; set; } - - /// - /// Format string for conversion from object to string. - /// - /// - public string Format { get; set; } - - /// - protected override void Append(StringBuilder builder, LogEventInfo logEvent) - { - var value = GetValue(); - var formatProvider = GetFormatProvider(logEvent, null); - builder.AppendFormattedValue(value, Format, formatProvider, ValueFormatter); - } - - string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue(logEvent); - - private string GetStringValue(LogEventInfo logEvent) - { - if (Format != MessageTemplates.ValueFormatter.FormatAsJson) - { - object value = GetValue(); - string stringValue = FormatHelper.TryFormatToString(value, Format, GetFormatProvider(logEvent, null)); - return stringValue; - } - return null; - } - - private object GetValue() - { - //don't use MappedDiagnosticsLogicalContext.Get to ensure we are not locking the Factory (indirect by LogManager.Configuration). - return MappedDiagnosticsLogicalContext.GetObject(Item); - } - } -} diff --git a/src/NLog/LayoutRenderers/MessageLayoutRenderer.cs b/src/NLog/LayoutRenderers/MessageLayoutRenderer.cs index 04281f9463..12eb926573 100644 --- a/src/NLog/LayoutRenderers/MessageLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/MessageLayoutRenderer.cs @@ -49,29 +49,24 @@ namespace NLog.LayoutRenderers [ThreadAgnostic] public class MessageLayoutRenderer : LayoutRenderer, IStringValueRenderer { - /// - /// Initializes a new instance of the class. - /// - public MessageLayoutRenderer() - { - ExceptionSeparator = "|"; - } - /// /// Gets or sets a value indicating whether to log exception along with message. /// + /// Default: /// public bool WithException { get; set; } /// /// Gets or sets the string that separates message from the exception. /// + /// Default: | /// - public string ExceptionSeparator { get; set; } + public string ExceptionSeparator { get; set; } = "|"; /// /// Gets or sets whether it should render the raw message without formatting parameters /// + /// Default: /// public bool Raw { get; set; } @@ -129,7 +124,7 @@ private static Exception GetPrimaryException(Exception exception) return exception; } - string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) { if (WithException) return null; diff --git a/src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs b/src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs index 763f439645..91e121db88 100644 --- a/src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.LayoutRenderers { using System; @@ -56,18 +54,20 @@ public class NLogDirLayoutRenderer : LayoutRenderer /// /// Gets or sets the name of the file to be Path.Combine()'d with the directory name. /// + /// Default: /// - public string File { get; set; } + public string File { get; set; } = string.Empty; /// /// Gets or sets the name of the directory to be Path.Combine()'d with the directory name. /// + /// Default: /// - public string Dir { get; set; } + public string Dir { get; set; } = string.Empty; private static string NLogDir => _nlogDir ?? (_nlogDir = ResolveNLogDir()); - private static string _nlogDir; - private string _nlogCombinedPath; + private static string? _nlogDir; + private string? _nlogCombinedPath; /// protected override void InitializeLayoutRenderer() @@ -76,12 +76,6 @@ protected override void InitializeLayoutRenderer() base.InitializeLayoutRenderer(); } - /// - protected override void CloseLayoutRenderer() - { - _nlogCombinedPath = null; - base.CloseLayoutRenderer(); - } /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) @@ -92,19 +86,16 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) private static string ResolveNLogDir() { - var nlogAssembly = typeof(LogFactory).GetAssembly(); - if (!string.IsNullOrEmpty(nlogAssembly.Location)) + var nlogAssembly = typeof(LogFactory).Assembly; + var nlogLocation = AssemblyHelpers.GetAssemblyFileLocation(nlogAssembly); + if (!string.IsNullOrEmpty(nlogLocation)) { - return Path.GetDirectoryName(nlogAssembly.Location); + return Path.GetDirectoryName(nlogLocation); } else { -#pragma warning disable CS0618 // Type or member is obsolete - return AssemblyHelpers.GetAssemblyFileLocation(nlogAssembly) ?? string.Empty; -#pragma warning restore CS0618 // Type or member is obsolete + return string.Empty; } } } } - -#endif diff --git a/src/NLog/LayoutRenderers/NdcLayoutRenderer.cs b/src/NLog/LayoutRenderers/NdcLayoutRenderer.cs deleted file mode 100644 index ae6e251870..0000000000 --- a/src/NLog/LayoutRenderers/NdcLayoutRenderer.cs +++ /dev/null @@ -1,113 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.LayoutRenderers -{ - using System; - using System.Text; - - /// - /// Obsolete and replaced by with NLog v5. - /// Render Nested Diagnostic Context (NDC) from - /// - /// - /// See NLog Wiki - /// - /// Documentation on NLog Wiki - [LayoutRenderer("ndc")] - [Obsolete("Replaced by ScopeContextNestedStatesLayoutRenderer ${scopenested}. Marked obsolete on NLog 5.0")] - public class NdcLayoutRenderer : LayoutRenderer - { - /// - /// Gets or sets the number of top stack frames to be rendered. - /// - /// - public int TopFrames { get; set; } = -1; - - /// - /// Gets or sets the number of bottom stack frames to be rendered. - /// - /// - public int BottomFrames { get; set; } = -1; - - /// - /// Gets or sets the separator to be used for concatenating nested diagnostics context output. - /// - /// - public string Separator { get; set; } = " "; - - /// - protected override void Append(StringBuilder builder, LogEventInfo logEvent) - { - if (TopFrames == 1) - { - // Allows fast rendering of topframes=1 - var topFrame = NestedDiagnosticsContext.PeekObject(); - if (topFrame != null) - AppendAsString(topFrame, GetFormatProvider(logEvent), builder); - return; - } - - var messages = NestedDiagnosticsContext.GetAllObjects(); - if (messages.Length == 0) - return; - - int startPos = 0; - int endPos = messages.Length; - - if (TopFrames != -1) - { - endPos = Math.Min(TopFrames, messages.Length); - } - else if (BottomFrames != -1) - { - startPos = messages.Length - Math.Min(BottomFrames, messages.Length); - } - - var formatProvider = GetFormatProvider(logEvent); - string currentSeparator = string.Empty; - for (int i = endPos - 1; i >= startPos; --i) - { - builder.Append(currentSeparator); - AppendAsString(messages[i], formatProvider, builder); - currentSeparator = Separator; - } - } - - private static void AppendAsString(object message, IFormatProvider formatProvider, StringBuilder builder) - { - string stringValue = Convert.ToString(message, formatProvider); - builder.Append(stringValue); - } - } -} diff --git a/src/NLog/LayoutRenderers/NdlcLayoutRenderer.cs b/src/NLog/LayoutRenderers/NdlcLayoutRenderer.cs deleted file mode 100644 index eb963927b6..0000000000 --- a/src/NLog/LayoutRenderers/NdlcLayoutRenderer.cs +++ /dev/null @@ -1,113 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.LayoutRenderers -{ - using System; - using System.Text; - - /// - /// Obsolete and replaced by with NLog v5. - /// Render Nested Diagnostic Context (NDLC) from - /// - /// - /// See NLog Wiki - /// - /// Documentation on NLog Wiki - [LayoutRenderer("ndlc")] - [Obsolete("Replaced by ScopeContextNestedStatesLayoutRenderer ${scopenested}. Marked obsolete on NLog 5.0")] - public class NdlcLayoutRenderer : LayoutRenderer - { - /// - /// Gets or sets the number of top stack frames to be rendered. - /// - /// - public int TopFrames { get; set; } = -1; - - /// - /// Gets or sets the number of bottom stack frames to be rendered. - /// - /// - public int BottomFrames { get; set; } = -1; - - /// - /// Gets or sets the separator to be used for concatenating nested logical context output. - /// - /// - public string Separator { get; set; } = " "; - - /// - protected override void Append(StringBuilder builder, LogEventInfo logEvent) - { - if (TopFrames == 1) - { - // Allows fast rendering of topframes=1 - var topFrame = NestedDiagnosticsLogicalContext.PeekObject(); - if (topFrame != null) - AppendAsString(topFrame, GetFormatProvider(logEvent), builder); - return; - } - - var messages = NestedDiagnosticsLogicalContext.GetAllObjects(); - if (messages.Length == 0) - return; - - int startPos = 0; - int endPos = messages.Length; - - if (TopFrames != -1) - { - endPos = Math.Min(TopFrames, messages.Length); - } - else if (BottomFrames != -1) - { - startPos = messages.Length - Math.Min(BottomFrames, messages.Length); - } - - var formatProvider = GetFormatProvider(logEvent); - string currentSeparator = string.Empty; - for (int i = endPos - 1; i >= startPos; --i) - { - builder.Append(currentSeparator); - AppendAsString(messages[i], formatProvider, builder); - currentSeparator = Separator; - } - } - - private static void AppendAsString(object message, IFormatProvider formatProvider, StringBuilder builder) - { - string stringValue = Convert.ToString(message, formatProvider); - builder.Append(stringValue); - } - } -} diff --git a/src/NLog/LayoutRenderers/NdlcTimingLayoutRenderer.cs b/src/NLog/LayoutRenderers/NdlcTimingLayoutRenderer.cs deleted file mode 100644 index 0e7e86f93b..0000000000 --- a/src/NLog/LayoutRenderers/NdlcTimingLayoutRenderer.cs +++ /dev/null @@ -1,96 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.LayoutRenderers -{ - using System; - using System.Text; - - /// - /// Obsolete and replaced by with NLog v5. - /// Render Nested Diagnostic Context (NDLC) timings from - /// - /// - /// See NLog Wiki - /// - /// Documentation on NLog Wiki - [LayoutRenderer("ndlctiming")] - [Obsolete("Replaced by ScopeContextTimingLayoutRenderer ${scopetiming}. Marked obsolete on NLog 5.0")] - public class NdlcTimingLayoutRenderer : LayoutRenderer - { - /// - /// Gets or sets whether to only include the duration of the last scope created - /// - /// - public bool CurrentScope { get; set; } - - /// - /// Gets or sets whether to just display the scope creation time, and not the duration - /// - /// - public bool ScopeBeginTime { get; set; } - - /// - /// Gets or sets the TimeSpan format. Can be any argument accepted by TimeSpan.ToString(format). - /// - /// - public string Format { get; set; } - - /// - protected override void Append(StringBuilder builder, LogEventInfo logEvent) - { - TimeSpan? scopeDuration = CurrentScope ? ScopeContext.PeekInnerNestedDuration() : ScopeContext.PeekOuterNestedDuration(); - if (scopeDuration.HasValue) - { - if (scopeDuration.Value < TimeSpan.Zero) - scopeDuration = TimeSpan.Zero; - - if (ScopeBeginTime) - { - var formatProvider = GetFormatProvider(logEvent, null); - var scopeBegin = Time.TimeSource.Current.Time.Subtract(scopeDuration.Value); - builder.Append(scopeBegin.ToString(Format, formatProvider)); - } - else - { -#if !NET35 - var formatProvider = GetFormatProvider(logEvent, null); - builder.Append(scopeDuration.Value.ToString(Format, formatProvider)); -#else - builder.Append(scopeDuration.Value.ToString()); -#endif - } - } - } - } -} diff --git a/src/NLog/LayoutRenderers/NewLineLayoutRenderer.cs b/src/NLog/LayoutRenderers/NewLineLayoutRenderer.cs index ae6d132e40..2c58e6c476 100644 --- a/src/NLog/LayoutRenderers/NewLineLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/NewLineLayoutRenderer.cs @@ -35,7 +35,6 @@ namespace NLog.LayoutRenderers { using System.Text; using NLog.Config; - using NLog.Internal; /// /// A newline literal. @@ -48,7 +47,7 @@ public class NewLineLayoutRenderer : LayoutRenderer /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { - builder.Append(EnvironmentHelper.NewLine); + builder.AppendLine(); } } } diff --git a/src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs b/src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs index c6a75cef91..9307edd137 100644 --- a/src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.LayoutRenderers { using System; @@ -56,18 +54,21 @@ namespace NLog.LayoutRenderers public class ProcessDirLayoutRenderer : LayoutRenderer { private readonly string _processDir; + private string? _nlogCombinedPath; /// - /// Gets or sets the name of the file to be Path.Combine()'d with the process directory. + /// Gets or sets the name of the file to be Path.Combine()'d with the directory name. /// + /// Default: /// - public string File { get; set; } + public string File { get; set; } = string.Empty; /// - /// Gets or sets the name of the directory to be Path.Combine()'d with the process directory. + /// Gets or sets the name of the directory to be Path.Combine()'d with the directory name. /// + /// Default: /// - public string Dir { get; set; } + public string Dir { get; set; } = string.Empty; /// /// Initializes a new instance of the class. @@ -85,13 +86,18 @@ internal ProcessDirLayoutRenderer(IAppEnvironment appEnvironment) _processDir = Path.GetDirectoryName(appEnvironment.CurrentProcessFilePath); } + /// + protected override void InitializeLayoutRenderer() + { + _nlogCombinedPath = null; + base.InitializeLayoutRenderer(); + } + /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { - var path = PathHelpers.CombinePaths(_processDir, Dir, File); + var path = _nlogCombinedPath ?? (_nlogCombinedPath = PathHelpers.CombinePaths(_processDir, Dir, File)); builder.Append(path); } } } - -#endif diff --git a/src/NLog/LayoutRenderers/ProcessIdLayoutRenderer.cs b/src/NLog/LayoutRenderers/ProcessIdLayoutRenderer.cs index 4edd2824eb..681fd09b0c 100644 --- a/src/NLog/LayoutRenderers/ProcessIdLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ProcessIdLayoutRenderer.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.LayoutRenderers { using System.Text; @@ -83,5 +81,3 @@ bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) } } } - -#endif diff --git a/src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs b/src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs index 99777fb930..64114f0727 100644 --- a/src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs @@ -31,14 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.LayoutRenderers { using System; using System.Diagnostics; using System.Globalization; - using System.Reflection; using System.Text; using NLog.Config; using NLog.Internal; @@ -53,25 +50,29 @@ namespace NLog.LayoutRenderers [LayoutRenderer("processinfo")] public class ProcessInfoLayoutRenderer : LayoutRenderer { - private Process _process; - private ReflectionHelpers.LateBoundMethod _lateBoundPropertyGet; + private Process? _process; + private ReflectionHelpers.LateBoundMethod? _lateBoundPropertyGet; /// /// Gets or sets the property to retrieve. /// + /// Default: /// [DefaultParameter] public ProcessInfoProperty Property { get; set; } = ProcessInfoProperty.Id; /// - /// Gets or sets the format-string to use if the property supports it (Ex. DateTime / TimeSpan / Enum) + /// Gets or sets the format string used when converting the property value to a string, when the + /// property supports formatting (e.g., , , or enum types). /// - /// - public string Format { get; set; } + /// Default: + /// + public string? Format { get; set; } /// /// Gets or sets the culture used for rendering. /// + /// Default: /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; @@ -101,19 +102,14 @@ protected override void CloseLayoutRenderer() /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { - var value = GetValue(); + if (_process is null || _lateBoundPropertyGet is null) + return; + + var value = _lateBoundPropertyGet.Invoke(_process, ArrayHelper.Empty()); if (value != null) { - var formatProvider = GetFormatProvider(logEvent, Culture); - builder.AppendFormattedValue(value, Format, formatProvider, ValueFormatter); + AppendFormattedValue(builder, logEvent, value, Format, Culture); } } - - private object GetValue() - { - return _lateBoundPropertyGet?.Invoke(_process, null); - } } } - -#endif diff --git a/src/NLog/LayoutRenderers/ProcessNameLayoutRenderer.cs b/src/NLog/LayoutRenderers/ProcessNameLayoutRenderer.cs index da89d4790e..f4df4ca39c 100644 --- a/src/NLog/LayoutRenderers/ProcessNameLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ProcessNameLayoutRenderer.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.LayoutRenderers { using System.Text; @@ -57,6 +55,7 @@ public class ProcessNameLayoutRenderer : LayoutRenderer /// /// Gets or sets a value indicating whether to write the full path to the process executable. /// + /// Default: /// public bool FullName { get; set; } @@ -85,5 +84,3 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) } } } - -#endif diff --git a/src/NLog/LayoutRenderers/ProcessTimeLayoutRenderer.cs b/src/NLog/LayoutRenderers/ProcessTimeLayoutRenderer.cs index 613ef87e2b..b56c976144 100644 --- a/src/NLog/LayoutRenderers/ProcessTimeLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ProcessTimeLayoutRenderer.cs @@ -53,14 +53,15 @@ public class ProcessTimeLayoutRenderer : LayoutRenderer, IRawValue /// /// Gets or sets a value indicating whether to output in culture invariant format /// + /// Default: /// public bool Invariant { get => ReferenceEquals(Culture, CultureInfo.InvariantCulture); set => Culture = value ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture; } /// /// Gets or sets the culture used for rendering. /// + /// Default: /// - [RequiredParameter] public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; /// @@ -86,9 +87,7 @@ internal static void WriteTimestamp(StringBuilder builder, TimeSpan ts, CultureI string ticksSeparator = "."; if (!ReferenceEquals(culture, CultureInfo.InvariantCulture)) { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 timeSeparator = culture.DateTimeFormat.TimeSeparator; -#endif ticksSeparator = culture.NumberFormat.NumberDecimalSeparator; } diff --git a/src/NLog/LayoutRenderers/ScopeContextIndentLayoutRenderer.cs b/src/NLog/LayoutRenderers/ScopeContextIndentLayoutRenderer.cs index 99d028be8d..151cad3ebf 100644 --- a/src/NLog/LayoutRenderers/ScopeContextIndentLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ScopeContextIndentLayoutRenderer.cs @@ -50,14 +50,15 @@ public sealed class ScopeContextIndentLayoutRenderer : LayoutRenderer /// /// Gets or sets the indent token. /// + /// Default: /// [DefaultParameter] - public Layout Indent { get; set; } = " "; + public Layout Indent { get; set; } = Layout.FromLiteral(" "); /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { - string indent = null; + string? indent = null; var messages = ScopeContext.GetAllNestedStateList(); for (int i = 0; i < messages.Count; ++i) diff --git a/src/NLog/LayoutRenderers/ScopeContextNestedStatesLayoutRenderer.cs b/src/NLog/LayoutRenderers/ScopeContextNestedStatesLayoutRenderer.cs index f7adb585d1..123f743290 100644 --- a/src/NLog/LayoutRenderers/ScopeContextNestedStatesLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ScopeContextNestedStatesLayoutRenderer.cs @@ -38,7 +38,6 @@ namespace NLog.LayoutRenderers using System.Globalization; using System.Text; using NLog.Internal; - using NLog.Layouts; /// /// Renders the nested states from like a callstack @@ -48,39 +47,63 @@ namespace NLog.LayoutRenderers /// /// Documentation on NLog Wiki [LayoutRenderer("scopenested")] + [LayoutRenderer("ndc")] + [LayoutRenderer("ndlc")] public sealed class ScopeContextNestedStatesLayoutRenderer : LayoutRenderer { /// /// Gets or sets the number of top stack frames to be rendered. /// + /// Default: /// public int TopFrames { get; set; } = -1; /// /// Gets or sets the number of bottom stack frames to be rendered. /// + /// Default: /// public int BottomFrames { get; set; } = -1; /// /// Gets or sets the separator to be used for concatenating nested logical context output. /// + /// Default: /// - public string Separator { get => _separator?.OriginalText; set => _separator = new SimpleLayout(value ?? string.Empty); } - private SimpleLayout _separator = new SimpleLayout(" "); + public string Separator + { + get => _separatorOriginal ?? _separator; + set + { + _separatorOriginal = value; + _separator = Layouts.SimpleLayout.Evaluate(value, LoggingConfiguration, throwConfigExceptions: false); + } + } + private string _separator = " "; + private string _separatorOriginal = " "; /// /// Gets or sets how to format each nested state. Ex. like JSON = @ /// + /// Default: /// - public string Format { get; set; } + public string? Format { get; set; } /// /// Gets or sets the culture used for rendering. /// + /// Default: /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + if (_separatorOriginal != null) + _separator = Layouts.SimpleLayout.Evaluate(_separatorOriginal, LoggingConfiguration); + } + /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { @@ -89,7 +112,7 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) // Allows fast rendering of topframes=1 var topFrame = ScopeContext.PeekNestedState(); if (topFrame != null) - builder.AppendFormattedValue(topFrame, Format, GetFormatProvider(logEvent, Culture), ValueFormatter); + AppendFormattedValue(builder, logEvent, topFrame, Format, Culture); return; } @@ -114,32 +137,35 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) private void AppendNestedStates(StringBuilder builder, IList messages, int startPos, int endPos, LogEventInfo logEvent) { - bool formatAsJson = MessageTemplates.ValueFormatter.FormatAsJson.Equals(Format, StringComparison.Ordinal); - var formatProvider = GetFormatProvider(logEvent, Culture); + bool formatAsJson = MessageTemplates.ValueFormatter.FormatAsJson.Equals(Format); - string separator = null; - string itemSeparator = null; + string separator = _separator ?? string.Empty; + string itemSeparator = separator; if (formatAsJson) { - separator = _separator?.Render(logEvent) ?? string.Empty; + if (itemSeparator == " ") + itemSeparator = ", "; + else if (string.IsNullOrEmpty(itemSeparator)) + itemSeparator = ","; + else + itemSeparator = "," + itemSeparator; builder.Append('['); builder.Append(separator); - itemSeparator = "," + separator; } try { - string currentSeparator = null; + string? currentSeparator = null; for (int i = endPos - 1; i >= startPos; --i) { builder.Append(currentSeparator); if (formatAsJson) - AppendJsonFormattedValue(messages[i], formatProvider, builder, separator, itemSeparator); + AppendJsonFormattedValue(messages[i], Culture ?? CultureInfo.InvariantCulture, builder, separator, itemSeparator); else if (messages[i] is IEnumerable>) builder.Append(Convert.ToString(messages[i])); // Special support for Microsoft Extension Logging ILogger.BeginScope else - builder.AppendFormattedValue(messages[i], Format, formatProvider, ValueFormatter); - currentSeparator = itemSeparator ?? _separator?.Render(logEvent) ?? string.Empty; + AppendFormattedValue(builder, logEvent, messages[i], Format, Culture); + currentSeparator = itemSeparator; } } finally @@ -188,7 +214,7 @@ private void AppendJsonFormattedValue(object nestedState, IFormatProvider format } } - bool AppendJsonProperty(string propertyName, object propertyValue, StringBuilder builder, string itemSeparator) + bool AppendJsonProperty(string propertyName, object? propertyValue, StringBuilder builder, string itemSeparator) { if (string.IsNullOrEmpty(propertyName)) return false; @@ -209,7 +235,7 @@ bool AppendJsonProperty(string propertyName, object propertyValue, StringBuilder return true; } - bool HasUniqueCollectionKeys(IEnumerable> propertyList) + private static bool HasUniqueCollectionKeys(IEnumerable> propertyList) { if (propertyList is IDictionary) { diff --git a/src/NLog/LayoutRenderers/ScopeContextPropertyLayoutRenderer.cs b/src/NLog/LayoutRenderers/ScopeContextPropertyLayoutRenderer.cs index b3e3ba9e08..3ec0b3777c 100644 --- a/src/NLog/LayoutRenderers/ScopeContextPropertyLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ScopeContextPropertyLayoutRenderer.cs @@ -46,50 +46,60 @@ namespace NLog.LayoutRenderers /// /// Documentation on NLog Wiki [LayoutRenderer("scopeproperty")] + [LayoutRenderer("mdc")] + [LayoutRenderer("mdlc")] public sealed class ScopeContextPropertyLayoutRenderer : LayoutRenderer, IStringValueRenderer { /// /// Gets or sets the name of the item. /// + /// [Required] Default: /// - [RequiredParameter] [DefaultParameter] - public string Item { get; set; } + public string Item { get; set; } = string.Empty; /// /// Format string for conversion from object to string. /// + /// Default: /// - public string Format { get; set; } + public string? Format { get; set; } /// /// Gets or sets the culture used for rendering. /// + /// Default: /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + + if (string.IsNullOrEmpty(Item)) + throw new NLogConfigurationException("ScopeProperty-LayoutRenderer Item-property must be assigned. Lookup blank value not supported."); + } + /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { var value = GetValue(); - var formatProvider = GetFormatProvider(logEvent, Culture); - builder.AppendFormattedValue(value, Format, formatProvider, ValueFormatter); + AppendFormattedValue(builder, logEvent, value, Format, Culture); } - string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue(logEvent); - - private string GetStringValue(LogEventInfo logEvent) + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) { - if (Format != MessageTemplates.ValueFormatter.FormatAsJson) + if (!MessageTemplates.ValueFormatter.FormatAsJson.Equals(Format)) { - object value = GetValue(); + var value = GetValue(); string stringValue = FormatHelper.TryFormatToString(value, Format, GetFormatProvider(logEvent, Culture)); return stringValue; } return null; } - private object GetValue() + private object? GetValue() { ScopeContext.TryGetProperty(Item, out var value); return value; diff --git a/src/NLog/LayoutRenderers/ScopeContextTimingLayoutRenderer.cs b/src/NLog/LayoutRenderers/ScopeContextTimingLayoutRenderer.cs index 4b36a1e77d..5cebb4cbc4 100644 --- a/src/NLog/LayoutRenderers/ScopeContextTimingLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ScopeContextTimingLayoutRenderer.cs @@ -46,32 +46,45 @@ namespace NLog.LayoutRenderers /// /// Documentation on NLog Wiki [LayoutRenderer("scopetiming")] + [LayoutRenderer("ndlctiming")] public sealed class ScopeContextTimingLayoutRenderer : LayoutRenderer { /// /// Gets or sets whether to only include the duration of the last scope created /// + /// Default: /// public bool CurrentScope { get; set; } /// /// Gets or sets whether to just display the scope creation time, and not the duration /// + /// Default: /// public bool StartTime { get; set; } + /// + /// Gets or sets whether to just display the scope creation time, and not the duration + /// + /// Default: + /// + [Obsolete("Replaced by StartTime. Marked obsolete on NLog 6.0")] + public bool ScopeBeginTime { get => StartTime; set => StartTime = value; } + /// /// Gets or sets the TimeSpan format. Can be any argument accepted by TimeSpan.ToString(format). /// /// When Format has not been specified, then it will render TimeSpan.TotalMilliseconds /// + /// Default: /// - public string Format { get; set; } + public string? Format { get; set; } /// /// Gets or sets the culture used for rendering. /// - /// + /// Default: + /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; /// diff --git a/src/NLog/LayoutRenderers/SequenceIdLayoutRenderer.cs b/src/NLog/LayoutRenderers/SequenceIdLayoutRenderer.cs index d922847e9f..42741388c3 100644 --- a/src/NLog/LayoutRenderers/SequenceIdLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/SequenceIdLayoutRenderer.cs @@ -33,12 +33,16 @@ namespace NLog.LayoutRenderers { + using System; + using System.ComponentModel; using System.Text; using NLog.Config; using NLog.Internal; /// /// The sequence ID + /// + /// Marked obsolete with NLog 6.0, instead use ${counter} /// /// /// See NLog Wiki @@ -46,6 +50,8 @@ namespace NLog.LayoutRenderers /// Documentation on NLog Wiki [LayoutRenderer("sequenceid")] [ThreadAgnostic] + [Obsolete("Use ${counter:sequence=global} instead of ${sequenceid}. Marked obsolete with NLog 6.0")] + [EditorBrowsable(EditorBrowsableState.Never)] public class SequenceIdLayoutRenderer : LayoutRenderer, IRawValue { /// diff --git a/src/NLog/LayoutRenderers/ShortDateLayoutRenderer.cs b/src/NLog/LayoutRenderers/ShortDateLayoutRenderer.cs index 5552d5de98..ca71ec7f9d 100644 --- a/src/NLog/LayoutRenderers/ShortDateLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ShortDateLayoutRenderer.cs @@ -53,6 +53,7 @@ public class ShortDateLayoutRenderer : LayoutRenderer, IRawValue, IStringValueRe /// /// Gets or sets a value indicating whether to output UTC time instead of local time. /// + /// Default: /// public bool UniversalTime { get => _universalTime ?? false; set => _universalTime = value; } private bool? _universalTime; diff --git a/src/NLog/LayoutRenderers/SpecialFolderApplicationDataLayoutRenderer.cs b/src/NLog/LayoutRenderers/SpecialFolderApplicationDataLayoutRenderer.cs index c596f4a51e..a29178c2b4 100644 --- a/src/NLog/LayoutRenderers/SpecialFolderApplicationDataLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/SpecialFolderApplicationDataLayoutRenderer.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.LayoutRenderers { using System; @@ -52,5 +50,3 @@ public SpecialFolderApplicationDataLayoutRenderer() } } } - -#endif diff --git a/src/NLog/LayoutRenderers/SpecialFolderCommonApplicationDataLayoutRenderer.cs b/src/NLog/LayoutRenderers/SpecialFolderCommonApplicationDataLayoutRenderer.cs index 28ba4143cf..32836a9117 100644 --- a/src/NLog/LayoutRenderers/SpecialFolderCommonApplicationDataLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/SpecialFolderCommonApplicationDataLayoutRenderer.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.LayoutRenderers { using System; @@ -52,5 +50,3 @@ public SpecialFolderCommonApplicationDataLayoutRenderer() } } } - -#endif diff --git a/src/NLog/LayoutRenderers/SpecialFolderLayoutRenderer.cs b/src/NLog/LayoutRenderers/SpecialFolderLayoutRenderer.cs index d1917b6d5e..f59fd255df 100644 --- a/src/NLog/LayoutRenderers/SpecialFolderLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/SpecialFolderLayoutRenderer.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.LayoutRenderers { using System; @@ -70,19 +68,21 @@ public class SpecialFolderLayoutRenderer : LayoutRenderer /// /// [DefaultParameter] - public Environment.SpecialFolder Folder { get; set; } + public Environment.SpecialFolder Folder { get; set; } = Environment.SpecialFolder.CommonApplicationData; /// /// Gets or sets the name of the file to be Path.Combine()'d with the directory name. /// - /// - public string File { get; set; } + /// Default: + /// + public string File { get; set; } = string.Empty; /// /// Gets or sets the name of the directory to be Path.Combine()'d with the directory name. /// - /// - public string Dir { get; set; } + /// Default: + /// + public string Dir { get; set; } = string.Empty; /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) @@ -147,5 +147,3 @@ private static string GetFolderWindowsEnvironmentVariable(Environment.SpecialFol } } } - -#endif diff --git a/src/NLog/LayoutRenderers/SpecialFolderLocalApplicationDataLayoutRenderer.cs b/src/NLog/LayoutRenderers/SpecialFolderLocalApplicationDataLayoutRenderer.cs index 42790c6be3..cfca115f6d 100644 --- a/src/NLog/LayoutRenderers/SpecialFolderLocalApplicationDataLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/SpecialFolderLocalApplicationDataLayoutRenderer.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.LayoutRenderers { using System; @@ -52,5 +50,3 @@ public SpecialFolderLocalApplicationDataLayoutRenderer() } } } - -#endif diff --git a/src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs b/src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs index 7771e5a9cb..95f50913f9 100644 --- a/src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs @@ -37,7 +37,6 @@ namespace NLog.LayoutRenderers using System.Text; using NLog.Config; using NLog.Internal; - using NLog.Layouts; /// /// Stack trace renderer. @@ -53,37 +52,52 @@ public class StackTraceLayoutRenderer : LayoutRenderer, IUsesStackTrace /// /// Gets or sets the output format of the stack trace. /// + /// Default: /// public StackTraceFormat Format { get; set; } = StackTraceFormat.Flat; /// /// Gets or sets the number of top stack frames to be rendered. /// + /// Default: /// public int TopFrames { get; set; } = 3; /// /// Gets or sets the number of frames to skip. /// + /// Default: /// public int SkipFrames { get; set; } /// /// Gets or sets the stack frame separator string. /// + /// Default: => /// - public string Separator { get => _separator?.OriginalText; set => _separator = new SimpleLayout(value ?? string.Empty); } - private SimpleLayout _separator = new SimpleLayout(" => "); + public string Separator + { + get => _separatorOriginal ?? _separator; + set + { + _separatorOriginal = value; + _separator = Layouts.SimpleLayout.Evaluate(value, LoggingConfiguration, throwConfigExceptions: false); + } + } + private string _separator = " => "; + private string _separatorOriginal = " => "; /// /// Logger should capture StackTrace, if it was not provided manually /// + /// Default: /// public bool CaptureStackTrace { get; set; } = true; /// /// Gets or sets whether to render StackFrames in reverse order /// + /// Default: /// public bool Reverse { get; set; } @@ -102,6 +116,14 @@ StackTraceUsage IUsesStackTrace.StackTraceUsage } } + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + if (_separatorOriginal != null) + _separator = Layouts.SimpleLayout.Evaluate(_separatorOriginal, LoggingConfiguration); + } + /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { @@ -122,20 +144,24 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) switch (Format) { case StackTraceFormat.Raw: - AppendRaw(builder, stackFrameList, logEvent); + AppendRaw(builder, stackFrameList); break; case StackTraceFormat.Flat: - AppendFlat(builder, stackFrameList, logEvent); + AppendFlat(builder, stackFrameList); break; case StackTraceFormat.DetailedFlat: - AppendDetailedFlat(builder, stackFrameList, logEvent); + AppendDetailedFlat(builder, stackFrameList); break; } } - private struct StackFrameList + private +#if !NETFRAMEWORK + readonly +#endif + struct StackFrameList { private readonly StackTrace _stackTrace; private readonly int _startingFrame; @@ -162,22 +188,20 @@ public StackFrameList(StackTrace stackTrace, int startingFrame, int endingFrame, } } - private void AppendRaw(StringBuilder builder, StackFrameList stackFrameList, LogEventInfo logEvent) + private void AppendRaw(StringBuilder builder, StackFrameList stackFrameList) { - string separator = null; + string? separator = null; for (int i = 0; i < stackFrameList.Count; ++i) { builder.Append(separator); StackFrame f = stackFrameList[i]; builder.Append(f.ToString()); - separator = separator ?? _separator?.Render(logEvent) ?? string.Empty; + separator = separator ?? _separator ?? string.Empty; } } - private void AppendFlat(StringBuilder builder, StackFrameList stackFrameList, LogEventInfo logEvent) + private void AppendFlat(StringBuilder builder, StackFrameList stackFrameList) { - string separator = null; - bool first = true; for (int i = 0; i < stackFrameList.Count; ++i) { @@ -189,8 +213,7 @@ private void AppendFlat(StringBuilder builder, StackFrameList stackFrameList, Lo if (!first) { - separator = separator ?? _separator?.Render(logEvent) ?? string.Empty; - builder.Append(separator); + builder.Append(_separator); } var type = method.DeclaringType; @@ -209,10 +232,8 @@ private void AppendFlat(StringBuilder builder, StackFrameList stackFrameList, Lo } } - private void AppendDetailedFlat(StringBuilder builder, StackFrameList stackFrameList, LogEventInfo logEvent) + private void AppendDetailedFlat(StringBuilder builder, StackFrameList stackFrameList) { - string separator = null; - bool first = true; for (int i = 0; i < stackFrameList.Count; ++i) { @@ -224,8 +245,7 @@ private void AppendDetailedFlat(StringBuilder builder, StackFrameList stackFrame if (!first) { - separator = separator ?? _separator?.Render(logEvent) ?? string.Empty; - builder.Append(separator); + builder.Append(_separator); } builder.Append('['); builder.Append(method); diff --git a/src/NLog/LayoutRenderers/TempDirLayoutRenderer.cs b/src/NLog/LayoutRenderers/TempDirLayoutRenderer.cs index c552acd8b5..560131762e 100644 --- a/src/NLog/LayoutRenderers/TempDirLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/TempDirLayoutRenderer.cs @@ -51,35 +51,35 @@ namespace NLog.LayoutRenderers [ThreadAgnostic] public class TempDirLayoutRenderer : LayoutRenderer { - private static string tempDir; + private static string TempDir => _tempDir ?? (_tempDir = Path.GetTempPath()); + private static string? _tempDir; + private string? _nlogCombinedPath; /// /// Gets or sets the name of the file to be Path.Combine()'d with the directory name. /// + /// Default: /// - public string File { get; set; } + public string File { get; set; } = string.Empty; /// /// Gets or sets the name of the directory to be Path.Combine()'d with the directory name. /// + /// Default: /// - public string Dir { get; set; } + public string Dir { get; set; } = string.Empty; /// protected override void InitializeLayoutRenderer() { - if (tempDir is null) - { - tempDir = Path.GetTempPath(); // Can throw exception - } - + _nlogCombinedPath = null; base.InitializeLayoutRenderer(); } /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { - var path = PathHelpers.CombinePaths(tempDir, Dir, File); + var path = _nlogCombinedPath ?? (_nlogCombinedPath = PathHelpers.CombinePaths(TempDir, Dir, File)); builder.Append(path); } } diff --git a/src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs b/src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs index f267ffbb14..8374a63e0e 100644 --- a/src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs @@ -54,11 +54,7 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) private static string GetStringValue() { -#if !NETSTANDARD1_3 return System.Threading.Thread.CurrentThread.Name; -#else - return string.Empty; -#endif } string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue(); diff --git a/src/NLog/LayoutRenderers/TimeLayoutRenderer.cs b/src/NLog/LayoutRenderers/TimeLayoutRenderer.cs index afc560cc8b..0791c9ad5a 100644 --- a/src/NLog/LayoutRenderers/TimeLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/TimeLayoutRenderer.cs @@ -53,6 +53,7 @@ public class TimeLayoutRenderer : LayoutRenderer, IRawValue /// /// Gets or sets a value indicating whether to output UTC time instead of local time. /// + /// Default: /// public bool UniversalTime { get => _universalTime ?? false; set => _universalTime = value; } private bool? _universalTime; @@ -60,14 +61,15 @@ public class TimeLayoutRenderer : LayoutRenderer, IRawValue /// /// Gets or sets a value indicating whether to output in culture invariant format /// + /// Default: /// public bool Invariant { get => ReferenceEquals(Culture, CultureInfo.InvariantCulture); set => Culture = value ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture; } /// /// Gets or sets the culture used for rendering. /// + /// Default: /// - [RequiredParameter] public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; /// @@ -80,9 +82,7 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent) string ticksSeparator = "."; if (!ReferenceEquals(culture, CultureInfo.InvariantCulture)) { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 timeSeparator = culture.DateTimeFormat.TimeSeparator; -#endif ticksSeparator = culture.NumberFormat.NumberDecimalSeparator; } diff --git a/src/NLog/LayoutRenderers/VariableLayoutRenderer.cs b/src/NLog/LayoutRenderers/VariableLayoutRenderer.cs index d8f2a3e78a..dad60afd8a 100644 --- a/src/NLog/LayoutRenderers/VariableLayoutRenderer.cs +++ b/src/NLog/LayoutRenderers/VariableLayoutRenderer.cs @@ -50,27 +50,30 @@ public class VariableLayoutRenderer : LayoutRenderer /// /// Gets or sets the name of the NLog variable. /// + /// [Required] Default: /// - [RequiredParameter] [DefaultParameter] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; /// /// Gets or sets the default value to be used when the variable is not set. /// - /// Not used if Name is null + /// Default: /// - public string Default { get; set; } + public string Default { get; set; } = string.Empty; /// /// Gets the configuration variable layout matching the configured Name /// /// Mostly relevant for the scanning of active NLog Layouts (Ex. CallSite capture) - public Layout ActiveLayout => TryGetLayout(out var layout) ? layout : null; + public Layout? ActiveLayout => TryGetLayout(out var layout) ? layout : null; /// protected override void InitializeLayoutRenderer() { + if (string.IsNullOrEmpty(Name)) + throw new NLogConfigurationException("Var-LayoutRenderer Name-property must be assigned. Lookup blank value not supported."); + if (TryGetLayout(out var layout) && layout != null) { //pass loggingConfiguration to layout @@ -83,7 +86,7 @@ protected override void InitializeLayoutRenderer() /// /// Try lookup the configuration variable layout matching the configured Name /// - private bool TryGetLayout(out Layout layout) + private bool TryGetLayout(out Layout? layout) { layout = null; //Note: don't use LogManager (locking, recursion) diff --git a/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs index a559cc5307..c46caeb70f 100644 --- a/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs @@ -48,9 +48,9 @@ namespace NLog.LayoutRenderers.Wrappers /// /// Documentation on NLog Wiki [LayoutRenderer("cached")] - [AmbientProperty("Cached")] - [AmbientProperty("ClearCache")] - [AmbientProperty("CachedSeconds")] + [AmbientProperty(nameof(Cached))] + [AmbientProperty(nameof(ClearCache))] + [AmbientProperty(nameof(CachedSeconds))] [ThreadAgnostic] public sealed class CachedLayoutRendererWrapper : WrapperLayoutRendererBase, IStringValueRenderer { @@ -69,32 +69,36 @@ public enum ClearCacheOption } private readonly object _lockObject = new object(); - private string _cachedValue; - private string _renderedCacheKey; + private string? _cachedValue; + private string? _renderedCacheKey; private DateTime _cachedValueExpires; private TimeSpan? _cachedValueTimeout; /// /// Gets or sets a value indicating whether this is enabled. /// + /// Default: /// public bool Cached { get; set; } = true; /// /// Gets or sets a value indicating when the cache is cleared. /// + /// Default: | /// public ClearCacheOption ClearCache { get; set; } = ClearCacheOption.OnInit | ClearCacheOption.OnClose; /// - /// Cachekey. If the cachekey changes, resets the value. For example, the cachekey would be the current day.s + /// Gets or sets whether to reset cached value when CacheKey output changes. Example CacheKey could render current day, so the cached-value is reset on day roll. /// + /// Default: /// - public Layout CacheKey { get; set; } + public Layout? CacheKey { get; set; } /// /// Gets or sets a value indicating how many seconds the value should stay cached until it expires /// + /// Default: /// public int CachedSeconds { @@ -160,7 +164,7 @@ protected override string RenderInner(LogEventInfo logEvent) } } - string LookupValidCachedValue(LogEventInfo logEvent, string newCacheKey) + string? LookupValidCachedValue(LogEventInfo logEvent, string newCacheKey) { if (_renderedCacheKey != newCacheKey) return null; @@ -171,6 +175,6 @@ string LookupValidCachedValue(LogEventInfo logEvent, string newCacheKey) return _cachedValue; } - string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => Cached ? RenderInner(logEvent) : null; + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => Cached ? RenderInner(logEvent) : null; } } diff --git a/src/NLog/LayoutRenderers/Wrappers/FileSystemNormalizeLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/FileSystemNormalizeLayoutRendererWrapper.cs index 6e6f55204e..0cbbdab126 100644 --- a/src/NLog/LayoutRenderers/Wrappers/FileSystemNormalizeLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/FileSystemNormalizeLayoutRendererWrapper.cs @@ -45,7 +45,7 @@ namespace NLog.LayoutRenderers.Wrappers /// /// Documentation on NLog Wiki [LayoutRenderer("filesystem-normalize")] - [AmbientProperty("FSNormalize")] + [AmbientProperty(nameof(FSNormalize))] [AppDomainFixedOutput] [ThreadAgnostic] public sealed class FileSystemNormalizeLayoutRendererWrapper : WrapperLayoutRendererBase @@ -54,6 +54,7 @@ public sealed class FileSystemNormalizeLayoutRendererWrapper : WrapperLayoutRend /// Gets or sets a value indicating whether to modify the output of this renderer so it can be used as a part of file path /// (illegal characters are replaced with '_'). /// + /// Default: /// public bool FSNormalize { get; set; } = true; diff --git a/src/NLog/LayoutRenderers/Wrappers/JsonEncodeLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/JsonEncodeLayoutRendererWrapper.cs index f29a60a902..6bf855e647 100644 --- a/src/NLog/LayoutRenderers/Wrappers/JsonEncodeLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/JsonEncodeLayoutRendererWrapper.cs @@ -34,6 +34,7 @@ namespace NLog.LayoutRenderers.Wrappers { using System; + using System.ComponentModel; using System.Text; using NLog.Config; @@ -45,7 +46,7 @@ namespace NLog.LayoutRenderers.Wrappers /// /// Documentation on NLog Wiki [LayoutRenderer("json-encode")] - [AmbientProperty("JsonEncode")] + [AmbientProperty(nameof(JsonEncode))] [AppDomainFixedOutput] [ThreadAgnostic] public sealed class JsonEncodeLayoutRendererWrapper : WrapperLayoutRendererBase @@ -53,22 +54,24 @@ public sealed class JsonEncodeLayoutRendererWrapper : WrapperLayoutRendererBase /// /// Gets or sets whether output should be encoded with Json-string escaping. /// + /// Default: /// public bool JsonEncode { get; set; } = true; /// /// Gets or sets a value indicating whether to escape non-ascii characters /// + /// Default: /// public bool EscapeUnicode { get; set; } = true; /// - /// Should forward slashes be escaped? If true, / will be converted to \/ + /// Should forward slashes be escaped? If , / will be converted to \/ /// - /// - /// If not set explicitly then the value of the parent will be used as default. - /// + /// Default: /// + [Obsolete("Marked obsolete with NLog 5.5. Should never escape forward slash")] + [EditorBrowsable(EditorBrowsableState.Never)] public bool EscapeForwardSlash { get; set; } /// @@ -77,7 +80,7 @@ protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBui Inner.Render(logEvent, builder); if (JsonEncode && builder.Length > orgLength) { - Targets.DefaultJsonSerializer.PerformJsonEscapeWhenNeeded(builder, orgLength, EscapeUnicode, EscapeForwardSlash); + Targets.DefaultJsonSerializer.PerformJsonEscapeWhenNeeded(builder, orgLength, EscapeUnicode); } } diff --git a/src/NLog/LayoutRenderers/Wrappers/LeftLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/LeftLayoutRendererWrapper.cs index ce69a43772..b33a50f19b 100644 --- a/src/NLog/LayoutRenderers/Wrappers/LeftLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/LeftLayoutRendererWrapper.cs @@ -45,20 +45,22 @@ namespace NLog.LayoutRenderers.Wrappers /// /// Documentation on NLog Wiki [LayoutRenderer("left")] - [AmbientProperty("Truncate")] + [AmbientProperty(nameof(Truncate))] [AppDomainFixedOutput] [ThreadAgnostic] public sealed class LeftLayoutRendererWrapper : WrapperLayoutRendererBase { /// - /// Gets or sets the length in characters. + /// Gets or sets the length in characters. Zero or negative means disabled. /// + /// Default: /// public int Length { get; set; } /// /// Same as -property, so it can be used as ambient property. /// + /// Default: /// /// ${message:truncate=80} /// @@ -73,7 +75,7 @@ protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBui return; } - Inner.Render(logEvent, builder); + Inner?.Render(logEvent, builder); var renderedLength = builder.Length - orgLength; if (renderedLength > Length) { diff --git a/src/NLog/LayoutRenderers/Wrappers/LowercaseLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/LowercaseLayoutRendererWrapper.cs index 3606d646cd..7aa46f0d98 100644 --- a/src/NLog/LayoutRenderers/Wrappers/LowercaseLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/LowercaseLayoutRendererWrapper.cs @@ -55,7 +55,7 @@ public sealed class LowercaseLayoutRendererWrapper : WrapperLayoutRendererBase /// /// Gets or sets a value indicating whether lower case conversion should be applied. /// - /// A value of true if lower case conversion should be applied; otherwise, false. + /// A value of if lower case conversion should be applied; otherwise, . /// public bool Lowercase { get; set; } = true; @@ -71,16 +71,17 @@ public sealed class LowercaseLayoutRendererWrapper : WrapperLayoutRendererBase /// /// Gets or sets the culture used for rendering. /// + /// Default: /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; /// protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBuilder builder, int orgLength) { - Inner.Render(logEvent, builder); + Inner?.Render(logEvent, builder); if (Lowercase && builder.Length > orgLength) { - TransformToLowerCase(builder, orgLength); + TransformToLowerCase(builder, logEvent, orgLength); } } @@ -90,29 +91,12 @@ protected override string Transform(string text) throw new NotSupportedException(); } - private void TransformToLowerCase(StringBuilder target, int startPos) + private void TransformToLowerCase(StringBuilder target, LogEventInfo logEvent, int startPos) { - CultureInfo culture = Culture; - -#if NETSTANDARD1_3 || NETSTANDARD1_5 - string stringToLower = null; - if (culture != null && culture != CultureInfo.InvariantCulture) - { - stringToLower = target.ToString(startPos, target.Length - startPos); - stringToLower = culture.TextInfo.ToLower(stringToLower); - } -#endif - + CultureInfo culture = GetCulture(logEvent, Culture); for (int i = startPos; i < target.Length; ++i) { -#if NETSTANDARD1_3 || NETSTANDARD1_5 - if (stringToLower != null) - target[i] = stringToLower[i]; //no char.ToLower with culture - else - target[i] = char.ToLowerInvariant(target[i]); -#else target[i] = char.ToLower(target[i], culture); -#endif } } } diff --git a/src/NLog/LayoutRenderers/Wrappers/NoRawValueLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/NoRawValueLayoutRendererWrapper.cs index 81370f44a9..840bea8dee 100644 --- a/src/NLog/LayoutRenderers/Wrappers/NoRawValueLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/NoRawValueLayoutRendererWrapper.cs @@ -42,7 +42,7 @@ namespace NLog.LayoutRenderers.Wrappers /// /// For performance and/or full (formatted) control of the output. [LayoutRenderer("norawvalue")] - [AmbientProperty("NoRawValue")] + [AmbientProperty(nameof(NoRawValue))] [AppDomainFixedOutput] [ThreadAgnostic] public sealed class NoRawValueLayoutRendererWrapper : WrapperLayoutRendererBase @@ -50,7 +50,7 @@ public sealed class NoRawValueLayoutRendererWrapper : WrapperLayoutRendererBase /// /// Gets or sets a value indicating whether to disable the IRawValue-interface /// - /// A value of true if IRawValue-interface should be ignored; otherwise, false. + /// Default: /// public bool NoRawValue { get; set; } = true; diff --git a/src/NLog/LayoutRenderers/Wrappers/ObjectPathRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/ObjectPathRendererWrapper.cs index 71bf520e65..e943868524 100644 --- a/src/NLog/LayoutRenderers/Wrappers/ObjectPathRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/ObjectPathRendererWrapper.cs @@ -52,7 +52,7 @@ namespace NLog.LayoutRenderers.Wrappers public sealed class ObjectPathRendererWrapper : WrapperLayoutRendererBase, IRawValue { private ObjectReflectionCache ObjectReflectionCache => _objectReflectionCache ?? (_objectReflectionCache = new ObjectReflectionCache(LoggingConfiguration.GetServiceProvider())); - private ObjectReflectionCache _objectReflectionCache; + private ObjectReflectionCache? _objectReflectionCache; private ObjectPropertyPath _objectPropertyPath; /// @@ -60,6 +60,7 @@ public sealed class ObjectPathRendererWrapper : WrapperLayoutRendererBase, IRawV /// /// Shortcut for /// + /// Default: /// public string Path { @@ -70,23 +71,26 @@ public string Path /// /// Gets or sets the object-property-navigation-path for lookup of nested property /// + /// [Required] Default: /// public string ObjectPath { - get => _objectPropertyPath.Value; + get => _objectPropertyPath.Value ?? string.Empty; set => _objectPropertyPath.Value = value; } /// /// Format string for conversion from object to string. /// - /// - public string Format { get; set; } + /// Default: + /// + public string? Format { get; set; } /// /// Gets or sets the culture used for rendering. /// - /// + /// Default: + /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; /// @@ -98,21 +102,16 @@ protected override string Transform(string text) /// protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBuilder builder, int orgLength) { - if (TryGetRawPropertyValue(logEvent, out object propertyValue)) + if (TryGetRawPropertyValue(logEvent, out var propertyValue)) { - var formatProvider = GetFormatProvider(logEvent, Culture); - builder.AppendFormattedValue(propertyValue, Format, formatProvider, ValueFormatter); + AppendFormattedValue(builder, logEvent, propertyValue, Format, Culture); } } - private bool TryGetRawPropertyValue(LogEventInfo logEvent, out object propertyValue) + private bool TryGetRawPropertyValue(LogEventInfo logEvent, out object? propertyValue) { - if (Inner != null && - Inner.TryGetRawValue(logEvent, out var rawValue) && - TryGetPropertyValue(rawValue, out propertyValue)) - { + if (Inner?.TryGetRawValue(logEvent, out var rawValue) == true && rawValue != null && TryGetPropertyValue(rawValue, out propertyValue)) return true; - } propertyValue = null; return false; @@ -122,11 +121,11 @@ private bool TryGetRawPropertyValue(LogEventInfo logEvent, out object propertyVa /// Lookup property-value from source object based on /// /// Could resolve property-value? - public bool TryGetPropertyValue(object sourceObject, out object propertyValue) + public bool TryGetPropertyValue(object sourceObject, out object? propertyValue) { return ObjectReflectionCache.TryGetObjectProperty(sourceObject, _objectPropertyPath.PathNames, out propertyValue); } - bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) => TryGetRawPropertyValue(logEvent, out value); + bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object? value) => TryGetRawPropertyValue(logEvent, out value); } } diff --git a/src/NLog/LayoutRenderers/Wrappers/OnExceptionLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/OnExceptionLayoutRendererWrapper.cs index 97f1f4f84d..af20d37192 100644 --- a/src/NLog/LayoutRenderers/Wrappers/OnExceptionLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/OnExceptionLayoutRendererWrapper.cs @@ -51,8 +51,9 @@ public sealed class OnExceptionLayoutRendererWrapper : WrapperLayoutRendererBase /// /// If is not found, print this layout. /// + /// Default: /// - public Layout Else { get; set; } + public Layout Else { get; set; } = Layout.Empty; /// protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBuilder builder, int orgLength) diff --git a/src/NLog/LayoutRenderers/Wrappers/OnHasPropertiesLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/OnHasPropertiesLayoutRendererWrapper.cs index 8ab425142b..bc14c03c2b 100644 --- a/src/NLog/LayoutRenderers/Wrappers/OnHasPropertiesLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/OnHasPropertiesLayoutRendererWrapper.cs @@ -54,8 +54,9 @@ public sealed class OnHasPropertiesLayoutRendererWrapper : WrapperLayoutRenderer /// /// If is not found, print this layout. /// + /// Default: /// - public Layout Else { get; set; } + public Layout Else { get; set; } = Layout.Empty; /// protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBuilder builder, int orgLength) diff --git a/src/NLog/LayoutRenderers/Wrappers/PaddingLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/PaddingLayoutRendererWrapper.cs index f81d198799..2e74cecae8 100644 --- a/src/NLog/LayoutRenderers/Wrappers/PaddingLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/PaddingLayoutRendererWrapper.cs @@ -57,6 +57,7 @@ public sealed class PaddingLayoutRendererWrapper : WrapperLayoutRendererBase /// Gets or sets the number of characters to pad the output to. /// /// + /// Default: . /// Positive padding values cause left padding, negative values /// cause right padding to the desired width. /// @@ -66,6 +67,7 @@ public sealed class PaddingLayoutRendererWrapper : WrapperLayoutRendererBase /// /// Gets or sets the padding character. /// + /// Default: /// public char PadCharacter { get; set; } = ' '; @@ -73,23 +75,25 @@ public sealed class PaddingLayoutRendererWrapper : WrapperLayoutRendererBase /// Gets or sets a value indicating whether to trim the /// rendered text to the absolute value of the padding length. /// + /// Default: /// public bool FixedLength { get; set; } /// /// Gets or sets a value indicating whether a value that has - /// been truncated (when is true) + /// been truncated (when is ) /// will be left-aligned (characters removed from the right) /// or right-aligned (characters removed from the left). The /// default is left alignment. /// + /// Default: /// public PaddingHorizontalAlignment AlignmentOnTruncation { get; set; } = PaddingHorizontalAlignment.Left; /// protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBuilder builder, int orgLength) { - Inner.Render(logEvent, builder); + Inner?.Render(logEvent, builder); if (Padding != 0) { int absolutePadding = Padding; diff --git a/src/NLog/LayoutRenderers/Wrappers/ReplaceLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/ReplaceLayoutRendererWrapper.cs index 7d1fb2ad27..47ebe3779d 100644 --- a/src/NLog/LayoutRenderers/Wrappers/ReplaceLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/ReplaceLayoutRendererWrapper.cs @@ -34,10 +34,6 @@ namespace NLog.LayoutRenderers.Wrappers { using System; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using NLog.Common; using NLog.Config; using NLog.Internal; @@ -45,7 +41,7 @@ namespace NLog.LayoutRenderers.Wrappers /// Replaces a string in the output of another layout with another string. /// /// - /// ${replace:searchFor=\\n+:replaceWith=-:regex=true:inner=${message}} + /// ${replace:searchFor=foo:replaceWith=bar:inner=${message}} /// /// /// See NLog Wiki @@ -56,149 +52,80 @@ namespace NLog.LayoutRenderers.Wrappers [ThreadAgnostic] public sealed class ReplaceLayoutRendererWrapper : WrapperLayoutRendererBase { - private RegexHelper _regexHelper; - private MatchEvaluator _groupMatchEvaluator; - /// /// Gets or sets the text to search for. /// - /// The text search for. + /// [Required] Default: /// - [RequiredParameter] - public string SearchFor { get; set; } - - /// - /// Gets or sets a value indicating whether regular expressions should be used. - /// - /// A value of true if regular expressions should be used otherwise, false. - /// - public bool Regex { get; set; } + public string SearchFor + { + get => _searchForOriginal ?? _searchFor; + set + { + _searchForOriginal = value; + _searchFor = Layouts.SimpleLayout.Evaluate(value, LoggingConfiguration, throwConfigExceptions: false); + } + } + private string _searchFor = string.Empty; + private string _searchForOriginal = string.Empty; /// /// Gets or sets the replacement string. /// - /// The replacement string. + /// Default: /// - public string ReplaceWith { get; set; } = string.Empty; - - /// - /// Gets or sets the group name to replace when using regular expressions. - /// Leave null or empty to replace without using group name. - /// - /// The group name. - /// - public string ReplaceGroupName { get; set; } + public string ReplaceWith + { + get => _replaceWithOriginal ?? _replaceWith; + set + { + _replaceWithOriginal = value; + _replaceWith = Layouts.SimpleLayout.Evaluate(value, LoggingConfiguration, throwConfigExceptions: false); + } + } + private string _replaceWith = string.Empty; + private string _replaceWithOriginal = string.Empty; /// /// Gets or sets a value indicating whether to ignore case. /// - /// A value of true if case should be ignored when searching; otherwise, false. + /// Default: /// public bool IgnoreCase { get; set; } /// - /// Gets or sets a value indicating whether to search for whole words. + /// Gets or sets a value indicating whether to search for whole words /// - /// A value of true if whole words should be searched for; otherwise, false. + /// Default: /// public bool WholeWords { get; set; } - /// - /// Compile the ? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used. - /// - /// - public bool CompileRegex { get; set; } - /// protected override void InitializeLayoutRenderer() { base.InitializeLayoutRenderer(); - _regexHelper = new RegexHelper() - { - IgnoreCase = IgnoreCase, - WholeWords = WholeWords, - CompileRegex = CompileRegex, - }; - if (Regex) - _regexHelper.RegexPattern = SearchFor; - else - _regexHelper.SearchText = SearchFor; + if (string.IsNullOrEmpty(SearchFor)) + throw new NLogConfigurationException("Replace-LayoutRenderer SearchFor-property must be assigned. Searching for blank value not supported."); - if (!string.IsNullOrEmpty(ReplaceGroupName) && _regexHelper.Regex?.GetGroupNames()?.Contains(ReplaceGroupName) == false) - { - InternalLogger.Warn("Replace-LayoutRenderer assigned unknown ReplaceGroupName: {0}", ReplaceGroupName); - } + if (_searchForOriginal != null) + _searchFor = Layouts.SimpleLayout.Evaluate(_searchForOriginal, LoggingConfiguration); + if (_replaceWithOriginal != null) + _replaceWith = Layouts.SimpleLayout.Evaluate(_replaceWithOriginal, LoggingConfiguration); } /// protected override string Transform(string text) { - if (string.IsNullOrEmpty(ReplaceGroupName)) + if (IgnoreCase || WholeWords) { - return _regexHelper.Replace(text, ReplaceWith); + var stringComparer = IgnoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture; + return StringHelpers.Replace(text, _searchFor, _replaceWith, stringComparer, WholeWords); } else { - if (_groupMatchEvaluator is null) - _groupMatchEvaluator = m => ReplaceNamedGroup(ReplaceGroupName, ReplaceWith, m); - return _regexHelper.Regex?.Replace(text, _groupMatchEvaluator) ?? text; + return text.Replace(_searchFor, _replaceWith); } } - - /// - /// This class was created instead of simply using a lambda expression so that the "ThreadAgnosticAttributeTest" will pass - /// - [ThreadAgnostic] - [Obsolete("This class should not be used. Marked obsolete on NLog 4.7")] - public class Replacer - { - private readonly string _replaceGroupName; - private readonly string _replaceWith; - - internal Replacer(string text, string replaceGroupName, string replaceWith) - { - _replaceGroupName = replaceGroupName; - _replaceWith = replaceWith; - } - - internal string EvaluateMatch(Match match) - { - return ReplaceNamedGroup(_replaceGroupName, _replaceWith, match); - } - } - - /// - /// A match evaluator for Regular Expression based replacing - /// - /// Input string. - /// Group name in the regex. - /// Replace value. - /// Match from regex. - /// Groups replaced with . - [Obsolete("This method should not be used. Marked obsolete on NLog 4.7")] - public static string ReplaceNamedGroup(string input, string groupName, string replacement, Match match) - { - return ReplaceNamedGroup(groupName, replacement, match); - } - - internal static string ReplaceNamedGroup(string groupName, string replacement, Match match) - { - var sb = new StringBuilder(match.Value); - var matchLength = match.Length; - - var captures = match.Groups[groupName].Captures.OfType().OrderByDescending(c => c.Index); - foreach (var capt in captures) - { - matchLength += replacement.Length - capt.Length; - - sb.Remove(capt.Index - match.Index, capt.Length); - sb.Insert(capt.Index - match.Index, replacement); - } - - var end = matchLength; - sb.Remove(end, sb.Length - end); - return sb.ToString(); - } } } diff --git a/src/NLog/LayoutRenderers/Wrappers/ReplaceNewLinesLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/ReplaceNewLinesLayoutRendererWrapper.cs index a53646292d..c3359ad707 100644 --- a/src/NLog/LayoutRenderers/Wrappers/ReplaceNewLinesLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/ReplaceNewLinesLayoutRendererWrapper.cs @@ -36,7 +36,6 @@ namespace NLog.LayoutRenderers.Wrappers using System; using System.Text; using NLog.Config; - using NLog.Internal; /// /// Replaces newline characters from the result of another layout renderer with spaces. @@ -46,49 +45,87 @@ namespace NLog.LayoutRenderers.Wrappers /// /// Documentation on NLog Wiki [LayoutRenderer("replace-newlines")] - [AmbientProperty("ReplaceNewLines")] + [AmbientProperty(nameof(ReplaceNewLines))] [AppDomainFixedOutput] [ThreadAgnostic] public sealed class ReplaceNewLinesLayoutRendererWrapper : WrapperLayoutRendererBase { - private const string WindowsNewLine = "\r\n"; - private const string UnixNewLine = "\n"; - /// - /// Gets or sets a value indicating the string that should be used for separating lines. + /// Gets or sets a value indicating the string that should be used to replace newlines. /// + /// Default: /// - public string Replacement - { - get => _replacement; - set - { - _replacement = value; - _replaceWithNewLines = value?.IndexOf('\n') >= 0; - } - } - private string _replacement = " "; - private bool _replaceWithNewLines; + public string Replacement { get; set; } = " "; + + /// + /// Gets or sets a value indicating the string that should be used to replace newlines (alias for ) + /// + /// Default: + /// + public string ReplaceNewLines { get => Replacement; set => Replacement = value; } /// protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBuilder builder, int orgLength) { - Inner.Render(logEvent, builder); + Inner?.Render(logEvent, builder); if (builder.Length > orgLength) { - var containsNewLines = builder.IndexOf('\n', orgLength) >= 0; - if (containsNewLines) + int lineEndIndex = IndexOfLineEndCharacters(builder, orgLength); + if (lineEndIndex > -1) { - string str = builder.ToString(orgLength, builder.Length - orgLength) - .Replace(WindowsNewLine, _replaceWithNewLines ? UnixNewLine : Replacement) - .Replace(UnixNewLine, Replacement); - + orgLength = lineEndIndex > orgLength ? lineEndIndex - 1 : orgLength; + string str = builder.ToString(orgLength, builder.Length - orgLength); builder.Length = orgLength; - builder.Append(str); + + bool ignoreNewLine = false; + foreach (var chr in str) + { + if (IsLineEndCharacter(chr)) // switches on chr, and allow method-reuse + { + if (!ignoreNewLine || chr != '\n') + { + builder.Append(Replacement); + } + ignoreNewLine = chr == '\r'; + } + else + { + builder.Append(chr); + ignoreNewLine = false; + } + } } } } + private static bool IsLineEndCharacter(char ch) + { + switch (ch) + { + case '\r': // CARRIAGE RETURN + case '\n': // LINE FEED + case '\f': // FORM FEED (FF) + case '\u0085': // NEXT LINE (NEL) + case '\u2028': // LINE SEPARATOR + case '\u2029': // PARAGRAPH SEPARATOR + return true; + } + return false; + } + + private static int IndexOfLineEndCharacters(StringBuilder builder, int startPosition) + { + for (int i = startPosition; i < builder.Length; i++) + { + if (IsLineEndCharacter(builder[i])) + { + return i; + } + } + return -1; + } + + /// protected override string Transform(string text) { diff --git a/src/NLog/LayoutRenderers/Wrappers/RightLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/RightLayoutRendererWrapper.cs index e2a099fffa..743688854f 100644 --- a/src/NLog/LayoutRenderers/Wrappers/RightLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/RightLayoutRendererWrapper.cs @@ -50,8 +50,9 @@ namespace NLog.LayoutRenderers.Wrappers public sealed class RightLayoutRendererWrapper : WrapperLayoutRendererBase { /// - /// Gets or sets the length in characters. + /// Gets or sets the length in characters. Zero or negative means disabled. /// + /// Default: /// public int Length { get; set; } diff --git a/src/NLog/LayoutRenderers/Wrappers/Rot13LayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/Rot13LayoutRendererWrapper.cs index 57682cad2c..931713ed5a 100644 --- a/src/NLog/LayoutRenderers/Wrappers/Rot13LayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/Rot13LayoutRendererWrapper.cs @@ -58,6 +58,7 @@ public sealed class Rot13LayoutRendererWrapper : WrapperLayoutRendererBase /// The layout to be wrapped. /// This variable is for backwards compatibility /// + [Obsolete("Replaced by Inner. Marked obsolete with NLog 2.0")] public Layout Text { get => Inner; diff --git a/src/NLog/LayoutRenderers/Wrappers/SubstringLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/SubstringLayoutRendererWrapper.cs index b018470a82..cea1086293 100644 --- a/src/NLog/LayoutRenderers/Wrappers/SubstringLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/SubstringLayoutRendererWrapper.cs @@ -57,14 +57,14 @@ public sealed class SubstringLayoutRendererWrapper : WrapperLayoutRendererBase /// /// Gets or sets the start index. /// - /// Index + /// Default: /// public int Start { get; set; } /// /// Gets or sets the length in characters. If null, then the whole string /// - /// Index + /// Default: /// public int? Length { get; set; } @@ -76,7 +76,7 @@ protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBui return; } - Inner.Render(logEvent, builder); + Inner?.Render(logEvent, builder); var renderedLength = builder.Length - orgLength; if (renderedLength > 0) diff --git a/src/NLog/LayoutRenderers/Wrappers/TrimWhiteSpaceLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/TrimWhiteSpaceLayoutRendererWrapper.cs index 467d5286c0..446cf9d9ea 100644 --- a/src/NLog/LayoutRenderers/Wrappers/TrimWhiteSpaceLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/TrimWhiteSpaceLayoutRendererWrapper.cs @@ -46,15 +46,15 @@ namespace NLog.LayoutRenderers.Wrappers /// /// Documentation on NLog Wiki [LayoutRenderer("trim-whitespace")] - [AmbientProperty("TrimWhiteSpace")] + [AmbientProperty(nameof(TrimWhiteSpace))] [AppDomainFixedOutput] [ThreadAgnostic] public sealed class TrimWhiteSpaceLayoutRendererWrapper : WrapperLayoutRendererBase { /// - /// Gets or sets a value indicating whether lower case conversion should be applied. + /// Gets or sets a value indicating whether whitespace should be trimmed. /// - /// A value of true if lower case conversion should be applied; otherwise, false. + /// Default: /// public bool TrimWhiteSpace { get; set; } = true; diff --git a/src/NLog/LayoutRenderers/Wrappers/UppercaseLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/UppercaseLayoutRendererWrapper.cs index 540cc20f05..7e0c0c702d 100644 --- a/src/NLog/LayoutRenderers/Wrappers/UppercaseLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/UppercaseLayoutRendererWrapper.cs @@ -60,13 +60,14 @@ public sealed class UppercaseLayoutRendererWrapper : WrapperLayoutRendererBase /// /// Gets or sets a value indicating whether upper case conversion should be applied. /// - /// A value of true if upper case conversion should be applied otherwise, false. + /// Default: /// public bool Uppercase { get; set; } = true; /// /// Same as -property, so it can be used as ambient property. /// + /// Default: /// /// ${level:toupper} /// @@ -76,6 +77,7 @@ public sealed class UppercaseLayoutRendererWrapper : WrapperLayoutRendererBase /// /// Gets or sets the culture used for rendering. /// + /// Default: /// public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; @@ -85,7 +87,7 @@ protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBui Inner.Render(logEvent, builder); if (Uppercase && builder.Length > orgLength) { - TransformToUpperCase(builder, orgLength); + TransformToUpperCase(builder, logEvent, orgLength); } } @@ -95,29 +97,12 @@ protected override string Transform(string text) throw new NotSupportedException(); } - private void TransformToUpperCase(StringBuilder target, int startPos) + private void TransformToUpperCase(StringBuilder target, LogEventInfo logEvent, int startPos) { - CultureInfo culture = Culture; - -#if NETSTANDARD1_3 || NETSTANDARD1_5 - string stringToUpper = null; - if (culture != null && culture != CultureInfo.InvariantCulture) - { - stringToUpper = target.ToString(startPos, target.Length - startPos); - stringToUpper = culture.TextInfo.ToUpper(stringToUpper); - } -#endif - + CultureInfo culture = GetCulture(logEvent, Culture); for (int i = startPos; i < target.Length; ++i) { -#if NETSTANDARD1_3 || NETSTANDARD1_5 - if (stringToUpper != null) - target[i] = stringToUpper[i]; //no char.ToUpper with culture - else - target[i] = char.ToUpperInvariant(target[i]); -#else target[i] = char.ToUpper(target[i], culture); -#endif } } } diff --git a/src/NLog/LayoutRenderers/Wrappers/UrlEncodeLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/UrlEncodeLayoutRendererWrapper.cs index e2870696e1..579db0932f 100644 --- a/src/NLog/LayoutRenderers/Wrappers/UrlEncodeLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/UrlEncodeLayoutRendererWrapper.cs @@ -51,32 +51,24 @@ namespace NLog.LayoutRenderers.Wrappers [ThreadAgnostic] public sealed class UrlEncodeLayoutRendererWrapper : WrapperLayoutRendererBase { - /// - /// Initializes a new instance of the class. - /// - public UrlEncodeLayoutRendererWrapper() - { - SpaceAsPlus = true; - } - /// /// Gets or sets a value indicating whether spaces should be translated to '+' or '%20'. /// - /// A value of true if space should be translated to '+'; otherwise, false. + /// Default: /// - public bool SpaceAsPlus { get; set; } + public bool SpaceAsPlus { get; set; } = true; /// /// Gets or sets a value whether escaping be done according to Rfc3986 (Supports Internationalized Resource Identifiers - IRIs) /// - /// A value of true if Rfc3986; otherwise, false for legacy Rfc2396. + /// Default: /// public bool EscapeDataRfc3986 { get; set; } /// /// Gets or sets a value whether escaping be done according to the old NLog style (Very non-standard) /// - /// A value of true if legacy encoding; otherwise, false for standard UTF8 encoding. + /// Default: /// [Obsolete("Instead use default Rfc2396 or EscapeDataRfc3986. Marked obsolete with NLog v5.3")] [EditorBrowsable(EditorBrowsableState.Never)] diff --git a/src/NLog/LayoutRenderers/Wrappers/WhenEmptyLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/WhenEmptyLayoutRendererWrapper.cs index 6bd594ce2a..86ae39e285 100644 --- a/src/NLog/LayoutRenderers/Wrappers/WhenEmptyLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/WhenEmptyLayoutRendererWrapper.cs @@ -47,27 +47,43 @@ namespace NLog.LayoutRenderers.Wrappers [ThreadAgnostic] public sealed class WhenEmptyLayoutRendererWrapper : WrapperLayoutRendererBase, IRawValue, IStringValueRenderer { - private bool _skipStringValueRenderer; + private Func? _stringValueRenderer; /// - /// Gets or sets the layout to be rendered when original layout produced empty result. + /// Gets or sets the layout to be rendered when Inner-layout produces empty result. /// + /// Default: /// - [RequiredParameter] - public Layout WhenEmpty { get; set; } + public Layout WhenEmpty { get; set; } = Layout.Empty; /// protected override void InitializeLayoutRenderer() { + _stringValueRenderer = null; + + if (WhenEmpty is null || ReferenceEquals(WhenEmpty, Layout.Empty)) + throw new NLogConfigurationException("WhenEmpty-LayoutRenderer WhenEmpty-property must be assigned."); + base.InitializeLayoutRenderer(); - WhenEmpty?.Initialize(LoggingConfiguration); - _skipStringValueRenderer = !TryGetStringValue(out _, out _); + WhenEmpty.Initialize(LoggingConfiguration); + + if (Inner is SimpleLayout innerLayout && WhenEmpty is SimpleLayout whenEmptyLayout) + { + if ((innerLayout.IsFixedText || innerLayout.IsSimpleStringText) && (whenEmptyLayout.IsFixedText || whenEmptyLayout.IsSimpleStringText)) + { + _stringValueRenderer = (logEvent) => + { + var innerValue = innerLayout.Render(logEvent); + return string.IsNullOrEmpty(innerValue) ? whenEmptyLayout.Render(logEvent) : innerValue; + }; + } + } } /// protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBuilder builder, int orgLength) { - Inner.Render(logEvent, builder); + Inner?.Render(logEvent, builder); if (builder.Length > orgLength) return; @@ -81,43 +97,12 @@ protected override string Transform(string text) throw new NotSupportedException(); } - string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) - { - if (_skipStringValueRenderer) - { - return null; - } - - if (TryGetStringValue(out var innerLayout, out var whenEmptyLayout)) - { - var innerValue = innerLayout.Render(logEvent); - if (!string.IsNullOrEmpty(innerValue)) - { - return innerValue; - } - - // render WhenEmpty when the inner layout was empty - return whenEmptyLayout.Render(logEvent); - } - - _skipStringValueRenderer = true; - return null; - } - - private bool TryGetStringValue(out SimpleLayout innerLayout, out SimpleLayout whenEmptyLayout) - { - whenEmptyLayout = WhenEmpty as SimpleLayout; - innerLayout = Inner as SimpleLayout; - - return IsStringLayout(innerLayout) && IsStringLayout(whenEmptyLayout); - } - - private static bool IsStringLayout(SimpleLayout innerLayout) + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) { - return innerLayout != null && (innerLayout.IsFixedText || innerLayout.IsSimpleStringText); + return _stringValueRenderer?.Invoke(logEvent); } - bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) + bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object? value) { if (Inner?.TryGetRawValue(logEvent, out var innerValue) == true) { diff --git a/src/NLog/LayoutRenderers/Wrappers/WhenLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/WhenLayoutRendererWrapper.cs index 4f1f882497..baf4e7f04c 100644 --- a/src/NLog/LayoutRenderers/Wrappers/WhenLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/WhenLayoutRendererWrapper.cs @@ -55,15 +55,25 @@ public sealed class WhenLayoutRendererWrapper : WrapperLayoutRendererBase, IRawV /// /// Gets or sets the condition that must be met for the layout to be printed. /// + /// [Required] Default: /// - [RequiredParameter] - public ConditionExpression When { get; set; } + public ConditionExpression? When { get; set; } /// /// If is not met, print this layout. /// + /// Default: /// - public Layout Else { get; set; } + public Layout Else { get; set; } = Layout.Empty; + + /// + protected override void InitializeLayoutRenderer() + { + if (When is null) + throw new NLogConfigurationException("When-LayoutRenderer When-property must be assigned."); + + base.InitializeLayoutRenderer(); + } /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) @@ -98,7 +108,7 @@ private bool ShouldRenderInner(LogEventInfo logEvent) return When is null || true.Equals(When.Evaluate(logEvent)); } - bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) + bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object? value) { if (ShouldRenderInner(logEvent)) { @@ -108,7 +118,7 @@ bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) return TryGetRawValueFromLayout(logEvent, Else, out value); } - private static bool TryGetRawValueFromLayout(LogEventInfo logEvent, Layout layout, out object value) + private static bool TryGetRawValueFromLayout(LogEventInfo logEvent, Layout layout, out object? value) { if (layout is null) { diff --git a/src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs index 67ef7f2304..fc87cb5dc1 100644 --- a/src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs @@ -47,11 +47,9 @@ namespace NLog.LayoutRenderers.Wrappers public sealed class WrapLineLayoutRendererWrapper : WrapperLayoutRendererBase { /// - /// Gets or sets the line length for wrapping. + /// Gets or sets the line length for wrapping. Only positive values are allowed. /// - /// - /// Only positive values are allowed - /// + /// Default: /// public int WrapLine { get; set; } = 80; @@ -83,7 +81,7 @@ protected override string Transform(string text) if (chunkLength + pos < text.Length) { - result.Append(Environment.NewLine); + result.AppendLine(); } } diff --git a/src/NLog/LayoutRenderers/Wrappers/WrapperLayoutRendererBase.cs b/src/NLog/LayoutRenderers/Wrappers/WrapperLayoutRendererBase.cs index f61eb0d6b2..b5d682ffaa 100644 --- a/src/NLog/LayoutRenderers/Wrappers/WrapperLayoutRendererBase.cs +++ b/src/NLog/LayoutRenderers/Wrappers/WrapperLayoutRendererBase.cs @@ -34,7 +34,6 @@ namespace NLog.LayoutRenderers.Wrappers { using System.Text; - using NLog.Common; using NLog.Config; using NLog.Layouts; @@ -54,9 +53,10 @@ public abstract class WrapperLayoutRendererBase : LayoutRenderer /// /// [DefaultParameter] so Inner: is not required if it's the first /// + /// Default: /// [DefaultParameter] - public Layout Inner { get; set; } + public Layout Inner { get; set; } = Layout.Empty; /// protected override void InitializeLayoutRenderer() @@ -68,12 +68,6 @@ protected override void InitializeLayoutRenderer() /// protected override void Append(StringBuilder builder, LogEventInfo logEvent) { - if (Inner is null) - { - InternalLogger.Warn("{0} has no configured Inner-Layout, so skipping", this); - return; - } - int orgLength = builder.Length; try { diff --git a/src/NLog/LayoutRenderers/Wrappers/WrapperLayoutRendererBuilderBase.cs b/src/NLog/LayoutRenderers/Wrappers/WrapperLayoutRendererBuilderBase.cs index 0c5d0ac1b6..10d767771e 100644 --- a/src/NLog/LayoutRenderers/Wrappers/WrapperLayoutRendererBuilderBase.cs +++ b/src/NLog/LayoutRenderers/Wrappers/WrapperLayoutRendererBuilderBase.cs @@ -31,11 +31,12 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Text; - namespace NLog.LayoutRenderers.Wrappers { + using System; + using System.Text; + using NLog.Internal; + /// /// Base class for s which wrapping other s. /// @@ -47,11 +48,22 @@ public abstract class WrapperLayoutRendererBuilderBase : WrapperLayoutRendererBa /// protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBuilder builder, int orgLength) { - using (var localTarget = new Internal.AppendBuilderCreator(builder, true)) + if (builder.Length > 0) + { + using (var localTarget = new AppendBuilderCreator(true)) + { +#pragma warning disable CS0618 // Type or member is obsolete + RenderFormattedMessage(logEvent, localTarget.Builder); + TransformFormattedMesssage(logEvent, localTarget.Builder); +#pragma warning restore CS0618 // Type or member is obsolete + localTarget.Builder.CopyTo(builder); + } + } + else { #pragma warning disable CS0618 // Type or member is obsolete - RenderFormattedMessage(logEvent, localTarget.Builder); - TransformFormattedMesssage(logEvent, localTarget.Builder); + RenderFormattedMessage(logEvent, builder); + TransformFormattedMesssage(logEvent, builder); #pragma warning restore CS0618 // Type or member is obsolete } } @@ -78,7 +90,7 @@ protected virtual void TransformFormattedMesssage(LogEventInfo logEvent, StringB [Obsolete("Inherit from WrapperLayoutRendererBase and override RenderInnerAndTransform() instead. Marked obsolete in NLog 4.6")] protected virtual void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target) { - Inner.Render(logEvent, target); + Inner?.Render(logEvent, target); } /// diff --git a/src/NLog/LayoutRenderers/Wrappers/XmlEncodeLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/XmlEncodeLayoutRendererWrapper.cs index 192acd40d7..0758065c07 100644 --- a/src/NLog/LayoutRenderers/Wrappers/XmlEncodeLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/XmlEncodeLayoutRendererWrapper.cs @@ -51,23 +51,44 @@ namespace NLog.LayoutRenderers.Wrappers public sealed class XmlEncodeLayoutRendererWrapper : WrapperLayoutRendererBase { /// - /// Gets or sets whether output should be encoded with Xml-string escaping. + /// Gets or sets whether output should be encoded with XML-string escaping. /// - /// Ensures always valid XML, but gives a performance hit + /// Default: /// public bool XmlEncode { get; set; } = true; + /// + /// Gets or sets whether output should be wrapped using CDATA section instead of XML-string escaping + /// + /// Default: + public bool CDataEncode { get; set; } = false; + /// /// Gets or sets a value indicating whether to transform newlines (\r\n) into ( ) /// + /// Default: /// public bool XmlEncodeNewlines { get; set; } /// protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBuilder builder, int orgLength) { - Inner.Render(logEvent, builder); - if (XmlEncode) + if (CDataEncode) + { + builder.Append(""); + } + else if (XmlEncode) { XmlHelper.PerformXmlEscapeWhenNeeded(builder, orgLength, XmlEncodeNewlines); } @@ -76,6 +97,11 @@ protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBui /// protected override string Transform(string text) { + if (CDataEncode) + { + return XmlHelper.EscapeCData(text); + } + if (XmlEncode) { return XmlHelper.EscapeXmlString(text, XmlEncodeNewlines); diff --git a/src/NLog/Layouts/CSV/CsvColumn.cs b/src/NLog/Layouts/CSV/CsvColumn.cs index 36df9586fb..de2c306642 100644 --- a/src/NLog/Layouts/CSV/CsvColumn.cs +++ b/src/NLog/Layouts/CSV/CsvColumn.cs @@ -38,6 +38,10 @@ namespace NLog.Layouts /// /// A column in the CSV. /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki [NLogConfigurationItem] public class CsvColumn { @@ -45,7 +49,7 @@ public class CsvColumn /// Initializes a new instance of the class. /// public CsvColumn() - : this(null, null) + : this(string.Empty, Layout.Empty) { } @@ -63,21 +67,24 @@ public CsvColumn(string name, Layout layout) /// /// Gets or sets the name of the column. /// + /// [Required] Default: /// public string Name { get; set; } /// - /// Gets or sets the layout of the column. + /// Gets or sets the layout used for rendering the column value. /// + /// [Required] Default: /// - [RequiredParameter] public Layout Layout { get; set; } /// /// Gets or sets the override of Quoting mode /// /// - /// and are faster than the default + /// Default: . + /// + /// For faster performance then consider and /// /// public CsvQuotingMode Quoting { get => _quoting ?? CsvQuotingMode.Auto; set => _quoting = value; } diff --git a/src/NLog/Layouts/CSV/CsvLayout.cs b/src/NLog/Layouts/CSV/CsvLayout.cs index 275b45b133..99ca049caa 100644 --- a/src/NLog/Layouts/CSV/CsvLayout.cs +++ b/src/NLog/Layouts/CSV/CsvLayout.cs @@ -58,7 +58,7 @@ public class CsvLayout : LayoutWithHeaderAndFooter private string _actualColumnDelimiter; private string _doubleQuoteChar; private char[] _quotableCharacters; - private Layout[] _precalculateLayouts; + private Layout[]? _precalculateLayouts; /// /// Initializes a new instance of the class. @@ -68,6 +68,7 @@ public CsvLayout() Layout = this; Header = new CsvHeaderLayout(this); Footer = null; + ResolveQuoteChars(QuoteChar, out _actualColumnDelimiter, out _doubleQuoteChar, out _quotableCharacters); } /// @@ -78,35 +79,39 @@ public CsvLayout() public IList Columns { get; } = new List(); /// - /// Gets or sets a value indicating whether CVS should include header. + /// Gets or sets a value indicating whether CSV should include header. /// - /// A value of true if CVS should include header; otherwise, false. + /// Default: /// public bool WithHeader { get; set; } = true; /// /// Gets or sets the column delimiter. /// + /// Default: /// public CsvColumnDelimiterMode Delimiter { get; set; } = CsvColumnDelimiterMode.Auto; /// /// Gets or sets the quoting mode. /// + /// Default: /// public CsvQuotingMode Quoting { get; set; } = CsvQuotingMode.Auto; /// /// Gets or sets the quote Character. /// + /// Default: " /// public string QuoteChar { get; set; } = "\""; /// /// Gets or sets the custom column delimiter value (valid when is set to ). /// + /// Default: /// - public string CustomColumnDelimiter { get; set; } + public string CustomColumnDelimiter { get; set; } = string.Empty; /// protected override void InitializeLayout() @@ -118,40 +123,51 @@ protected override void InitializeLayout() base.InitializeLayout(); - switch (Delimiter) + ResolveQuoteChars(QuoteChar, out _actualColumnDelimiter, out _doubleQuoteChar, out _quotableCharacters); + + _precalculateLayouts = ResolveLayoutPrecalculation(Columns.Select(cln => cln.Layout)); + + foreach (var csvColumn in Columns) + { + if (string.IsNullOrEmpty(csvColumn.Name)) + throw new NLogConfigurationException("CsvLayout: Contains invalid CsvColumn with unassigned Name-property"); + } + } + + private void ResolveQuoteChars(string quoteChar, out string actualColumnDelimiter, out string doubleQuoteChar, out char[] quotableCharacters) + { + actualColumnDelimiter = ResolveColumnDelimiter(Delimiter); + quotableCharacters = (quoteChar + "\r\n" + actualColumnDelimiter).ToCharArray(); + doubleQuoteChar = quoteChar + quoteChar; + } + + private string ResolveColumnDelimiter(CsvColumnDelimiterMode delimiter) + { + switch (delimiter) { case CsvColumnDelimiterMode.Auto: - _actualColumnDelimiter = CultureInfo.CurrentCulture.TextInfo.ListSeparator; - break; + return CultureInfo.CurrentCulture.TextInfo.ListSeparator; case CsvColumnDelimiterMode.Comma: - _actualColumnDelimiter = ","; - break; + return ","; case CsvColumnDelimiterMode.Semicolon: - _actualColumnDelimiter = ";"; - break; + return ";"; case CsvColumnDelimiterMode.Pipe: - _actualColumnDelimiter = "|"; - break; + return "|"; case CsvColumnDelimiterMode.Tab: - _actualColumnDelimiter = "\t"; - break; + return "\t"; case CsvColumnDelimiterMode.Space: - _actualColumnDelimiter = " "; - break; + return " "; case CsvColumnDelimiterMode.Custom: - _actualColumnDelimiter = CustomColumnDelimiter; - break; + return string.IsNullOrEmpty(CustomColumnDelimiter) ? ";" : CustomColumnDelimiter; } - _quotableCharacters = (QuoteChar + "\r\n" + _actualColumnDelimiter).ToCharArray(); - _doubleQuoteChar = QuoteChar + QuoteChar; - _precalculateLayouts = ResolveLayoutPrecalculation(Columns.Select(cln => cln.Layout)); + return ";"; } /// @@ -231,7 +247,7 @@ private void RenderHeader(StringBuilder sb) for (int i = 0; i < Columns.Count; i++) { CsvColumn col = Columns[i]; - var columnLayout = new SimpleLayout(new LayoutRenderers.LayoutRenderer[] { new LayoutRenderers.LiteralLayoutRenderer(col.Name) }, col.Name, ConfigurationItemFactory.Default); + var columnLayout = Layout.FromLiteral(col.Name); columnLayout.Initialize(LoggingConfiguration); RenderColumnLayout(logEvent, columnLayout, col._quoting ?? Quoting, sb, i); } @@ -264,7 +280,7 @@ private bool ColumnValueRequiresQuotes(CsvQuotingMode quoting, StringBuilder sb, internal sealed class CsvHeaderLayout : Layout { private readonly CsvLayout _parent; - private string _headerOutput; + private string? _headerOutput; /// /// Initializes a new instance of the class. diff --git a/src/NLog/Layouts/CompoundLayout.cs b/src/NLog/Layouts/CompoundLayout.cs index 94154d1d87..20cf03f1f4 100644 --- a/src/NLog/Layouts/CompoundLayout.cs +++ b/src/NLog/Layouts/CompoundLayout.cs @@ -31,7 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // - namespace NLog.Layouts { using System.Collections.Generic; @@ -50,7 +49,7 @@ namespace NLog.Layouts [AppDomainFixedOutput] public class CompoundLayout : Layout { - private Layout[] _precalculateLayouts; + private Layout[]? _precalculateLayouts; /// /// Initializes a new instance of the class. diff --git a/src/NLog/Layouts/JSON/JsonArrayLayout.cs b/src/NLog/Layouts/JSON/JsonArrayLayout.cs index 761c1be66e..a5b16dac37 100644 --- a/src/NLog/Layouts/JSON/JsonArrayLayout.cs +++ b/src/NLog/Layouts/JSON/JsonArrayLayout.cs @@ -48,14 +48,10 @@ namespace NLog.Layouts [ThreadAgnostic] public class JsonArrayLayout : Layout { - private Layout[] _precalculateLayouts; + private Layout[]? _precalculateLayouts; - private IJsonConverter JsonConverter - { - get => _jsonConverter ?? (_jsonConverter = ResolveService()); - set => _jsonConverter = value; - } - private IJsonConverter _jsonConverter; + private IJsonConverter JsonConverter => _jsonConverter ?? (_jsonConverter = ResolveService()); + private IJsonConverter? _jsonConverter; /// /// Gets the array of items to include in JSON-Array @@ -65,14 +61,16 @@ private IJsonConverter JsonConverter public IList Items { get; } = new List(); /// - /// Gets or sets the option to suppress the extra spaces in the output json + /// Gets or sets the option to suppress the extra spaces in the output json. /// + /// Default: /// - public bool SuppressSpaces { get; set; } + public bool SuppressSpaces { get; set; } = true; /// /// Gets or sets the option to render the empty object value {} /// + /// Default: /// public bool RenderEmptyObject { get; set; } = true; @@ -86,7 +84,7 @@ protected override void InitializeLayout() /// protected override void CloseLayout() { - JsonConverter = null; + _jsonConverter = null; _precalculateLayouts = null; base.CloseLayout(); } @@ -148,7 +146,7 @@ private bool RenderLayoutJsonValue(LogEventInfo logEvent, Layout layout, StringB { layout.Render(logEvent, sb); } - else if (layout.TryGetRawValue(logEvent, out object rawValue)) + else if (layout.TryGetRawValue(logEvent, out var rawValue)) { if (!JsonConverter.SerializeObject(rawValue, sb)) { @@ -162,7 +160,7 @@ private bool RenderLayoutJsonValue(LogEventInfo logEvent, Layout layout, StringB layout.Render(logEvent, sb); if (beforeValueLength != sb.Length) { - NLog.Targets.DefaultJsonSerializer.PerformJsonEscapeWhenNeeded(sb, beforeValueLength, true, false); + NLog.Targets.DefaultJsonSerializer.PerformJsonEscapeWhenNeeded(sb, beforeValueLength, true); sb.Append('"'); } } diff --git a/src/NLog/Layouts/JSON/JsonAttribute.cs b/src/NLog/Layouts/JSON/JsonAttribute.cs index 86f39ac453..11ec3eb128 100644 --- a/src/NLog/Layouts/JSON/JsonAttribute.cs +++ b/src/NLog/Layouts/JSON/JsonAttribute.cs @@ -34,12 +34,17 @@ namespace NLog.Layouts { using System; + using System.ComponentModel; using System.Text; using NLog.Config; /// /// JSON attribute. /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki [NLogConfigurationItem] public class JsonAttribute { @@ -48,7 +53,7 @@ public class JsonAttribute /// /// Initializes a new instance of the class. /// - public JsonAttribute() : this(null, null, true) { } + public JsonAttribute() : this(string.Empty, Layout.Empty, true) { } /// /// Initializes a new instance of the class. @@ -66,16 +71,16 @@ public JsonAttribute(string name, Layout layout) : this(name, layout, true) { } public JsonAttribute(string name, Layout layout, bool encode) { Name = name; + _name = Name; Layout = layout; Encode = encode; - IncludeEmptyValue = false; } /// /// Gets or sets the name of the attribute. /// + /// [Required] Default: /// - [RequiredParameter] public string Name { get => _name; @@ -88,7 +93,7 @@ public string Name else { var builder = new System.Text.StringBuilder(); - Targets.DefaultJsonSerializer.AppendStringEscape(builder, value, false, false); + Targets.DefaultJsonSerializer.AppendStringEscape(builder, value.Trim(), false); _name = builder.ToString(); } } @@ -96,49 +101,53 @@ public string Name private string _name; /// - /// Gets or sets the layout that will be rendered as the attribute's value. + /// Gets or sets the layout used for rendering the attribute value. /// + /// [Required] Default: /// - [RequiredParameter] public Layout Layout { get => _layoutInfo.Layout; set => _layoutInfo.Layout = value; } /// /// Gets or sets the result value type, for conversion of layout rendering output /// + /// Default: /// - public Type ValueType { get => _layoutInfo.ValueType; set => _layoutInfo.ValueType = value; } + public Type? ValueType { get => _layoutInfo.ValueType; set => _layoutInfo.ValueType = value; } /// /// Gets or sets the fallback value when result value is not available /// + /// Default: /// - public Layout DefaultValue { get => _layoutInfo.DefaultValue; set => _layoutInfo.DefaultValue = value; } + public Layout? DefaultValue { get => _layoutInfo.DefaultValue; set => _layoutInfo.DefaultValue = value; } /// /// Gets or sets whether output should be encoded as Json-String-Property, or be treated as valid json. /// + /// Default: /// public bool Encode { get; set; } /// /// Gets or sets a value indicating whether to escape non-ascii characters /// + /// Default: /// public bool EscapeUnicode { get; set; } /// /// Should forward slashes be escaped? If true, / will be converted to \/ /// - /// - /// If not set explicitly then the value of the parent will be used as default. - /// + /// Default: /// - public bool EscapeForwardSlash { get => EscapeForwardSlashInternal ?? false; set => EscapeForwardSlashInternal = value; } - internal bool? EscapeForwardSlashInternal { get; private set; } + [Obsolete("Marked obsolete since forward slash are valid JSON. Marked obsolete with NLog v5.4")] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool EscapeForwardSlash { get; set; } /// - /// Gets or sets whether an attribute with empty value should be included in the output + /// Gets or sets whether empty attribute value should be included in the output. /// + /// Default: . Empty value is either null or empty string /// public bool IncludeEmptyValue { @@ -170,7 +179,7 @@ internal bool RenderAppendJsonValue(LogEventInfo logEvent, IJsonConverter jsonCo if (Encode) { - Targets.DefaultJsonSerializer.PerformJsonEscapeWhenNeeded(builder, orgLength, EscapeUnicode, EscapeForwardSlash); + Targets.DefaultJsonSerializer.PerformJsonEscapeWhenNeeded(builder, orgLength, EscapeUnicode); builder.Append('"'); } } diff --git a/src/NLog/Layouts/JSON/JsonLayout.cs b/src/NLog/Layouts/JSON/JsonLayout.cs index fe27503525..6b396f0cda 100644 --- a/src/NLog/Layouts/JSON/JsonLayout.cs +++ b/src/NLog/Layouts/JSON/JsonLayout.cs @@ -52,35 +52,27 @@ namespace NLog.Layouts public class JsonLayout : Layout { private const int SpacesPerIndent = 2; - private Layout[] _precalculateLayouts; + private Layout[]? _precalculateLayouts; - private LimitRecursionJsonConvert JsonConverter - { - get => _jsonConverter ?? (_jsonConverter = new LimitRecursionJsonConvert(MaxRecursionLimit, EscapeForwardSlash, ResolveService())); - set => _jsonConverter = value; - } - private LimitRecursionJsonConvert _jsonConverter; - private IValueFormatter ValueFormatter - { - get => _valueFormatter ?? (_valueFormatter = ResolveService()); - set => _valueFormatter = value; - } - private IValueFormatter _valueFormatter; + private LimitRecursionJsonConvert JsonConverter => _jsonConverter ?? (_jsonConverter = new LimitRecursionJsonConvert(MaxRecursionLimit, SuppressSpaces, ResolveService())); + private LimitRecursionJsonConvert? _jsonConverter; + private IValueFormatter ValueFormatter => _valueFormatter ?? (_valueFormatter = ResolveService()); + private IValueFormatter? _valueFormatter; - class LimitRecursionJsonConvert : IJsonConverter + private sealed class LimitRecursionJsonConvert : IJsonConverter { - readonly IJsonConverter _converter; - readonly Targets.DefaultJsonSerializer _serializer; - readonly Targets.JsonSerializeOptions _serializerOptions; + private readonly IJsonConverter _converter; + private readonly Targets.DefaultJsonSerializer? _serializer; + private readonly Targets.JsonSerializeOptions _serializerOptions; - public LimitRecursionJsonConvert(int maxRecursionLimit, bool escapeForwardSlash, IJsonConverter converter) + public LimitRecursionJsonConvert(int maxRecursionLimit, bool suppressSpaces, IJsonConverter converter) { _converter = converter; _serializer = converter as Targets.DefaultJsonSerializer; - _serializerOptions = new Targets.JsonSerializeOptions() { MaxRecursionLimit = Math.Max(0, maxRecursionLimit), EscapeForwardSlash = escapeForwardSlash }; + _serializerOptions = new Targets.JsonSerializeOptions() { MaxRecursionLimit = Math.Max(0, maxRecursionLimit), SuppressSpaces = suppressSpaces, SanitizeDictionaryKeys = true }; } - public bool SerializeObject(object value, StringBuilder builder) + public bool SerializeObject(object? value, StringBuilder builder) { if (_serializer != null) return _serializer.SerializeObject(value, builder, _serializerOptions); @@ -88,7 +80,7 @@ public bool SerializeObject(object value, StringBuilder builder) return _converter.SerializeObject(value, builder); } - public bool SerializeObjectNoLimit(object value, StringBuilder builder) + public bool SerializeObjectNoLimit(object? value, StringBuilder builder) { return _converter.SerializeObject(value, builder); } @@ -107,11 +99,13 @@ public JsonLayout() /// /// [ArrayParameter(typeof(JsonAttribute), "attribute")] - public IList Attributes { get; } = new List(); + public IList Attributes => _attributes; + private readonly List _attributes = new List(); /// - /// Gets or sets the option to suppress the extra spaces in the output json + /// Gets or sets the option to suppress the extra spaces in the output json. /// + /// Default: /// public bool SuppressSpaces { @@ -125,11 +119,12 @@ public bool SuppressSpaces } } } - private bool _suppressSpaces; + private bool _suppressSpaces = true; /// /// Gets or sets the option to render the empty object value {} /// + /// Default: /// public bool RenderEmptyObject { get => _renderEmptyObject ?? true; set => _renderEmptyObject = value; } private bool? _renderEmptyObject; @@ -137,6 +132,7 @@ public bool SuppressSpaces /// /// Auto indent and create new lines /// + /// Default: /// public bool IndentJson { @@ -146,6 +142,8 @@ public bool IndentJson if (_indentJson != value) { _indentJson = value; + if (_indentJson) + _suppressSpaces = false; RefreshJsonDelimiters(); } } @@ -155,18 +153,21 @@ public bool IndentJson /// /// Gets or sets the option to include all properties from the log event (as JSON) /// + /// Default: /// public bool IncludeEventProperties { get; set; } /// /// Gets or sets a value indicating whether to include contents of the dictionary. /// + /// Default: /// public bool IncludeGdc { get; set; } /// /// Gets or sets whether to include the contents of the dictionary. /// + /// Default: /// public bool IncludeScopeProperties { get => _includeScopeProperties ?? (_includeMdlc == true || _includeMdc == true); set => _includeScopeProperties = value; } private bool? _includeScopeProperties; @@ -176,6 +177,7 @@ public bool IndentJson /// /// Gets or sets the option to include all properties from the log event (as JSON) /// + /// Default: /// [Obsolete("Replaced by IncludeEventProperties. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -186,6 +188,7 @@ public bool IndentJson /// /// Gets or sets a value indicating whether to include contents of the dictionary. /// + /// Default: /// [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -197,6 +200,7 @@ public bool IndentJson /// /// Gets or sets a value indicating whether to include contents of the dictionary. /// + /// Default: /// [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -206,6 +210,7 @@ public bool IndentJson /// /// Gets or sets the option to exclude null/empty properties from the log event (as JSON) /// + /// Default: /// public bool ExcludeEmptyProperties { get; set; } @@ -222,22 +227,18 @@ public bool IndentJson /// /// How far should the JSON serializer follow object references before backing off /// + /// Default: /// public int MaxRecursionLimit { get; set; } = 1; /// - /// Should forward slashes be escaped? If true, / will be converted to \/ + /// Should forward slashes be escaped? If , / will be converted to \/ /// - /// - /// If not set explicitly then the value of the parent will be used as default. - /// + /// Default: /// - public bool EscapeForwardSlash - { - get => _escapeForwardSlashInternal ?? false; - set => _escapeForwardSlashInternal = value; - } - private bool? _escapeForwardSlashInternal; + [Obsolete("Marked obsolete with NLog 5.5. Should never escape forward slash")] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool EscapeForwardSlash { get; set; } /// protected override void InitializeLayout() @@ -255,25 +256,18 @@ protected override void InitializeLayout() _precalculateLayouts = (IncludeScopeProperties || IncludeEventProperties) ? null : ResolveLayoutPrecalculation(Attributes.Select(atr => atr.Layout)); - if (_escapeForwardSlashInternal.HasValue && Attributes?.Count > 0) + foreach (var attribute in _attributes) { - foreach (var attribute in Attributes) - { - if (!attribute.EscapeForwardSlashInternal.HasValue) - { - attribute.EscapeForwardSlash = _escapeForwardSlashInternal.Value; - } - } - } + if (string.IsNullOrEmpty(attribute.Name)) + throw new NLogConfigurationException("JsonLayout: Contains invalid JsonAttribute with unassigned Name-property"); - if (Attributes?.Count > 0) - { - foreach (var attribute in Attributes) + if (!attribute.Encode && attribute.Layout is JsonLayout jsonLayout) { - if (!attribute.IncludeEmptyValue && !attribute.Encode && attribute.Layout is JsonLayout jsonLayout && !jsonLayout._renderEmptyObject.HasValue) - { + if (!attribute.IncludeEmptyValue && !jsonLayout._renderEmptyObject.HasValue) jsonLayout.RenderEmptyObject = false; - } + + if (!SuppressSpaces || IndentJson) + jsonLayout.SuppressSpaces = false; } } } @@ -281,8 +275,8 @@ protected override void InitializeLayout() /// protected override void CloseLayout() { - JsonConverter = null; - ValueFormatter = null; + _jsonConverter = null; + _valueFormatter = null; _precalculateLayouts = null; base.CloseLayout(); } @@ -313,13 +307,10 @@ private void RenderJsonFormattedMessage(LogEventInfo logEvent, StringBuilder sb) { int orgLength = sb.Length; - //Memory profiling pointed out that using a foreach-loop was allocating - //an Enumerator. Switching to a for-loop avoids the memory allocation. - for (int i = 0; i < Attributes.Count; i++) + foreach (var attribute in _attributes) { - var attrib = Attributes[i]; int beforeAttribLength = sb.Length; - if (!RenderAppendJsonPropertyValue(attrib, logEvent, sb, beforeAttribLength == orgLength)) + if (!RenderAppendJsonPropertyValue(attribute, logEvent, sb, beforeAttribLength == orgLength)) { sb.Length = beforeAttribLength; } @@ -334,7 +325,8 @@ private void RenderJsonFormattedMessage(LogEventInfo logEvent, StringBuilder sb) { if (string.IsNullOrEmpty(key)) continue; - object propertyValue = GlobalDiagnosticsContext.GetObject(key); + + var propertyValue = GlobalDiagnosticsContext.GetObject(key); AppendJsonPropertyValue(key, propertyValue, sb, sb.Length == orgLength); } } @@ -362,7 +354,7 @@ private void RenderJsonFormattedMessage(LogEventInfo logEvent, StringBuilder sb) if (IncludeEventProperties && logEvent.HasProperties) { bool checkExcludeProperties = ExcludeProperties.Count > 0; - using (var propertyEnumerator = logEvent.CreateOrUpdatePropertiesInternal().GetPropertyEnumerator()) + using (var propertyEnumerator = logEvent.CreatePropertiesInternal().GetPropertyEnumerator()) { while (propertyEnumerator.MoveNext()) { @@ -387,17 +379,17 @@ private void BeginJsonProperty(StringBuilder sb, string propName, bool beginJson sb.Append(beginJsonMessage ? _beginJsonMessage : _beginJsonPropertyName); if (ensureStringEscape) - Targets.DefaultJsonSerializer.AppendStringEscape(sb, propName, false, false); + Targets.DefaultJsonSerializer.AppendStringEscape(sb, propName, false); else sb.Append(propName); sb.Append(_completeJsonPropertyName); } - private string _beginJsonMessage = "{ \""; - private string _completeJsonMessage = " }"; - private string _beginJsonPropertyName = ", \""; - private string _completeJsonPropertyName = "\": "; + private string _beginJsonMessage = "{\""; + private string _completeJsonMessage = "}"; + private string _beginJsonPropertyName = ",\""; + private string _completeJsonPropertyName = "\":"; private void RefreshJsonDelimiters() { @@ -419,7 +411,7 @@ private void RefreshJsonDelimiters() _completeJsonPropertyName = SuppressSpaces ? "\":" : "\": "; } - private void AppendJsonPropertyValue(string propName, object propertyValue, StringBuilder sb, bool beginJsonMessage) + private void AppendJsonPropertyValue(string propName, object? propertyValue, StringBuilder sb, bool beginJsonMessage) { if (ExcludeEmptyProperties && (propertyValue is null || ReferenceEquals(propertyValue, string.Empty))) return; @@ -438,7 +430,7 @@ private void AppendJsonPropertyValue(string propName, object propertyValue, Stri } } - private void AppendJsonPropertyValue(string propName, object propertyValue, string format, IFormatProvider formatProvider, MessageTemplates.CaptureType captureType, StringBuilder sb, bool beginJsonMessage) + private void AppendJsonPropertyValue(string propName, object? propertyValue, string? format, IFormatProvider? formatProvider, MessageTemplates.CaptureType captureType, StringBuilder sb, bool beginJsonMessage) { if (captureType == MessageTemplates.CaptureType.Serialize && MaxRecursionLimit <= 1) { @@ -464,7 +456,7 @@ private void AppendJsonPropertyValue(string propName, object propertyValue, stri // Overrides MaxRecursionLimit as message-template tells us it is unsafe int originalStart = sb.Length; ValueFormatter.FormatValue(propertyValue, format, captureType, formatProvider, sb); - PerformJsonEscapeIfNeeded(sb, originalStart, EscapeForwardSlash); + PerformJsonEscapeIfNeeded(sb, originalStart); } else { @@ -472,7 +464,7 @@ private void AppendJsonPropertyValue(string propName, object propertyValue, stri } } - private static void PerformJsonEscapeIfNeeded(StringBuilder sb, int valueStart, bool escapeForwardSlash) + private static void PerformJsonEscapeIfNeeded(StringBuilder sb, int valueStart) { var builderLength = sb.Length; if (builderLength - valueStart <= 2) @@ -480,12 +472,12 @@ private static void PerformJsonEscapeIfNeeded(StringBuilder sb, int valueStart, for (int i = valueStart + 1; i < builderLength - 1; ++i) { - if (Targets.DefaultJsonSerializer.RequiresJsonEscape(sb[i], false, escapeForwardSlash)) + if (Targets.DefaultJsonSerializer.RequiresJsonEscape(sb[i], false)) { var jsonEscape = sb.ToString(valueStart + 1, sb.Length - valueStart - 2); sb.Length = valueStart; sb.Append('"'); - Targets.DefaultJsonSerializer.AppendStringEscape(sb, jsonEscape, false, escapeForwardSlash); + Targets.DefaultJsonSerializer.AppendStringEscape(sb, jsonEscape, false); sb.Append('"'); break; } diff --git a/src/NLog/Layouts/Layout.cs b/src/NLog/Layouts/Layout.cs index 4ed95ac3f9..06cc973925 100644 --- a/src/NLog/Layouts/Layout.cs +++ b/src/NLog/Layouts/Layout.cs @@ -39,7 +39,6 @@ namespace NLog.Layouts using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; - using JetBrains.Annotations; using NLog.Common; using NLog.Config; using NLog.Internal; @@ -51,6 +50,11 @@ namespace NLog.Layouts [NLogConfigurationItem] public abstract class Layout : ISupportsInitialize, IRenderable { + /// + /// Default Layout-value that renders string.Empty + /// + public static readonly Layout Empty = new SimpleLayout(); + /// /// Is this layout initialized? See /// @@ -78,21 +82,23 @@ public abstract class Layout : ISupportsInitialize, IRenderable /// /// Gets the logging configuration this target is part of. /// - [CanBeNull] - protected internal LoggingConfiguration LoggingConfiguration { get; private set; } + protected internal LoggingConfiguration? LoggingConfiguration { get; private set; } /// - /// Converts a given text to a . + /// Implicitly converts the specified string as LayoutRenderer-expression into a . /// /// Text to be converted. /// object represented by the text. public static implicit operator Layout([Localizable(false)] string text) { - return FromString(text, ConfigurationItemFactory.Default); + if (text is null) + return new Layout(null); + else + return FromString(text, ConfigurationItemFactory.Default); } /// - /// Implicitly converts the specified string to a . + /// Parses the specified string as LayoutRenderer-expression into a . /// /// The layout string. /// Instance of .' @@ -102,27 +108,27 @@ public static Layout FromString([Localizable(false)] string layoutText) } /// - /// Implicitly converts the specified string to a . + /// Parses the specified string as LayoutRenderer-expression into a . /// /// The layout string. /// The NLog factories to use when resolving layout renderers. /// Instance of . public static Layout FromString([Localizable(false)] string layoutText, ConfigurationItemFactory configurationItemFactory) { - return new SimpleLayout(layoutText, configurationItemFactory); + return string.IsNullOrEmpty(layoutText) ? Layout.Empty : new SimpleLayout(layoutText, configurationItemFactory); } /// - /// Implicitly converts the specified string to a . + /// Parses the specified string as LayoutRenderer-expression into a . /// /// The layout string. - /// Whether should be thrown on parse errors (false = replace unrecognized tokens with a space). + /// Whether should be thrown on parse errors ( = replace unrecognized tokens with a space). /// Instance of . public static Layout FromString([Localizable(false)] string layoutText, bool throwConfigExceptions) { try { - return new SimpleLayout(layoutText, ConfigurationItemFactory.Default, throwConfigExceptions); + return string.IsNullOrEmpty(layoutText) ? Layout.Empty : new SimpleLayout(layoutText, ConfigurationItemFactory.Default, throwConfigExceptions); } catch (NLogConfigurationException) { @@ -137,6 +143,17 @@ public static Layout FromString([Localizable(false)] string layoutText, bool thr } } + /// + /// Create a containing literal value + /// + public static Layout FromLiteral([Localizable(false)] string literalText) + { + if (string.IsNullOrEmpty(literalText)) + return Layout.Empty; + else + return new SimpleLayout(new[] { new NLog.LayoutRenderers.LiteralLayoutRenderer(literalText) }, literalText); + } + /// /// Create a from a lambda method. /// @@ -147,16 +164,12 @@ public static Layout FromMethod(Func layoutMethod, LayoutR { Guard.ThrowIfNull(layoutMethod); -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 var name = $"{layoutMethod.Method?.DeclaringType?.ToString()}.{layoutMethod.Method?.Name}"; -#else - var name = $"{layoutMethod.Target?.ToString()}"; -#endif var layoutRenderer = CreateFuncLayoutRenderer((l, c) => layoutMethod(l), options, name); - return new SimpleLayout(new[] { layoutRenderer }, layoutRenderer.LayoutRendererName, ConfigurationItemFactory.Default); + return new SimpleLayout(new[] { layoutRenderer }, layoutRenderer.LayoutRendererName); } - internal static LayoutRenderers.FuncLayoutRenderer CreateFuncLayoutRenderer(Func layoutMethod, LayoutRenderOptions options, string name) + internal static LayoutRenderers.FuncLayoutRenderer CreateFuncLayoutRenderer(Func layoutMethod, LayoutRenderOptions options, string name) { if ((options & LayoutRenderOptions.ThreadAgnostic) == LayoutRenderOptions.ThreadAgnostic) return new LayoutRenderers.FuncThreadAgnosticLayoutRenderer(name, layoutMethod); @@ -168,13 +181,11 @@ internal static LayoutRenderers.FuncLayoutRenderer CreateFuncLayoutRenderer(Func /// Precalculates the layout for the specified log event and stores the result /// in per-log event cache. /// - /// Only if the layout doesn't have [ThreadAgnostic] and doesn't contain layouts with [ThreadAgnostic]. + /// Skips context capture when Layout have [ThreadAgnostic], and only contains layouts with [ThreadAgnostic]. /// /// The log event. /// - /// Calling this method enables you to store the log event in a buffer - /// and/or potentially evaluate it in another thread even though the - /// layout may contain thread-dependent renderer. + /// Override this method to make it conditional whether to capture Layout output-value for /// public virtual void Precalculate(LogEventInfo logEvent) { @@ -182,7 +193,7 @@ public virtual void Precalculate(LogEventInfo logEvent) { using (var localTarget = new AppendBuilderCreator(true)) { - RenderAppendBuilder(logEvent, localTarget.Builder, true); + PrecalculateCachedLayoutValue(logEvent, localTarget.Builder); } } } @@ -194,35 +205,21 @@ public virtual void Precalculate(LogEventInfo logEvent) /// The logging event. /// The formatted output as string. public string Render(LogEventInfo logEvent) - { - return Render(logEvent, true); - } - - internal string Render(LogEventInfo logEvent, bool cacheLayoutResult) { if (!IsInitialized) { Initialize(LoggingConfiguration); } - bool lookupCacheLayout = !ThreadAgnostic || ThreadAgnosticImmutable; - if (lookupCacheLayout) + if (!ThreadAgnostic || ThreadAgnosticImmutable) { - object cachedValue; - if (logEvent.TryGetCachedLayoutValue(this, out cachedValue)) + if (logEvent.TryGetCachedLayoutValue(this, out var cachedValue)) { return cachedValue?.ToString() ?? string.Empty; } } - string layoutValue = GetFormattedMessage(logEvent) ?? string.Empty; - if (lookupCacheLayout && cacheLayoutResult) - { - // Would be nice to only do this in Precalculate(), but we need to ensure internal cache - // is updated for custom Layouts that overrides Precalculate (without calling base.Precalculate) - logEvent.AddCachedLayoutValue(this, layoutValue); - } - return layoutValue; + return GetFormattedMessage(logEvent) ?? string.Empty; } /// @@ -232,23 +229,6 @@ internal string Render(LogEventInfo logEvent, bool cacheLayoutResult) /// The logging event. /// Appends the formatted output to target public void Render(LogEventInfo logEvent, StringBuilder target) - { - RenderAppendBuilder(logEvent, target, false); - } - - internal virtual void PrecalculateBuilder(LogEventInfo logEvent, StringBuilder target) - { - Precalculate(logEvent); // Allow custom Layouts to also work - } - - /// - /// Optimized version of that works best when - /// override of is available. - /// - /// The logging event. - /// Appends the string representing log event to target - /// Should rendering result be cached on LogEventInfo - private void RenderAppendBuilder(LogEventInfo logEvent, StringBuilder target, bool cacheLayoutResult) { if (!IsInitialized) { @@ -257,26 +237,35 @@ private void RenderAppendBuilder(LogEventInfo logEvent, StringBuilder target, bo if (!ThreadAgnostic || ThreadAgnosticImmutable) { - object cachedValue; - if (logEvent.TryGetCachedLayoutValue(this, out cachedValue)) + if (logEvent.TryGetCachedLayoutValue(this, out var cachedValue)) { target.Append(cachedValue?.ToString()); return; } } - else + + RenderFormattedMessage(logEvent, target); + } + + internal virtual void PrecalculateBuilder(LogEventInfo logEvent, StringBuilder target) + { + Precalculate(logEvent); // Allow custom Layouts to also work + } + + private void PrecalculateCachedLayoutValue(LogEventInfo logEvent, StringBuilder target) + { + if (!IsInitialized) { - cacheLayoutResult = false; + Initialize(LoggingConfiguration); } - using (var localTarget = new AppendBuilderCreator(target, cacheLayoutResult)) + if (!ThreadAgnostic || ThreadAgnosticImmutable) { - RenderFormattedMessage(logEvent, localTarget.Builder); - if (cacheLayoutResult) - { - // when needed as it generates garbage - logEvent.AddCachedLayoutValue(this, localTarget.Builder.ToString()); - } + if (logEvent.TryGetCachedLayoutValue(this, out var _)) + return; // Precalculate already captured + + RenderFormattedMessage(logEvent, target); + logEvent.AddCachedLayoutValue(this, target.ToString()); } } @@ -314,7 +303,7 @@ protected virtual void RenderFormattedMessage(LogEventInfo logEvent, StringBuild /// Initializes this instance. /// /// The configuration. - void ISupportsInitialize.Initialize(LoggingConfiguration configuration) + void ISupportsInitialize.Initialize(LoggingConfiguration? configuration) { Initialize(configuration); } @@ -331,7 +320,7 @@ void ISupportsInitialize.Close() /// Initializes this instance. /// /// The configuration. - internal void Initialize(LoggingConfiguration configuration) + internal void Initialize(LoggingConfiguration? configuration) { if (!IsInitialized) { @@ -341,8 +330,6 @@ internal void Initialize(LoggingConfiguration configuration) _scannedForObjects = false; - PropertyHelper.CheckRequiredParameters(ConfigurationItemFactory.Default, this); - InitializeLayout(); if (!_scannedForObjects) @@ -364,6 +351,7 @@ internal void PerformObjectScanning() var objectGraphTypes = new HashSet(objectGraphScannerList.Select(o => o.GetType())); objectGraphTypes.Remove(typeof(SimpleLayout)); objectGraphTypes.Remove(typeof(NLog.LayoutRenderers.LiteralLayoutRenderer)); + objectGraphTypes.Remove(typeof(NLog.LayoutRenderers.LiteralWithRawValueLayoutRenderer)); // determine whether the layout is thread-agnostic // layout is thread agnostic if it is thread-agnostic and @@ -391,7 +379,7 @@ internal void PerformObjectScanning() _scannedForObjects = true; } - internal Layout[] ResolveLayoutPrecalculation(IEnumerable allLayouts) + internal Layout[]? ResolveLayoutPrecalculation(IEnumerable allLayouts) { if (!_scannedForObjects || (ThreadAgnostic && !ThreadAgnosticImmutable)) return null; @@ -494,20 +482,20 @@ public static void Register(string name, [DynamicallyAccessedMembers(Dynamically /// Optimized version of for internal Layouts, when /// override of is available. /// - internal void PrecalculateBuilderInternal(LogEventInfo logEvent, StringBuilder target, Layout[] precalculateLayout) + internal void PrecalculateBuilderInternal(LogEventInfo logEvent, StringBuilder target, Layout[]? precalculateLayout) { if (!ThreadAgnostic || ThreadAgnosticImmutable) { if (precalculateLayout is null) { - RenderAppendBuilder(logEvent, target, true); + PrecalculateCachedLayoutValue(logEvent, target); } else { foreach (var layout in precalculateLayout) { + target.ClearBuilder(); layout.PrecalculateBuilder(logEvent, target); - target.Length = 0; } } } @@ -527,9 +515,9 @@ internal string ToStringWithNestedItems(IList nestedItems, Func /// Try get value /// /// - /// rawValue if return result is true - /// false if we could not determine the rawValue - internal virtual bool TryGetRawValue(LogEventInfo logEvent, out object rawValue) + /// rawValue if return result is + /// if we could not determine the rawValue + internal virtual bool TryGetRawValue(LogEventInfo logEvent, out object? rawValue) { rawValue = null; return false; diff --git a/src/NLog/Layouts/LayoutAttribute.cs b/src/NLog/Layouts/LayoutAttribute.cs index 961a21422d..bc44fe134c 100644 --- a/src/NLog/Layouts/LayoutAttribute.cs +++ b/src/NLog/Layouts/LayoutAttribute.cs @@ -34,7 +34,7 @@ namespace NLog.Layouts { using System; - using Config; + using NLog.Config; /// /// Marks class as Layout and attaches a type-alias name for use in NLog configuration. diff --git a/src/NLog/Layouts/LayoutParser.cs b/src/NLog/Layouts/LayoutParser.cs index 5935dfa58e..b65ed14584 100644 --- a/src/NLog/Layouts/LayoutParser.cs +++ b/src/NLog/Layouts/LayoutParser.cs @@ -52,21 +52,21 @@ internal static class LayoutParser { private static readonly char[] SpecialTokens = new char[] { '$', '\\', '}', ':' }; - internal static LayoutRenderer[] CompileLayout(string value, ConfigurationItemFactory configurationItemFactory, bool? throwConfigExceptions, out string text) + internal static LayoutRenderer[] CompileLayout(string value, ConfigurationItemFactory configurationItemFactory, bool? throwConfigExceptions, out string parsedText) { - if (value is null) + if (string.IsNullOrEmpty(value)) { - text = string.Empty; + parsedText = string.Empty; return ArrayHelper.Empty(); } else if (value.Length < 128 && value.IndexOfAny(SpecialTokens) < 0) { - text = value; + parsedText = value; return new LayoutRenderer[] { new LiteralLayoutRenderer(value) }; } else { - return CompileLayout(configurationItemFactory, new SimpleStringReader(value), throwConfigExceptions, false, out text); + return CompileLayout(configurationItemFactory, new SimpleStringReader(value), throwConfigExceptions, false, out parsedText); } } @@ -299,7 +299,7 @@ private static string ParseParameterValue(SimpleStringReader sr) return value; } - private static string EscapeUnicodeStringValue(string value, StringBuilder nameBuf = null) + private static string EscapeUnicodeStringValue(string value, StringBuilder? nameBuf = null) { bool escapeNext = false; @@ -424,10 +424,10 @@ private static LayoutRenderer ParseLayoutRenderer(ConfigurationItemFactory confi string typeName = ParseLayoutRendererTypeName(stringReader); var layoutRenderer = GetLayoutRenderer(typeName, configurationItemFactory, throwConfigExceptions); - Dictionary wrappers = null; - List orderedWrappers = null; + Dictionary? wrappers = null; + List? orderedWrappers = null; - string previousParameterName = null; + string? previousParameterName = null; ch = stringReader.Read(); while (ch != -1 && ch != '}') @@ -442,11 +442,12 @@ private static LayoutRenderer ParseLayoutRenderer(ConfigurationItemFactory confi if (!PropertyHelper.TryGetPropertyInfo(configurationItemFactory, layoutRenderer, parameterName, out var propertyInfo)) { - parameterTarget = LookupAmbientProperty(parameterName, configurationItemFactory, ref wrappers, ref orderedWrappers); - if (parameterTarget is null || !PropertyHelper.TryGetPropertyInfo(configurationItemFactory, parameterTarget, parameterName, out propertyInfo)) + if (TryResolveAmbientLayoutWrapper(parameterName, configurationItemFactory, ref wrappers, ref orderedWrappers, out var layoutWrapper) && layoutWrapper != null) { - parameterTarget = layoutRenderer; - propertyInfo = null; + if (PropertyHelper.TryGetPropertyInfo(configurationItemFactory, layoutWrapper, parameterName, out propertyInfo)) + { + parameterTarget = layoutWrapper; + } } } @@ -488,7 +489,7 @@ private static LayoutRenderer ParseLayoutRenderer(ConfigurationItemFactory confi return BuildCompleteLayoutRenderer(configurationItemFactory, layoutRenderer, orderedWrappers); } - private static LayoutRenderer BuildCompleteLayoutRenderer(ConfigurationItemFactory configurationItemFactory, LayoutRenderer layoutRenderer, List orderedWrappers = null) + private static LayoutRenderer BuildCompleteLayoutRenderer(ConfigurationItemFactory configurationItemFactory, LayoutRenderer layoutRenderer, List? orderedWrappers = null) { if (orderedWrappers != null) { @@ -503,17 +504,18 @@ private static LayoutRenderer BuildCompleteLayoutRenderer(ConfigurationItemFacto return layoutRenderer; } - private static object ParseLayoutRendererPropertyValue(ConfigurationItemFactory configurationItemFactory, SimpleStringReader stringReader, bool? throwConfigExceptions, string targetTypeName, PropertyInfo propertyInfo) + + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow converting option-values from config", "IL2072")] + private static object? ParseLayoutRendererPropertyValue(ConfigurationItemFactory configurationItemFactory, SimpleStringReader stringReader, bool? throwConfigExceptions, string targetTypeName, PropertyInfo propertyInfo) { if (typeof(Layout).IsAssignableFrom(propertyInfo.PropertyType)) { - LayoutRenderer[] renderers = CompileLayout(configurationItemFactory, stringReader, throwConfigExceptions, true, out var txt); - Layout nestedLayout = new SimpleLayout(renderers, txt, configurationItemFactory); + LayoutRenderer[] renderers = CompileLayout(configurationItemFactory, stringReader, throwConfigExceptions, true, out var parsedTxt); + Layout nestedLayout = new SimpleLayout(renderers, parsedTxt); - if (propertyInfo.PropertyType.IsGenericType() && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Layout<>)) + if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Layout<>)) { - var concreteType = typeof(Layout<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()); - nestedLayout = (Layout)Activator.CreateInstance(concreteType, BindingFlags.Instance | BindingFlags.Public, null, new object[] { nestedLayout }, null); + nestedLayout = (Layout)Activator.CreateInstance(propertyInfo.PropertyType, BindingFlags.Instance | BindingFlags.Public, null, new object[] { nestedLayout }, null); } return nestedLayout; @@ -545,7 +547,7 @@ private static object ParseLayoutRendererPropertyValue(ConfigurationItemFactory } } - private static string ValidatePreviousParameterName(string previousParameterName, string parameterName, LayoutRenderer layoutRenderer, bool? throwConfigExceptions) + private static string? ValidatePreviousParameterName(string? previousParameterName, string parameterName, LayoutRenderer layoutRenderer, bool? throwConfigExceptions) { if (parameterName?.Equals(previousParameterName, StringComparison.OrdinalIgnoreCase) == true) { @@ -563,32 +565,41 @@ private static string ValidatePreviousParameterName(string previousParameterName return previousParameterName; } - private static LayoutRenderer LookupAmbientProperty(string propertyName, ConfigurationItemFactory configurationItemFactory, ref Dictionary wrappers, ref List orderedWrappers) + private static bool TryResolveAmbientLayoutWrapper(string propertyName, ConfigurationItemFactory configurationItemFactory, ref Dictionary? wrappers, ref List? orderedWrappers, out LayoutRenderer? layoutRenderer) { - if (configurationItemFactory.AmbientRendererFactory.TryCreateInstance(propertyName, out var wrapperInstance)) + if (!configurationItemFactory.AmbientRendererFactory.TryCreateInstance(propertyName, out var wrapperInstance) || wrapperInstance is null) { - wrappers = wrappers ?? new Dictionary(); - orderedWrappers = orderedWrappers ?? new List(); - var wrapperType = wrapperInstance.GetType(); - if (!wrappers.TryGetValue(wrapperType, out var wrapperRenderer)) - { - wrappers[wrapperType] = wrapperInstance; - orderedWrappers.Add(wrapperInstance); - wrapperRenderer = wrapperInstance; - } - return wrapperRenderer; + layoutRenderer = null; + return false; } - return null; + wrappers = wrappers ?? new Dictionary(); + orderedWrappers = orderedWrappers ?? new List(); + var wrapperType = wrapperInstance.GetType(); + if (!wrappers.TryGetValue(wrapperType, out layoutRenderer)) + { + wrappers[wrapperType] = wrapperInstance; + orderedWrappers.Add(wrapperInstance); + layoutRenderer = wrapperInstance; + } + return true; } private static LayoutRenderer GetLayoutRenderer(string typeName, ConfigurationItemFactory configurationItemFactory, bool? throwConfigExceptions) { - LayoutRenderer layoutRenderer = null; + LayoutRenderer? layoutRenderer = null; try { - layoutRenderer = configurationItemFactory.LayoutRendererFactory.CreateInstance(typeName); + if (throwConfigExceptions == false && !configurationItemFactory.LayoutRendererFactory.TryCreateInstance(typeName, out layoutRenderer)) + { + InternalLogger.Debug("Failed to create LayoutRenderer with unknown type-alias: '{0}'", typeName); + return new LiteralLayoutRenderer(string.Empty); // replace with empty values + } + else + { + layoutRenderer = configurationItemFactory.LayoutRendererFactory.CreateInstance(typeName); + } } catch (NLogConfigurationException ex) { @@ -612,7 +623,7 @@ private static string SetDefaultPropertyValue(string value, LayoutRenderer layou { // what we've just read is not a parameterName, but a value // assign it to a default property (denoted by empty string) - if (PropertyHelper.TryGetPropertyInfo(configurationItemFactory, layoutRenderer, string.Empty, out var propertyInfo)) + if (PropertyHelper.TryGetPropertyInfo(configurationItemFactory, layoutRenderer, string.Empty, out var propertyInfo) && propertyInfo != null) { if (!typeof(Layout).IsAssignableFrom(propertyInfo.PropertyType) && value.IndexOf('\\') >= 0) { @@ -630,7 +641,7 @@ private static string SetDefaultPropertyValue(string value, LayoutRenderer layou throw configException; } - return null; + return string.Empty; } } @@ -645,7 +656,7 @@ private static LayoutRenderer ApplyWrappers(ConfigurationItemFactory configurati lr = ConvertToLiteral(lr); } - newRenderer.Inner = new SimpleLayout(new[] { lr }, string.Empty, configurationItemFactory); + newRenderer.Inner = new SimpleLayout(new[] { lr }, string.Empty); lr = newRenderer; } diff --git a/src/NLog/Layouts/LayoutWithHeaderAndFooter.cs b/src/NLog/Layouts/LayoutWithHeaderAndFooter.cs index b2079325ec..a7cd3226ef 100644 --- a/src/NLog/Layouts/LayoutWithHeaderAndFooter.cs +++ b/src/NLog/Layouts/LayoutWithHeaderAndFooter.cs @@ -47,19 +47,19 @@ public class LayoutWithHeaderAndFooter : Layout /// Gets or sets the body layout (can be repeated multiple times). /// /// - public Layout Layout { get; set; } + public Layout Layout { get; set; } = Layout.Empty; /// /// Gets or sets the header layout. /// /// - public Layout Header { get; set; } + public Layout? Header { get; set; } /// /// Gets or sets the footer layout. /// /// - public Layout Footer { get; set; } + public Layout? Footer { get; set; } /// protected override string GetFormattedMessage(LogEventInfo logEvent) diff --git a/src/NLog/Layouts/Log4JXmlEventLayout.cs b/src/NLog/Layouts/Log4JXmlEventLayout.cs deleted file mode 100644 index 57bfb6883b..0000000000 --- a/src/NLog/Layouts/Log4JXmlEventLayout.cs +++ /dev/null @@ -1,234 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.Layouts -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Text; - using NLog.Config; - using NLog.LayoutRenderers; - using NLog.Targets; - - /// - /// A specialized layout that renders Log4j-compatible XML events. - /// - /// - /// - /// This layout is not meant to be used explicitly. Instead you can use ${log4jxmlevent} layout renderer. - /// - /// See NLog Wiki - /// - /// Documentation on NLog Wiki - [Layout("Log4JXmlEventLayout")] - [ThreadAgnostic] - public class Log4JXmlEventLayout : Layout, IIncludeContext - { - /// - /// Initializes a new instance of the class. - /// - public Log4JXmlEventLayout() - { - Renderer = new Log4JXmlEventLayoutRenderer(); - Parameters = new List(); - Renderer.Parameters = Parameters; - } - - /// - /// Gets the instance that renders log events. - /// - public Log4JXmlEventLayoutRenderer Renderer { get; } - - /// - /// Gets the collection of parameters. Each parameter contains a mapping - /// between NLog layout and a named parameter. - /// - /// - [ArrayParameter(typeof(NLogViewerParameterInfo), "parameter")] - public IList Parameters { get => Renderer.Parameters; set => Renderer.Parameters = value; } - - /// - /// Gets or sets the option to include all properties from the log events - /// - /// - public bool IncludeEventProperties - { - get => Renderer.IncludeEventProperties; - set => Renderer.IncludeEventProperties = value; - } - - /// - /// Gets or sets whether to include the contents of the properties-dictionary. - /// - /// - public bool IncludeScopeProperties - { - get => Renderer.IncludeScopeProperties; - set => Renderer.IncludeScopeProperties = value; - } - - /// - /// Gets or sets whether to include log4j:NDC in output from nested context. - /// - /// - public bool IncludeScopeNested - { - get => Renderer.IncludeScopeNested; - set => Renderer.IncludeScopeNested = value; - } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Gets or sets the option to include all properties from the log events - /// - /// - [Obsolete("Replaced by IncludeEventProperties. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IncludeAllProperties { get => IncludeEventProperties; set => IncludeEventProperties = value; } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Gets or sets a value indicating whether to include contents of the dictionary. - /// - /// - [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IncludeMdc { get => Renderer.IncludeMdc; set => Renderer.IncludeMdc = value; } - - /// - /// Gets or sets whether to include log4j:NDC in output from nested context. - /// - /// - public bool IncludeNdc { get => Renderer.IncludeNdc; set => Renderer.IncludeNdc = value; } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Gets or sets a value indicating whether to include contents of the dictionary. - /// - /// - [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IncludeMdlc { get => Renderer.IncludeMdlc; set => Renderer.IncludeMdlc = value; } - - /// - /// Obsolete and replaced by with NLog v5. - /// - /// Gets or sets a value indicating whether to include contents of the stack. - /// - /// - [Obsolete("Replaced by IncludeScopeNested. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IncludeNdlc { get => Renderer.IncludeNdlc; set => Renderer.IncludeNdlc = value; } - - /// - /// Gets or sets the log4j:event logger-xml-attribute. Default: ${logger} - /// - /// - public Layout LoggerName - { - get => Renderer.LoggerName; - set => Renderer.LoggerName = value; - } - - /// - /// Gets or sets the log4j:event message-xml-element. Default: ${message} - /// - /// - public Layout FormattedMessage - { - get => Renderer.FormattedMessage; - set => Renderer.FormattedMessage = value; - } - - /// - /// Gets or sets the log4j:event log4japp-xml-element. By default it's the friendly name of the current AppDomain. - /// - /// - public Layout AppInfo - { - get => Renderer.AppInfo; - set => Renderer.AppInfo = value; - } - - /// - /// Gets or sets whether the log4j:throwable xml-element should be written as CDATA - /// - /// - public bool WriteThrowableCData - { - get => Renderer.WriteThrowableCData; - set => Renderer.WriteThrowableCData = value; - } - - /// - /// Gets or sets a value indicating whether to include call site (class and method name) in the information sent over the network. - /// - /// - public bool IncludeCallSite - { - get => Renderer.IncludeCallSite; - set => Renderer.IncludeCallSite = value; - } - - /// - /// Gets or sets a value indicating whether to include source info (file name and line number) in the information sent over the network. - /// - /// - public bool IncludeSourceInfo - { - get => Renderer.IncludeSourceInfo; - set => Renderer.IncludeSourceInfo = value; - } - - internal override void PrecalculateBuilder(LogEventInfo logEvent, StringBuilder target) - { - PrecalculateBuilderInternal(logEvent, target, null); - } - - /// - protected override string GetFormattedMessage(LogEventInfo logEvent) - { - return RenderAllocateBuilder(logEvent); - } - - /// - protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target) - { - Renderer.RenderAppendBuilder(logEvent, target); - } - } -} diff --git a/src/NLog/Layouts/SimpleLayout.cs b/src/NLog/Layouts/SimpleLayout.cs index fb7441b695..0b74923de3 100644 --- a/src/NLog/Layouts/SimpleLayout.cs +++ b/src/NLog/Layouts/SimpleLayout.cs @@ -57,19 +57,18 @@ namespace NLog.Layouts [Layout("SimpleLayout")] [ThreadAgnostic] [AppDomainFixedOutput] - public class SimpleLayout : Layout, IUsesStackTrace + public sealed class SimpleLayout : Layout, IUsesStackTrace, IStringValueRenderer { - private string _fixedText; - private string _layoutText; - private IRawValue _rawValueRenderer; - private IStringValueRenderer _stringValueRenderer; - private readonly ConfigurationItemFactory _configurationItemFactory; + private readonly IRawValue? _rawValueRenderer; + private IStringValueRenderer? _stringValueRenderer; + + internal static SimpleLayout Default => (SimpleLayout)Layout.Empty; /// /// Initializes a new instance of the class. /// public SimpleLayout() - : this(string.Empty) + : this(ArrayHelper.Empty(), string.Empty) { } @@ -99,49 +98,67 @@ public SimpleLayout([Localizable(false)] string txt, ConfigurationItemFactory co /// The NLog factories to use when creating references to layout renderers. /// Whether should be thrown on parse errors. internal SimpleLayout([Localizable(false)] string txt, ConfigurationItemFactory configurationItemFactory, bool? throwConfigExceptions) + : this(LayoutParser.CompileLayout(txt, configurationItemFactory, throwConfigExceptions, out var parsedTxt), parsedTxt) { - _configurationItemFactory = configurationItemFactory; - SetLayoutText(txt, throwConfigExceptions); + OriginalText = txt ?? string.Empty; } - internal SimpleLayout(LayoutRenderer[] renderers, [Localizable(false)] string text, ConfigurationItemFactory configurationItemFactory) + internal SimpleLayout(LayoutRenderer[] layoutRenderers, [Localizable(false)] string txt) { - _configurationItemFactory = configurationItemFactory; - OriginalText = text; - SetLayoutRenderers(renderers, text); + Text = txt ?? string.Empty; + OriginalText = txt ?? string.Empty; + + _layoutRenderers = layoutRenderers ?? ArrayHelper.Empty(); + _renderers = null; + + FixedText = null; + _rawValueRenderer = null; + _stringValueRenderer = null; + + if (_layoutRenderers.Length == 0) + { + FixedText = string.Empty; + _stringValueRenderer = this; + } + else if (_layoutRenderers.Length == 1) + { + if (_layoutRenderers[0] is LiteralLayoutRenderer renderer) + { + FixedText = renderer.Text; + _stringValueRenderer = this; + } + else if (_layoutRenderers[0] is IStringValueRenderer stringValueRenderer) + { + _stringValueRenderer = stringValueRenderer; + } + + if (_layoutRenderers[0] is IRawValue rawValueRenderer) + { + _rawValueRenderer = rawValueRenderer; + } + } } /// - /// Original text before compile to Layout renderes + /// Original text before parsing as Layout renderes. /// - public string OriginalText { get; private set; } + public string OriginalText { get; } /// - /// Gets or sets the layout text. + /// Gets or sets the layout text that could be parsed. /// /// - public string Text - { - get => _layoutText; - set => SetLayoutText(value); - } - - private void SetLayoutText(string value, bool? throwConfigExceptions = null) - { - OriginalText = value; - var renderers = LayoutParser.CompileLayout(value, _configurationItemFactory, throwConfigExceptions, out var txt); - SetLayoutRenderers(renderers, txt); - } + public string Text { get; } /// /// Is the message fixed? (no Layout renderers used) /// - public bool IsFixedText => _fixedText != null; + public bool IsFixedText => FixedText != null; /// - /// Get the fixed text. Only set when is true + /// Get the fixed text. Only set when is /// - public string FixedText => _fixedText; + public string? FixedText { get; } /// /// Is the message a simple formatted string? (Can skip StringBuilder) @@ -153,8 +170,8 @@ private void SetLayoutText(string value, bool? throwConfigExceptions = null) /// [NLogConfigurationIgnoreProperty] public ReadOnlyCollection Renderers => _renderers ?? (_renderers = new ReadOnlyCollection(_layoutRenderers)); - private ReadOnlyCollection _renderers; - private LayoutRenderer[] _layoutRenderers; + private ReadOnlyCollection? _renderers; + private readonly LayoutRenderer[] _layoutRenderers; /// /// Gets a collection of objects that make up this layout. @@ -167,15 +184,14 @@ private void SetLayoutText(string value, bool? throwConfigExceptions = null) public new StackTraceUsage StackTraceUsage => base.StackTraceUsage; /// - /// Converts a text to a simple layout. + /// Implicitly converts the specified string as LayoutRenderer-expression into a . /// /// Text to be converted. /// A object. - public static implicit operator SimpleLayout([Localizable(false)] string text) + public static implicit operator SimpleLayout?([Localizable(false)] string text) { if (text is null) return null; - - return new SimpleLayout(text); + return string.IsNullOrEmpty(text) ? SimpleLayout.Default : new SimpleLayout(text); } /// @@ -190,6 +206,7 @@ public static implicit operator SimpleLayout([Localizable(false)] string text) /// Escaping is done by replacing all occurrences of /// '${' with '${literal:text=${}' /// + [Obsolete("Instead use Layout.FromLiteral()")] public static string Escape([Localizable(false)] string text) { return text.Replace("${", @"${literal:text=\$\{}"); @@ -204,8 +221,7 @@ public static string Escape([Localizable(false)] string text) /// values provided by the appropriate layout renderers. public static string Evaluate([Localizable(false)] string text, LogEventInfo logEvent) { - var layout = new SimpleLayout(text); - return layout.Render(logEvent); + return Evaluate(text, null, logEvent); } /// @@ -217,56 +233,48 @@ public static string Evaluate([Localizable(false)] string text, LogEventInfo log /// values provided by the appropriate layout renderers. public static string Evaluate([Localizable(false)] string text) { - return Evaluate(text, LogEventInfo.CreateNullEvent()); + return Evaluate(text, null, null); } - /// - public override string ToString() + internal static string Evaluate(string text, LoggingConfiguration? loggingConfiguration, LogEventInfo? logEventInfo = null, bool? throwConfigExceptions = null) { - if (string.IsNullOrEmpty(Text) && !IsFixedText && _layoutRenderers.Length > 0) + try { - return ToStringWithNestedItems(_layoutRenderers, r => r.ToString()); - } - - return Text ?? _fixedText ?? string.Empty; - } - - internal void SetLayoutRenderers(LayoutRenderer[] layoutRenderers, string text) - { - _layoutRenderers = layoutRenderers ?? ArrayHelper.Empty(); - _renderers = null; + if (string.IsNullOrEmpty(text)) + return string.Empty; - _fixedText = null; - _rawValueRenderer = null; - _stringValueRenderer = null; + if (text.IndexOf('$') < 0 || text.IndexOf('{') < 0 || text.IndexOf('}') < 0) + return text; - if (_layoutRenderers.Length == 0) - { - _fixedText = string.Empty; + throwConfigExceptions = throwConfigExceptions ?? loggingConfiguration?.LogFactory.ThrowConfigExceptions; + var layout = Layout.FromString(text, throwConfigExceptions ?? LogManager.ThrowConfigExceptions ?? LogManager.ThrowExceptions); + layout.Initialize(loggingConfiguration); + return layout.Render(logEventInfo ?? LogEventInfo.CreateNullEvent()); } - else if (_layoutRenderers.Length == 1) + catch (NLogConfigurationException ex) { - if (_layoutRenderers[0] is LiteralLayoutRenderer renderer) - { - _fixedText = renderer.Text; - } - else if (_layoutRenderers[0] is IStringValueRenderer stringValueRenderer) - { - _stringValueRenderer = stringValueRenderer; - } + if (throwConfigExceptions ?? LogManager.ThrowConfigExceptions ?? LogManager.ThrowExceptions) + throw; - if (_layoutRenderers[0] is IRawValue rawValueRenderer) - { - _rawValueRenderer = rawValueRenderer; - } + InternalLogger.Warn(ex, "Failed to Evaluate SimpleLayout: {0}", text); + return text; } + catch (Exception ex) + { + InternalLogger.Warn(ex, "Failed to Evaluate SimpleLayout: {0}", text); + return text; + } + } - _layoutText = text; - - if (LoggingConfiguration != null) + /// + public override string ToString() + { + if (string.IsNullOrEmpty(Text) && !IsFixedText && _layoutRenderers.Length > 0) { - PerformObjectScanning(); + return ToStringWithNestedItems(_layoutRenderers, r => r.ToString()); } + + return Text; } /// @@ -281,11 +289,9 @@ protected override void InitializeLayout() catch (Exception exception) { //also check IsErrorEnabled, otherwise 'MustBeRethrown' writes it to Error - - //check for performance if (InternalLogger.IsWarnEnabled || InternalLogger.IsErrorEnabled) { - InternalLogger.Warn(exception, "Exception in '{0}.InitializeLayout()'", renderer.GetType().FullName); + InternalLogger.Warn(exception, "Exception in '{0}.Initialize()'", renderer.GetType()); } if (exception.MustBeRethrown()) @@ -301,113 +307,102 @@ protected override void InitializeLayout() /// public override void Precalculate(LogEventInfo logEvent) { - if (MustPrecalculateLayoutValue(logEvent)) + if (PrecalculateMustRenderLayoutValue(logEvent)) { - Render(logEvent); + using (var localTarget = new AppendBuilderCreator(true)) + { + RenderFormattedMessage(logEvent, localTarget.Builder); + logEvent.AddCachedLayoutValue(this, localTarget.Builder.ToString()); + } } } internal override void PrecalculateBuilder(LogEventInfo logEvent, StringBuilder target) { - if (MustPrecalculateLayoutValue(logEvent)) + if (PrecalculateMustRenderLayoutValue(logEvent)) + { + RenderFormattedMessage(logEvent, target); + logEvent.AddCachedLayoutValue(this, target.ToString()); + } + } + + private bool PrecalculateMustRenderLayoutValue(LogEventInfo logEvent) + { + if (!IsInitialized) + { + Initialize(LoggingConfiguration); + } + + if (ThreadAgnostic && !ThreadAgnosticImmutable) + return false; + + if (_rawValueRenderer != null && TryGetRawValue(logEvent, out var rawValue) && IsRawValueImmutable(rawValue)) + return false; // If raw value is immutable, then we can skip precalculate-caching + + if (logEvent.TryGetCachedLayoutValue(this, out var _)) + return false; + + if (IsSimpleStringText) { - PrecalculateBuilderInternal(logEvent, target, null); + var cachedLayout = GetFormattedMessage(logEvent); + logEvent.AddCachedLayoutValue(this, cachedLayout); + return false; } + + return true; + } + + private static bool IsRawValueImmutable(object? value) + { + return value != null && (Convert.GetTypeCode(value) != TypeCode.Object || value.GetType().IsValueType); } - private bool MustPrecalculateLayoutValue(LogEventInfo logEvent) + /// + internal override bool TryGetRawValue(LogEventInfo logEvent, out object? rawValue) { - return _rawValueRenderer is null - ? (!ThreadAgnostic || ThreadAgnosticImmutable) - : !IsRawValueImmutable(logEvent); + if (_rawValueRenderer is null) + { + rawValue = null; + return false; + } + return TryGetSafeRawValue(logEvent, out rawValue); } - private bool IsRawValueImmutable(LogEventInfo logEvent) + private bool TryGetSafeRawValue(LogEventInfo logEvent, out object? rawValue) { try { + if (_rawValueRenderer is null) + { + rawValue = null; + return false; + } + if (!IsInitialized) { Initialize(LoggingConfiguration); } - if (ThreadAgnostic) + if ((!ThreadAgnostic || ThreadAgnosticImmutable) && logEvent.TryGetCachedLayoutValue(this, out _)) { - if (ThreadAgnosticImmutable) - { - // If raw value doesn't have the ability to mutate, then we can skip precalculate - var success = _rawValueRenderer.TryGetRawValue(logEvent, out var value); - if (success && IsRawValueImmutable(value)) - return true; - } - else - { - return true; - } + rawValue = null; + return false; // Raw-Value has been precalculated, so not available } - return false; + return _rawValueRenderer.TryGetRawValue(logEvent, out rawValue); } catch (Exception exception) { //also check IsErrorEnabled, otherwise 'MustBeRethrown' writes it to Error - - //check for performance if (InternalLogger.IsWarnEnabled || InternalLogger.IsErrorEnabled) { - InternalLogger.Warn(exception, "Exception in precalculate using '{0}.TryGetRawValue()'", _rawValueRenderer?.GetType()); + InternalLogger.Warn(exception, "Exception in TryGetRawValue using '{0}.TryGetRawValue()'", _rawValueRenderer?.GetType()); } if (exception.MustBeRethrown()) { throw; } - - return false; - } - } - - private static bool IsRawValueImmutable(object value) - { - return value != null && (Convert.GetTypeCode(value) != TypeCode.Object || value.GetType().IsValueType()); - } - - /// - internal override bool TryGetRawValue(LogEventInfo logEvent, out object rawValue) - { - if (_rawValueRenderer != null) - { - try - { - if (!IsInitialized) - { - Initialize(LoggingConfiguration); - } - - if ((!ThreadAgnostic || ThreadAgnosticImmutable) && logEvent.TryGetCachedLayoutValue(this, out _)) - { - rawValue = null; - return false; // Raw-Value has been precalculated, so not available - } - - var success = _rawValueRenderer.TryGetRawValue(logEvent, out rawValue); - return success; - } - catch (Exception exception) - { - //also check IsErrorEnabled, otherwise 'MustBeRethrown' writes it to Error - - //check for performance - if (InternalLogger.IsWarnEnabled || InternalLogger.IsErrorEnabled) - { - InternalLogger.Warn(exception, "Exception in TryGetRawValue using '{0}.TryGetRawValue()'", _rawValueRenderer?.GetType()); - } - - if (exception.MustBeRethrown()) - { - throw; - } - } } rawValue = null; @@ -417,76 +412,73 @@ internal override bool TryGetRawValue(LogEventInfo logEvent, out object rawValue /// protected override string GetFormattedMessage(LogEventInfo logEvent) { - if (IsFixedText) - { - return _fixedText; - } + var stringValue = FixedText; + if (stringValue != null) + return stringValue; + + if (_stringValueRenderer is null || !TryGetSafeStringValue(logEvent, out stringValue)) + return RenderAllocateBuilder(logEvent); + else + return stringValue; + } - if (_stringValueRenderer != null) + private bool TryGetSafeStringValue(LogEventInfo logEvent, out string stringValue) + { + try { - try + if (!IsInitialized) { - string stringValue = _stringValueRenderer.GetFormattedString(logEvent); - if (stringValue != null) - return stringValue; + Initialize(LoggingConfiguration); + } + var value = _stringValueRenderer?.GetFormattedString(logEvent); + if (value is null) + { + stringValue = string.Empty; _stringValueRenderer = null; // Optimization is not possible + return false; } - catch (Exception exception) + stringValue = value; + return true; + } + catch (Exception exception) + { + //also check IsErrorEnabled, otherwise 'MustBeRethrown' writes it to Error + if (InternalLogger.IsWarnEnabled || InternalLogger.IsErrorEnabled) { - //also check IsErrorEnabled, otherwise 'MustBeRethrown' writes it to Error - //check for performance - if (InternalLogger.IsWarnEnabled || InternalLogger.IsErrorEnabled) - { - InternalLogger.Warn(exception, "Exception in '{0}.GetFormattedString()'", _stringValueRenderer.GetType().FullName); - } + InternalLogger.Warn(exception, "Exception in '{0}.GetFormattedString()'", _stringValueRenderer?.GetType()); + } - if (exception.MustBeRethrown()) - { - throw; - } + if (exception.MustBeRethrown()) + { + throw; } } - return RenderAllocateBuilder(logEvent); + stringValue = string.Empty; + return false; } - private void RenderAllRenderers(LogEventInfo logEvent, StringBuilder target) + /// + protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target) { - foreach (var renderer in _layoutRenderers) + var fixedText = FixedText; + if (fixedText is null) { - try + foreach (var renderer in _layoutRenderers) { renderer.RenderAppendBuilder(logEvent, target); } - catch (Exception exception) - { - //also check IsErrorEnabled, otherwise 'MustBeRethrown' writes it to Error - - //check for performance - if (InternalLogger.IsWarnEnabled || InternalLogger.IsErrorEnabled) - { - InternalLogger.Warn(exception, "Exception in '{0}.Append()'", renderer.GetType().FullName); - } - - if (exception.MustBeRethrown()) - { - throw; - } - } } - } - - /// - protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target) - { - if (IsFixedText) + else { - target.Append(_fixedText); - return; + target.Append(fixedText); } + } - RenderAllRenderers(logEvent, target); + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) + { + return FixedText ?? (IsSimpleStringText ? Render(logEvent) : null); } } } diff --git a/src/NLog/Layouts/Typed/Layout.cs b/src/NLog/Layouts/Typed/Layout.cs index 8bf328846d..4fcf9e1209 100644 --- a/src/NLog/Layouts/Typed/Layout.cs +++ b/src/NLog/Layouts/Typed/Layout.cs @@ -31,17 +31,16 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.ComponentModel; -using System.Globalization; -using System.Text; -using JetBrains.Annotations; -using NLog.Common; -using NLog.Config; -using NLog.Internal; - namespace NLog.Layouts { + using System; + using System.ComponentModel; + using System.Globalization; + using System.Text; + using NLog.Common; + using NLog.Config; + using NLog.Internal; + /// /// Typed Layout for easy conversion from NLog Layout logic to a simple value (ex. integer or enum) /// @@ -49,15 +48,15 @@ namespace NLog.Layouts [ThreadAgnostic] public sealed class Layout : Layout, ILayoutTypeValue, IEquatable { - private readonly T _fixedValue; - private object _fixedObjectValue; + private readonly T? _fixedValue; + private object? _fixedObjectValue; ILayoutTypeValue ILayoutTypeValue.InnerLayout => _layoutValue; Type ILayoutTypeValue.InnerType => typeof(T); bool ILayoutTypeValue.ThreadAgnostic => true; bool ILayoutTypeValue.ThreadAgnosticImmutable => false; StackTraceUsage ILayoutTypeValue.StackTraceUsage => StackTraceUsage.None; - LoggingConfiguration ILayoutTypeValue.LoggingConfiguration => LoggingConfiguration; + LoggingConfiguration? ILayoutTypeValue.LoggingConfiguration => LoggingConfiguration; void ILayoutTypeValue.InitializeLayout() { // SONAR: Nothing to initialize @@ -66,12 +65,12 @@ void ILayoutTypeValue.CloseLayout() { // SONAR: Nothing to initialize } - bool ILayoutTypeValue.TryRenderValue(LogEventInfo logEvent, StringBuilder stringBuilder, out T value) + bool ILayoutTypeValue.TryRenderValue(LogEventInfo logEvent, StringBuilder? stringBuilder, out T? value) { value = _fixedValue; return true; } - object ILayoutTypeValue.RenderObjectValue(NLog.LogEventInfo logEvent, System.Text.StringBuilder stringBuilder) => FixedObjectValue; + object? ILayoutTypeValue.RenderObjectValue(NLog.LogEventInfo logEvent, StringBuilder? stringBuilder) => FixedObjectValue; private readonly ILayoutTypeValue _layoutValue; @@ -83,12 +82,12 @@ bool ILayoutTypeValue.TryRenderValue(LogEventInfo logEvent, StringBuilder str /// /// Fixed value /// - public T FixedValue => _fixedValue; + public T? FixedValue => _fixedValue; - private object FixedObjectValue => (_fixedObjectValue ?? (_fixedObjectValue = _fixedValue)); + private object? FixedObjectValue => _fixedObjectValue ?? (_fixedObjectValue = _fixedValue); private IPropertyTypeConverter ValueTypeConverter => _valueTypeConverter ?? (_valueTypeConverter = ResolveService()); - IPropertyTypeConverter _valueTypeConverter; + IPropertyTypeConverter? _valueTypeConverter; /// /// Initializes a new instance of the class. @@ -105,7 +104,7 @@ public Layout(Layout layout) /// Dynamic NLog Layout /// Format used for parsing string-value into result value type /// Culture used for parsing string-value into result value type - public Layout(Layout layout, string parseValueFormat, CultureInfo parseValueCulture) + public Layout(Layout layout, string? parseValueFormat, CultureInfo? parseValueCulture) { if (PropertyTypeConverter.IsComplexType(typeof(T))) { @@ -126,7 +125,7 @@ public Layout(Layout layout, string parseValueFormat, CultureInfo parseValueCult /// Initializes a new instance of the class. /// /// Fixed value - public Layout(T value) + public Layout(T? value) { _fixedValue = value; _layoutValue = this; @@ -144,12 +143,12 @@ private Layout(Func layoutMethod, LayoutRenderOptions options) /// Log event for rendering /// Fallback value when no value available /// Result value when available, else fallback to defaultValue - internal T RenderTypedValue([CanBeNull] LogEventInfo logEvent, T defaultValue = default(T)) + internal T? RenderTypedValue(LogEventInfo logEvent, T? defaultValue = default(T)) { return RenderTypedValue(logEvent, null, defaultValue); } - internal T RenderTypedValue([CanBeNull] LogEventInfo logEvent, [CanBeNull] StringBuilder stringBuilder, T defaultValue) + internal T? RenderTypedValue(LogEventInfo logEvent, StringBuilder? stringBuilder, T? defaultValue) { if (IsFixed) return _fixedValue; @@ -173,7 +172,7 @@ internal T RenderTypedValue([CanBeNull] LogEventInfo logEvent, [CanBeNull] Strin return defaultValue; } - private object RenderObjectValue([CanBeNull] LogEventInfo logEvent, [CanBeNull] StringBuilder stringBuilder) + private object? RenderObjectValue(LogEventInfo logEvent, StringBuilder? stringBuilder) { if (logEvent is null) return null; @@ -191,8 +190,7 @@ private object RenderObjectValue([CanBeNull] LogEventInfo logEvent, [CanBeNull] protected override string GetFormattedMessage(LogEventInfo logEvent) { var objectValue = IsFixed ? FixedObjectValue : RenderObjectValue(logEvent, null); - var formatProvider = logEvent.FormatProvider ?? LoggingConfiguration?.DefaultCultureInfo; - return Convert.ToString(objectValue, formatProvider); + return FormatHelper.TryFormatToString(objectValue, null, CultureInfo.InvariantCulture); } /// @@ -223,7 +221,7 @@ public override void Precalculate(LogEventInfo logEvent) } /// - internal override void PrecalculateBuilder(LogEventInfo logEvent, StringBuilder target) + internal override void PrecalculateBuilder(LogEventInfo logEvent, StringBuilder? target) { PrecalculateInnerLayout(logEvent, target); } @@ -239,7 +237,7 @@ public static Layout FromMethod(Func layoutMethod, LayoutRen return new Layout(layoutMethod, options); } - private void PrecalculateInnerLayout(LogEventInfo logEvent, StringBuilder target) + private void PrecalculateInnerLayout(LogEventInfo logEvent, StringBuilder? target) { if (IsFixed || (_layoutValue.ThreadAgnostic && !_layoutValue.ThreadAgnosticImmutable)) return; @@ -254,8 +252,8 @@ private sealed class LayoutGenericTypeValue : LayoutTypeValue, ILayoutTypeValue< public override IPropertyTypeConverter ValueTypeConverter => _ownerLayout.ValueTypeConverter; - public LayoutGenericTypeValue(Layout layout, string parseValueFormat, CultureInfo parseValueCulture, Layout ownerLayout) - :base(layout, typeof(T), parseValueFormat, parseValueCulture, null) + public LayoutGenericTypeValue(Layout layout, string? parseValueFormat, CultureInfo? parseValueCulture, Layout ownerLayout) + : base(layout, typeof(T), parseValueFormat, parseValueCulture, null) { _ownerLayout = ownerLayout; } @@ -270,7 +268,7 @@ public void CloseLayout() base.Close(); } - public bool TryRenderValue(LogEventInfo logEvent, StringBuilder stringBuilder, out T value) + public bool TryRenderValue(LogEventInfo logEvent, StringBuilder? stringBuilder, out T? value) { var objectValue = RenderObjectValue(logEvent, stringBuilder); if (objectValue is T typedValue) @@ -283,15 +281,15 @@ public bool TryRenderValue(LogEventInfo logEvent, StringBuilder stringBuilder, o } } - private bool TryParseFixedValue(Layout layout, string parseValueFormat, CultureInfo parseValueCulture, ref T fixedValue) + private bool TryParseFixedValue(Layout layout, string? parseValueFormat, CultureInfo? parseValueCulture, ref T? fixedValue) { if (layout is SimpleLayout simpleLayout && simpleLayout.IsFixedText) { - if (!string.IsNullOrEmpty(simpleLayout.FixedText)) + if (simpleLayout.FixedText != null && !string.IsNullOrEmpty(simpleLayout.FixedText)) { try { - fixedValue = (T)ParseValueFromObject(simpleLayout.FixedText, parseValueFormat, parseValueCulture); + fixedValue = (T?)ParseValueFromObject(simpleLayout.FixedText, parseValueFormat, parseValueCulture); return true; } catch (Exception ex) @@ -303,7 +301,7 @@ private bool TryParseFixedValue(Layout layout, string parseValueFormat, CultureI } else if (typeof(T) == typeof(string)) { - fixedValue = (T)(object)simpleLayout.FixedText; + fixedValue = (T)(object)(simpleLayout.FixedText ?? string.Empty); return true; } else if (Nullable.GetUnderlyingType(typeof(T)) != null) @@ -330,7 +328,7 @@ private sealed class FuncMethodValue : ILayoutTypeValue ILayoutTypeValue ILayoutTypeValue.InnerLayout => this; Type ILayoutTypeValue.InnerType => typeof(T); - LoggingConfiguration ILayoutTypeValue.LoggingConfiguration => null; + LoggingConfiguration? ILayoutTypeValue.LoggingConfiguration => null; bool ILayoutTypeValue.ThreadAgnosticImmutable => false; StackTraceUsage ILayoutTypeValue.StackTraceUsage => StackTraceUsage.None; @@ -350,13 +348,13 @@ public void CloseLayout() // SONAR: Nothing to close } - public bool TryRenderValue(LogEventInfo logEvent, StringBuilder stringBuilder, out T value) + public bool TryRenderValue(LogEventInfo logEvent, StringBuilder? stringBuilder, out T? value) { value = _layoutMethod(logEvent); return true; } - public object RenderObjectValue(LogEventInfo logEvent, StringBuilder stringBuilder) + public object? RenderObjectValue(LogEventInfo logEvent, StringBuilder? stringBuilder) { return _layoutMethod(logEvent); } @@ -367,7 +365,7 @@ public override string ToString() } } - private object ParseValueFromObject(object rawValue, string parseValueFormat, CultureInfo parseValueCulture) + private object? ParseValueFromObject(object rawValue, string? parseValueFormat, CultureInfo? parseValueCulture) { return ValueTypeConverter.Convert(rawValue, typeof(T), parseValueFormat, parseValueCulture); } @@ -421,8 +419,6 @@ public override int GetHashCode() /// Text to be converted. public static implicit operator Layout(T value) { - if (object.Equals(value, default(T)) && !typeof(T).IsValueType()) return null; - return new Layout(value); } @@ -432,9 +428,7 @@ public static implicit operator Layout(T value) /// Text to be converted. public static implicit operator Layout([Localizable(false)] string layout) { - if (layout is null) return null; - - return new Layout(layout); + return (layout is null && !typeof(T).IsValueType) ? new Layout(default(T)) : new Layout(layout ?? string.Empty); } /// @@ -456,32 +450,32 @@ public static implicit operator Layout([Localizable(false)] string layout) internal interface ILayoutTypeValue { - Type InnerType { get; } + Type? InnerType { get; } ILayoutTypeValue InnerLayout { get; } - object RenderObjectValue(LogEventInfo logEvent, StringBuilder stringBuilder); + object? RenderObjectValue(LogEventInfo logEvent, StringBuilder? stringBuilder); } internal interface ILayoutTypeValue : ILayoutTypeValue { - LoggingConfiguration LoggingConfiguration { get; } + LoggingConfiguration? LoggingConfiguration { get; } bool ThreadAgnostic { get; } bool ThreadAgnosticImmutable { get; } StackTraceUsage StackTraceUsage { get; } void InitializeLayout(); void CloseLayout(); - bool TryRenderValue(LogEventInfo logEvent, StringBuilder stringBuilder, out T value); + bool TryRenderValue(LogEventInfo logEvent, StringBuilder? stringBuilder, out T? value); } - internal class LayoutTypeValue : ILayoutTypeValue + internal class LayoutTypeValue : ILayoutTypeValue, IPropertyTypeConverter { private readonly Layout _innerLayout; private readonly Type _valueType; - private readonly CultureInfo _parseValueCulture; - private readonly string _parseValueFormat; - private string _previousStringValue; - private object _previousValue; + private readonly CultureInfo? _parseValueCulture; + private readonly string? _parseValueFormat; + private string? _previousStringValue; + private object? _previousValue; - public LoggingConfiguration LoggingConfiguration => _innerLayout.LoggingConfiguration; + public LoggingConfiguration? LoggingConfiguration => _innerLayout.LoggingConfiguration; public bool ThreadAgnostic => _innerLayout.ThreadAgnostic; public bool ThreadAgnosticImmutable => _innerLayout.ThreadAgnosticImmutable; public StackTraceUsage StackTraceUsage => _innerLayout.StackTraceUsage; @@ -489,20 +483,20 @@ internal class LayoutTypeValue : ILayoutTypeValue ILayoutTypeValue ILayoutTypeValue.InnerLayout => this; Type ILayoutTypeValue.InnerType => _valueType; - public LayoutTypeValue(Layout layout, Type valueType, string parseValueFormat, CultureInfo parseValueCulture, IPropertyTypeConverter valueTypeConverter) + public LayoutTypeValue(Layout layout, Type valueType, string? parseValueFormat, CultureInfo? parseValueCulture, IPropertyTypeConverter? valueTypeConverter) { _innerLayout = layout; _valueType = valueType; _parseValueFormat = parseValueFormat; _parseValueCulture = parseValueCulture; - ValueTypeConverter = valueTypeConverter; + ValueTypeConverter = valueTypeConverter ?? this; } - public object TryParseFixedValue() + public object? TryParseFixedValue() { - if (_innerLayout is SimpleLayout simpleLayout && simpleLayout.IsFixedText) + if (_innerLayout is SimpleLayout simpleLayout && simpleLayout.FixedText != null) { - if (TryParseValueFromString(simpleLayout.FixedText, _parseValueFormat, _parseValueCulture, out object objectValue)) + if (TryParseValueFromString(simpleLayout.FixedText, out var objectValue)) { return objectValue; } @@ -524,7 +518,7 @@ protected void Close() _previousValue = null; } - public object RenderObjectValue(LogEventInfo logEvent, StringBuilder stringBuilder) + public object? RenderObjectValue(LogEventInfo logEvent, StringBuilder? stringBuilder) { if (_innerLayout.TryGetRawValue(logEvent, out var rawValue)) { @@ -532,7 +526,7 @@ public object RenderObjectValue(LogEventInfo logEvent, StringBuilder stringBuild { if (string.IsNullOrEmpty(rawStringValue)) { - TryParseValueFromString(rawStringValue, _parseValueFormat, _parseValueCulture, out var objectValue); + TryParseValueFromString(rawStringValue, out var objectValue); return objectValue; } } @@ -543,7 +537,7 @@ public object RenderObjectValue(LogEventInfo logEvent, StringBuilder stringBuild return null; } - TryParseValueFromObject(rawValue, _parseValueFormat, _parseValueCulture, out var objectValue); + TryParseValueFromObject(rawValue, out var objectValue); return objectValue; } } @@ -557,7 +551,7 @@ public object RenderObjectValue(LogEventInfo logEvent, StringBuilder stringBuild return previousValue; } - if (TryParseValueFromString(stringValue, _parseValueFormat, _parseValueCulture, out var parsedValue)) + if (TryParseValueFromString(stringValue, out var parsedValue)) { if (string.IsNullOrEmpty(previousStringValue) || stringValue?.Length < 3) { @@ -571,11 +565,13 @@ public object RenderObjectValue(LogEventInfo logEvent, StringBuilder stringBuild return null; } - private string RenderStringValue(LogEventInfo logEvent, StringBuilder stringBuilder, string previousStringValue) + private string RenderStringValue(LogEventInfo logEvent, StringBuilder? stringBuilder, string? previousStringValue) { - if (_innerLayout is SimpleLayout simpleLayout && simpleLayout.IsSimpleStringText) + if (_innerLayout is IStringValueRenderer stringLayout) { - return simpleLayout.Render(logEvent, false); + var result = stringLayout.GetFormattedString(logEvent); + if (result != null) + return result; } if (stringBuilder?.Length == 0) @@ -583,10 +579,10 @@ private string RenderStringValue(LogEventInfo logEvent, StringBuilder stringBuil _innerLayout.Render(logEvent, stringBuilder); if (stringBuilder.Length == 0) return string.Empty; - else if (!string.IsNullOrEmpty(previousStringValue) && stringBuilder.EqualTo(previousStringValue)) - return previousStringValue; - else + else if (previousStringValue is null || string.IsNullOrEmpty(previousStringValue) || !stringBuilder.EqualTo(previousStringValue)) return stringBuilder.ToString(); + else + return previousStringValue; } else { @@ -594,11 +590,11 @@ private string RenderStringValue(LogEventInfo logEvent, StringBuilder stringBuil } } - private bool TryParseValueFromObject(object rawValue, string parseValueFormat, CultureInfo parseValueCulture, out object parsedValue) + private bool TryParseValueFromObject(object rawValue, out object? parsedValue) { try { - parsedValue = ParseValueFromObject(rawValue, parseValueFormat, parseValueCulture); + parsedValue = ParseValueFromObject(rawValue); return true; } catch (Exception ex) @@ -609,12 +605,12 @@ private bool TryParseValueFromObject(object rawValue, string parseValueFormat, C } } - private object ParseValueFromObject(object rawValue, string parseValueFormat, CultureInfo parseValueCulture) + private object? ParseValueFromObject(object rawValue) { - return ValueTypeConverter.Convert(rawValue, _valueType, parseValueFormat, parseValueCulture); + return ValueTypeConverter.Convert(rawValue, _valueType, _parseValueFormat, _parseValueCulture); } - private bool TryParseValueFromString(string stringValue, string parseValueFormat, CultureInfo parseValueCulture, out object parsedValue) + private bool TryParseValueFromString(string stringValue, out object? parsedValue) { if (string.IsNullOrEmpty(stringValue)) { @@ -622,12 +618,17 @@ private bool TryParseValueFromString(string stringValue, string parseValueFormat return true; } - return TryParseValueFromObject(stringValue, parseValueFormat, parseValueCulture, out parsedValue); + return TryParseValueFromObject(stringValue, out parsedValue); } public override string ToString() { return _innerLayout.ToString(); } + + object? IPropertyTypeConverter.Convert(object? propertyValue, Type propertyType, string? format, IFormatProvider? formatProvider) + { + return null; + } } } diff --git a/src/NLog/Layouts/Typed/LayoutTypedExtensions.cs b/src/NLog/Layouts/Typed/LayoutTypedExtensions.cs index cc6c36c487..bd2c4e064b 100644 --- a/src/NLog/Layouts/Typed/LayoutTypedExtensions.cs +++ b/src/NLog/Layouts/Typed/LayoutTypedExtensions.cs @@ -31,12 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using JetBrains.Annotations; -using NLog.Layouts; -using NLog.Targets; - namespace NLog { + using NLog.Layouts; + using NLog.Targets; + /// /// Extensions for NLog . /// @@ -51,7 +50,7 @@ public static class LayoutTypedExtensions /// The logevent info. /// Fallback value when no value available /// Result value when available, else fallback to defaultValue - public static T RenderValue([CanBeNull] this Layout layout, [CanBeNull] LogEventInfo logEvent, T defaultValue = default(T)) + public static T? RenderValue(this Layout? layout, LogEventInfo logEvent, T? defaultValue = default(T)) { return layout is null ? defaultValue : layout.RenderTypedValue(logEvent, defaultValue); } diff --git a/src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs b/src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs index c0c55e5d5b..1a511facba 100644 --- a/src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs +++ b/src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs @@ -31,14 +31,14 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Globalization; -using System.Text; -using NLog.Config; -using NLog.Internal; - namespace NLog.Layouts { + using System; + using System.Globalization; + using System.Text; + using NLog.Config; + using NLog.Internal; + /// /// Typed Value that is easily configured from NLog.config file /// @@ -55,16 +55,15 @@ public ValueTypeLayoutInfo() } /// - /// Gets or sets the layout that will render the result value + /// Gets or sets the layout used for rendering the value. /// /// - [RequiredParameter] public Layout Layout { get => _layout; set { - _layout = value; + _layout = value ?? Layout.Empty; if (ValueType is null && _layout is ILayoutTypeValue layoutTyped) { ValueType = layoutTyped.InnerType; @@ -72,20 +71,20 @@ public Layout Layout _layoutValue = null; } } - private Layout _layout; + private Layout _layout = Layout.Empty; /// /// Gets or sets the result value type, for conversion of layout rendering output /// /// - public Type ValueType + public Type? ValueType { get => _valueType; [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow ValueType assign from config", "IL2067")] set { _valueType = value; - if (value?.IsValueType() == true) + if (value?.IsValueType == true) _createDefaultValue = () => Activator.CreateInstance(value); else _createDefaultValue = null; @@ -94,14 +93,14 @@ public Type ValueType _useDefaultWhenEmptyString = UseDefaultWhenEmptyString(_valueType, _defaultValue); } } - private Type _valueType; - private Func _createDefaultValue; + private Type? _valueType; + private Func? _createDefaultValue; /// /// Gets or sets the fallback value when result value is not available /// /// - public Layout DefaultValue + public Layout? DefaultValue { get => _defaultValue; set @@ -111,10 +110,10 @@ public Layout DefaultValue _useDefaultWhenEmptyString = UseDefaultWhenEmptyString(_valueType, _defaultValue); } } - private Layout _defaultValue; + private Layout? _defaultValue; private bool _useDefaultWhenEmptyString; - private static bool UseDefaultWhenEmptyString(Type valueType, Layout defaultValue) + private static bool UseDefaultWhenEmptyString(Type? valueType, Layout? defaultValue) { if (valueType is null || typeof(string).Equals(valueType) || typeof(object).Equals(valueType)) { @@ -125,7 +124,7 @@ private static bool UseDefaultWhenEmptyString(Type valueType, Layout defaultValu } return true; } - + /// /// Gets or sets the fallback value should be null (instead of default value of ) when result value is not available /// @@ -147,7 +146,7 @@ public bool ForceDefaultValueNull /// Gets or sets format used for parsing parameter string-value for type-conversion /// /// - public string ValueParseFormat + public string? ValueParseFormat { get => _valueParseFormat; set @@ -157,13 +156,13 @@ public string ValueParseFormat _defaultLayoutValue = null; } } - private string _valueParseFormat; + private string? _valueParseFormat; /// /// Gets or sets the culture used for parsing parameter string-value for type-conversion /// /// - public CultureInfo ValueParseCulture + public CultureInfo? ValueParseCulture { get => _valueParseCulture; set @@ -173,14 +172,14 @@ public CultureInfo ValueParseCulture _defaultLayoutValue = null; } } - private CultureInfo _valueParseCulture; + private CultureInfo? _valueParseCulture; /// /// Render Result Value /// /// Log event for rendering /// Result value when available, else fallback to defaultValue - public object RenderValue(LogEventInfo logEvent) + public object? RenderValue(LogEventInfo logEvent) { var objectValue = LayoutValue.RenderObjectValue(logEvent, null); if (objectValue is null || (_useDefaultWhenEmptyString && StringHelpers.IsNullOrEmptyString(objectValue))) @@ -192,12 +191,12 @@ public object RenderValue(LogEventInfo logEvent) } private ILayoutTypeValue LayoutValue => _layoutValue ?? (_layoutValue = BuildLayoutTypeValue(Layout)); - private ILayoutTypeValue _layoutValue; + private ILayoutTypeValue? _layoutValue; private ILayoutTypeValue DefaultLayoutValue => _defaultLayoutValue ?? (_defaultLayoutValue = BuildLayoutTypeValue(DefaultValue)); - private ILayoutTypeValue _defaultLayoutValue; + private ILayoutTypeValue? _defaultLayoutValue; - private ILayoutTypeValue BuildLayoutTypeValue(Layout layout) + private ILayoutTypeValue BuildLayoutTypeValue(Layout? layout) { if (layout is null) { @@ -212,7 +211,7 @@ private ILayoutTypeValue BuildLayoutTypeValue(Layout layout) } else { - layout = string.Empty; + layout = Layout.Empty; } } @@ -234,16 +233,16 @@ private ILayoutTypeValue BuildLayoutTypeValue(Layout layout) private sealed class FixedLayoutTypeValue : ILayoutTypeValue { - private readonly object _fixedValue; + private readonly object? _fixedValue; public ILayoutTypeValue InnerLayout => this; - public Type InnerType => _fixedValue?.GetType(); + public Type? InnerType => _fixedValue?.GetType(); public FixedLayoutTypeValue(object fixedValue) { _fixedValue = fixedValue; } - public object RenderObjectValue(LogEventInfo logEvent, StringBuilder stringBuilder) + public object? RenderObjectValue(LogEventInfo logEvent, StringBuilder? stringBuilder) { return _fixedValue; } @@ -265,9 +264,9 @@ public StringLayoutTypeValue(Layout layout) _innerLayout = layout; } - public object RenderObjectValue(LogEventInfo logEvent, StringBuilder stringBuilder) + public object RenderObjectValue(LogEventInfo logEvent, StringBuilder? stringBuilder) { - return _innerLayout.Render(logEvent, false); + return _innerLayout.Render(logEvent); } public override string ToString() diff --git a/src/NLog/Layouts/XML/XmlAttribute.cs b/src/NLog/Layouts/XML/XmlAttribute.cs index 1ad88e06f1..f3c31f86f0 100644 --- a/src/NLog/Layouts/XML/XmlAttribute.cs +++ b/src/NLog/Layouts/XML/XmlAttribute.cs @@ -41,6 +41,10 @@ namespace NLog.Layouts /// /// XML attribute. /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki [NLogConfigurationItem] public class XmlAttribute { @@ -49,7 +53,7 @@ public class XmlAttribute /// /// Initializes a new instance of the class. /// - public XmlAttribute() : this(null, null, true) { } + public XmlAttribute() : this(string.Empty, Layout.Empty, true) { } /// /// Initializes a new instance of the class. @@ -75,39 +79,43 @@ public XmlAttribute(string name, Layout layout, bool encode) /// /// Gets or sets the name of the attribute. /// + /// [Required] Default: /// - [RequiredParameter] - public string Name { get => _name; set => _name = XmlHelper.XmlConvertToElementName(value?.Trim()); } - private string _name; + public string Name { get => _name; set => _name = XmlHelper.XmlConvertToElementName(value?.Trim() ?? string.Empty); } + private string _name = string.Empty; /// - /// Gets or sets the layout that will be rendered as the attribute's value. + /// Gets or sets the layout used for rendering the attribute value. /// + /// [Required] Default: /// - [RequiredParameter] public Layout Layout { get => _layoutInfo.Layout; set => _layoutInfo.Layout = value; } /// /// Gets or sets the result value type, for conversion of layout rendering output /// + /// Default: /// - public Type ValueType { get => _layoutInfo.ValueType; set => _layoutInfo.ValueType = value; } + public Type? ValueType { get => _layoutInfo.ValueType; set => _layoutInfo.ValueType = value; } /// /// Gets or sets the fallback value when result value is not available /// + /// Default: /// - public Layout DefaultValue { get => _layoutInfo.DefaultValue; set => _layoutInfo.DefaultValue = value; } + public Layout? DefaultValue { get => _layoutInfo.DefaultValue; set => _layoutInfo.DefaultValue = value; } /// /// Gets or sets whether output should be encoded with Xml-string escaping, or be treated as valid xml-attribute-value /// + /// Default: /// public bool Encode { get; set; } /// - /// Gets or sets whether an attribute with empty value should be included in the output + /// Gets or sets whether empty attribute value should be included in the output. /// + /// Default: . Empty value is either null or empty string /// public bool IncludeEmptyValue { diff --git a/src/NLog/Layouts/XML/XmlElement.cs b/src/NLog/Layouts/XML/XmlElement.cs index 55457023d0..30bb55f54e 100644 --- a/src/NLog/Layouts/XML/XmlElement.cs +++ b/src/NLog/Layouts/XML/XmlElement.cs @@ -38,6 +38,10 @@ namespace NLog.Layouts /// /// A XML Element /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki [ThreadAgnostic] public class XmlElement : XmlElementBase { @@ -46,7 +50,7 @@ public class XmlElement : XmlElementBase /// /// Initializes a new instance of the class. /// - public XmlElement() : this(DefaultElementName, null) + public XmlElement() : this(DefaultElementName, Layout.Empty) { } @@ -60,11 +64,8 @@ public XmlElement(string elementName, Layout elementValue) : base(elementName, e /// /// Name of the element /// - /// - /// Default value "item" - /// + /// [Required] Default: item /// - [RequiredParameter] public string Name { get => base.ElementNameInternal; @@ -74,6 +75,7 @@ public string Name /// /// Value inside the element /// + /// Default: /// public Layout Value { @@ -82,19 +84,31 @@ public Layout Value } /// - /// Value inside the element + /// Gets or sets the layout used for rendering the XML-element InnerText. /// + /// Default: /// public Layout Layout { get => Value; set => Value = value; } /// - /// Gets or sets whether output should be encoded with Xml-string escaping, or be treated as valid xml-element-value + /// Gets or sets whether output should be encoded with XML-string escaping, or be treated as valid xml-element-value /// + /// Default: /// public bool Encode { get => base.LayoutWrapper.XmlEncode; set => base.LayoutWrapper.XmlEncode = value; } + + /// + /// Gets or sets whether output should be wrapped using CDATA section instead of XML-string escaping + /// + /// Default: + public bool CDataEncode + { + get => LayoutWrapper.CDataEncode; + set => LayoutWrapper.CDataEncode = value; + } } } diff --git a/src/NLog/Layouts/XML/XmlElementBase.cs b/src/NLog/Layouts/XML/XmlElementBase.cs index 1b60a69e97..b15f349f8f 100644 --- a/src/NLog/Layouts/XML/XmlElementBase.cs +++ b/src/NLog/Layouts/XML/XmlElementBase.cs @@ -40,13 +40,18 @@ namespace NLog.Layouts using System.Text; using NLog.Config; using NLog.Internal; + using NLog.Targets; /// /// A specialized layout that renders XML-formatted events. /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki public abstract class XmlElementBase : Layout { - private Layout[] _precalculateLayouts; + private Layout[]? _precalculateLayouts; private const string DefaultPropertyName = "property"; private const string DefaultPropertyKeyAttribute = "key"; private const string DefaultCollectionItemName = "item"; @@ -66,9 +71,8 @@ protected XmlElementBase(string elementName, Layout elementValue) /// /// Name of the XML element /// - /// Upgrade to private protected when using C# 7.2 - internal string ElementNameInternal { get => _elementNameInternal; set => _elementNameInternal = XmlHelper.XmlConvertToElementName(value?.Trim()); } - private string _elementNameInternal; + internal string ElementNameInternal { get => _elementNameInternal; set => _elementNameInternal = XmlHelper.XmlConvertToElementName(value?.Trim() ?? string.Empty); } + private string _elementNameInternal = string.Empty; /// /// Value inside the XML element @@ -79,7 +83,8 @@ protected XmlElementBase(string elementName, Layout elementValue) /// /// Auto indent and create new lines /// - /// + /// Default: + /// public bool IndentXml { get; set; } /// @@ -97,20 +102,30 @@ protected XmlElementBase(string elementName, Layout elementValue) public IList Attributes { get; } = new List(); /// - /// Gets or sets whether a ElementValue with empty value should be included in the output + /// Gets the collection of context properties that should be included with the other properties. /// - /// + /// + [ArrayParameter(typeof(TargetPropertyWithContext), "contextproperty")] + public List? ContextProperties { get; set; } + + /// + /// Gets or sets whether empty XML-element should be included in the output. + /// + /// Default: . Empty value is either null or empty string + /// public bool IncludeEmptyValue { get; set; } /// /// Gets or sets the option to include all properties from the log event (as XML) /// + /// Default: /// public bool IncludeEventProperties { get; set; } /// /// Gets or sets whether to include the contents of the dictionary. /// + /// Default: /// public bool IncludeScopeProperties { get => _includeScopeProperties ?? (_includeMdlc == true || _includeMdc == true); set => _includeScopeProperties = value; } private bool? _includeScopeProperties; @@ -120,7 +135,8 @@ protected XmlElementBase(string elementName, Layout elementValue) /// /// Gets or sets a value indicating whether to include contents of the dictionary. /// - /// + /// Default: + /// [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] public bool IncludeMdc { get => _includeMdc ?? false; set => _includeMdc = value; } @@ -131,7 +147,8 @@ protected XmlElementBase(string elementName, Layout elementValue) /// /// Gets or sets a value indicating whether to include contents of the dictionary. /// - /// + /// Default: + /// [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] public bool IncludeMdlc { get => _includeMdlc ?? false; set => _includeMdlc = value; } @@ -142,15 +159,16 @@ protected XmlElementBase(string elementName, Layout elementValue) /// /// Gets or sets the option to include all properties from the log event (as XML) /// - /// + /// Default: + /// [Obsolete("Replaced by IncludeEventProperties. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] public bool IncludeAllProperties { get => IncludeEventProperties; set => IncludeEventProperties = value; } /// - /// List of property names to exclude when is true + /// List of property names to exclude when is /// - /// + /// #if !NET35 public ISet ExcludeProperties { get; set; } #else @@ -165,7 +183,7 @@ protected XmlElementBase(string elementName, Layout elementValue) /// /// Skips closing element tag when having configured /// - /// + /// public string PropertiesElementName { get => _propertiesElementName; @@ -174,7 +192,7 @@ public string PropertiesElementName _propertiesElementName = value; _propertiesElementNameHasFormat = value?.IndexOf('{') >= 0; if (!_propertiesElementNameHasFormat) - _propertiesElementName = XmlHelper.XmlConvertToElementName(value?.Trim()); + _propertiesElementName = XmlHelper.XmlConvertToElementName(value?.Trim() ?? string.Empty); } } private string _propertiesElementName = DefaultPropertyName; @@ -185,40 +203,36 @@ public string PropertiesElementName /// /// When null (or empty) then key-attribute is not included /// - /// - /// Will replace newlines in attribute-value with - /// - /// + /// Default: key . Newlines in attribute-value will be replaced with + /// public string PropertiesElementKeyAttribute { get; set; } = DefaultPropertyKeyAttribute; /// /// XML attribute name to use when rendering property-value /// /// When null (or empty) then value-attribute is not included and - /// value is formatted as XML-element-value + /// value is formatted as XML-element-value. /// - /// - /// Skips closing element tag when using attribute for value - /// - /// Will replace newlines in attribute-value with - /// - /// - public string PropertiesElementValueAttribute { get; set; } + /// Default: . Newlines in attribute-value will be replaced with + /// + public string PropertiesElementValueAttribute { get; set; } = string.Empty; /// /// XML element name to use for rendering IList-collections items /// - /// + /// Default: item + /// public string PropertiesCollectionItemName { get; set; } = DefaultCollectionItemName; /// /// How far should the XML serializer follow object references before backing off /// - /// + /// Default: + /// public int MaxRecursionLimit { get; set; } = 1; private ObjectReflectionCache ObjectReflectionCache => _objectReflectionCache ?? (_objectReflectionCache = new ObjectReflectionCache(LoggingConfiguration.GetServiceProvider())); - private ObjectReflectionCache _objectReflectionCache; + private ObjectReflectionCache? _objectReflectionCache; private static readonly IEqualityComparer _referenceEqualsComparer = SingleItemOptimizedHashSet.ReferenceEqualityComparer.Default; private const int MaxXmlLength = 512 * 1024; @@ -227,24 +241,26 @@ protected override void InitializeLayout() { base.InitializeLayout(); + if (string.IsNullOrEmpty(ElementNameInternal)) + throw new NLogConfigurationException("XmlLayout Name-property must be assigned. Name is required for valid XML element."); + if (IncludeScopeProperties) ThreadAgnostic = false; if (IncludeEventProperties) ThreadAgnosticImmutable = true; - if (Attributes.Count > 1) + if (Attributes.Count > 0) { HashSet attributeValidator = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var attribute in Attributes) { if (string.IsNullOrEmpty(attribute.Name)) + throw new NLogConfigurationException($"XmlElement(Name={ElementNameInternal}): Contains invalid XmlAttribute with unassigned Name-property"); + + if (attributeValidator.Contains(attribute.Name)) { - Common.InternalLogger.Warn("XmlElement(ElementName={0}): Contains attribute with missing name (Ignored)", ElementNameInternal); - } - else if (attributeValidator.Contains(attribute.Name)) - { - Common.InternalLogger.Warn("XmlElement(ElementName={0}): Contains duplicate attribute name: {1} (Invalid xml)", ElementNameInternal, attribute.Name); + Common.InternalLogger.Warn("XmlElement(ElementName={0}): Contains duplicate XmlAttribute(Name={1}) (Invalid xml)", ElementNameInternal, attribute.Name); } else { @@ -253,8 +269,17 @@ protected override void InitializeLayout() } } + if (ContextProperties != null && ContextProperties.Count > 0) + { + foreach (var contextProperty in ContextProperties) + { + if (string.IsNullOrEmpty(contextProperty.Name)) + throw new NLogConfigurationException($"XmlElement(Name={ElementNameInternal}): Contains invalid ContextProperty with unassigned Name-property"); + } + } + var innerLayouts = LayoutWrapper.Inner is null ? ArrayHelper.Empty() : new[] { LayoutWrapper.Inner }; - _precalculateLayouts = (IncludeEventProperties || IncludeScopeProperties) ? null : ResolveLayoutPrecalculation(Attributes.Select(atr => atr.Layout).Concat(Elements.Where(elm => elm.Layout != null).Select(elm => elm.Layout)).Concat(innerLayouts)); + _precalculateLayouts = (IncludeEventProperties || IncludeScopeProperties) ? null : ResolveLayoutPrecalculation(Attributes.Select(atr => atr.Layout).Concat(Elements.Where(elm => elm.Layout != null).Select(elm => elm.Layout)).Concat(ContextProperties?.Select(ctx => ctx.Layout) ?? Enumerable.Empty()).Concat(innerLayouts)); } /// @@ -358,12 +383,16 @@ private void RenderXmlFormattedMessage(LogEventInfo logEvent, StringBuilder sb) private bool HasNestedXmlElements(LogEventInfo logEvent) { - if (LayoutWrapper.Inner != null) + var innerText = LayoutWrapper.Inner; + if (!ReferenceEquals(innerText, null) && !ReferenceEquals(innerText, Layout.Empty)) return true; if (Elements.Count > 0) return true; + if (ContextProperties?.Count > 0) + return true; + if (IncludeScopeProperties) return true; @@ -375,6 +404,18 @@ private bool HasNestedXmlElements(LogEventInfo logEvent) private void AppendLogEventXmlProperties(LogEventInfo logEventInfo, StringBuilder sb, int orgLength) { + if (ContextProperties != null) + { + foreach (var contextProperty in ContextProperties) + { + var propertyValue = contextProperty.RenderValue(logEventInfo); + if (!contextProperty.IncludeEmptyValue && StringHelpers.IsNullOrEmptyString(propertyValue)) + continue; + + AppendXmlPropertyValue(contextProperty.Name, propertyValue, sb, orgLength); + } + } + if (IncludeScopeProperties) { bool checkExcludeProperties = ExcludeProperties.Count > 0; @@ -406,7 +447,7 @@ private void AppendLogEventProperties(LogEventInfo logEventInfo, StringBuilder s return; bool checkExcludeProperties = ExcludeProperties.Count > 0; - using (var propertyEnumerator = logEventInfo.CreateOrUpdatePropertiesInternal().GetPropertyEnumerator()) + using (var propertyEnumerator = logEventInfo.CreatePropertiesInternal().GetPropertyEnumerator()) { while (propertyEnumerator.MoveNext()) { @@ -420,78 +461,90 @@ private void AppendLogEventProperties(LogEventInfo logEventInfo, StringBuilder s var propertyValue = prop.Value; if (!string.IsNullOrEmpty(prop.Format) && propertyValue is IFormattable formattedProperty) - propertyValue = formattedProperty.ToString(prop.Format, - logEventInfo.FormatProvider ?? LoggingConfiguration?.DefaultCultureInfo); + propertyValue = formattedProperty.ToString(prop.Format, System.Globalization.CultureInfo.InvariantCulture); else if (prop.CaptureType == MessageTemplates.CaptureType.Stringify) - propertyValue = Convert.ToString(prop.Value ?? string.Empty, - logEventInfo.FormatProvider ?? LoggingConfiguration?.DefaultCultureInfo); + propertyValue = Convert.ToString(prop.Value ?? string.Empty, System.Globalization.CultureInfo.InvariantCulture); AppendXmlPropertyObjectValue(prop.Name, propertyValue, sb, orgLength, default(SingleItemOptimizedHashSet), 0); } } } - private bool AppendXmlPropertyObjectValue(string propName, object propertyValue, StringBuilder sb, int orgLength, SingleItemOptimizedHashSet objectsInPath, int depth, bool ignorePropertiesElementName = false) + private bool AppendXmlPropertyObjectValue(string propName, object? propertyValue, StringBuilder sb, int orgLength, SingleItemOptimizedHashSet objectsInPath, int depth, bool ignorePropertiesElementName = false) { - var convertibleValue = propertyValue as IConvertible; - var objTypeCode = convertibleValue?.GetTypeCode() ?? (propertyValue is null ? TypeCode.Empty : TypeCode.Object); - if (objTypeCode != TypeCode.Object) + if (propertyValue is IConvertible convertibleValue) { - string xmlValueString = XmlHelper.XmlConvertToString(convertibleValue, objTypeCode, true); + var objTypeCode = convertibleValue.GetTypeCode(); + if (objTypeCode != TypeCode.Object) + { + string xmlValueString = XmlHelper.XmlConvertToString(convertibleValue, objTypeCode, true); + AppendXmlPropertyStringValue(propName, xmlValueString, sb, orgLength, false, ignorePropertiesElementName); + return true; + } + } + else if (propertyValue is null) + { + string xmlValueString = XmlHelper.XmlConvertToString(null, TypeCode.Empty, true); AppendXmlPropertyStringValue(propName, xmlValueString, sb, orgLength, false, ignorePropertiesElementName); + return true; } - else + + int beforeValueLength = sb.Length; + if (beforeValueLength > MaxXmlLength) { - int beforeValueLength = sb.Length; - if (beforeValueLength > MaxXmlLength) - { - return false; - } + return false; + } - int nextDepth = objectsInPath.Count == 0 ? depth : (depth + 1); // Allow serialization of list-items - if (nextDepth > MaxRecursionLimit) - { - return false; - } + int nextDepth = objectsInPath.Count == 0 ? depth : (depth + 1); // Allow serialization of list-items + if (nextDepth > MaxRecursionLimit) + { + return false; + } - if (objectsInPath.Contains(propertyValue)) - { - return false; - } + if (objectsInPath.Contains(propertyValue)) + { + return false; + } - if (propertyValue is System.Collections.IDictionary dict) + if (MaxRecursionLimit == 0 || (nextDepth == MaxRecursionLimit && !(propertyValue is System.Collections.IEnumerable))) + { + string xmlValueString = XmlHelper.XmlConvertToStringSafe(propertyValue); + AppendXmlPropertyStringValue(propName, xmlValueString, sb, orgLength, false, ignorePropertiesElementName); + return true; + } + + if (propertyValue is System.Collections.IDictionary dict) + { + using (StartCollectionScope(ref objectsInPath, dict)) { - using (StartCollectionScope(ref objectsInPath, dict)) - { - AppendXmlDictionaryObject(propName, dict, sb, orgLength, objectsInPath, nextDepth, ignorePropertiesElementName); - } + AppendXmlDictionaryObject(propName, dict, sb, orgLength, objectsInPath, nextDepth, ignorePropertiesElementName); } - else if (propertyValue is System.Collections.IEnumerable collection) + } + else if (propertyValue is System.Collections.IEnumerable collection) + { + if (ObjectReflectionCache.TryLookupExpandoObject(propertyValue, out var propertyValues)) { - if (ObjectReflectionCache.TryLookupExpandoObject(propertyValue, out var propertyValues)) - { - using (new SingleItemOptimizedHashSet.SingleItemScopedInsert(propertyValue, ref objectsInPath, false, _referenceEqualsComparer)) - { - AppendXmlObjectPropertyValues(propName, ref propertyValues, sb, orgLength, ref objectsInPath, nextDepth, ignorePropertiesElementName); - } - } - else + using (new SingleItemOptimizedHashSet.SingleItemScopedInsert(propertyValue, ref objectsInPath, false, _referenceEqualsComparer)) { - using (StartCollectionScope(ref objectsInPath, collection)) - { - AppendXmlCollectionObject(propName, collection, sb, orgLength, objectsInPath, nextDepth, ignorePropertiesElementName); - } + AppendXmlObjectPropertyValues(propName, ref propertyValues, sb, orgLength, ref objectsInPath, nextDepth, ignorePropertiesElementName); } } else { - using (new SingleItemOptimizedHashSet.SingleItemScopedInsert(propertyValue, ref objectsInPath, false, _referenceEqualsComparer)) + using (StartCollectionScope(ref objectsInPath, collection)) { - var propertyValues = ObjectReflectionCache.LookupObjectProperties(propertyValue); - AppendXmlObjectPropertyValues(propName, ref propertyValues, sb, orgLength, ref objectsInPath, nextDepth, ignorePropertiesElementName); + AppendXmlCollectionObject(propName, collection, sb, orgLength, objectsInPath, nextDepth, ignorePropertiesElementName); } } } + else + { + using (new SingleItemOptimizedHashSet.SingleItemScopedInsert(propertyValue, ref objectsInPath, false, _referenceEqualsComparer)) + { + var propertyValues = ObjectReflectionCache.LookupObjectProperties(propertyValue); + AppendXmlObjectPropertyValues(propName, ref propertyValues, sb, orgLength, ref objectsInPath, nextDepth, ignorePropertiesElementName); + } + } return true; } @@ -532,7 +585,11 @@ private void AppendXmlDictionaryObject(string propName, System.Collections.IDict if (beforeValueLength > MaxXmlLength) break; - if (!AppendXmlPropertyObjectValue(item.Key?.ToString(), item.Value, sb, orgLength, objectsInPath, depth)) + var propertyName = item.Key?.ToString() ?? string.Empty; + if (string.IsNullOrEmpty(propertyName)) + continue; + + if (!AppendXmlPropertyObjectValue(propertyName, item.Value, sb, orgLength, objectsInPath, depth)) { sb.Length = beforeValueLength; } @@ -559,13 +616,13 @@ private void AppendXmlObjectPropertyValues(string propName, ref ObjectReflection if (beforeValueLength > MaxXmlLength) break; - if (!property.HasNameAndValue) + if (string.IsNullOrEmpty(property.Name) || (!IncludeEmptyValue && StringHelpers.IsNullOrEmptyString(property.Value))) continue; var propertyTypeCode = property.TypeCode; if (propertyTypeCode != TypeCode.Object) { - string xmlValueString = XmlHelper.XmlConvertToString((IConvertible)property.Value, propertyTypeCode, true); + string xmlValueString = XmlHelper.XmlConvertToString((IConvertible?)property.Value, propertyTypeCode, true); AppendXmlPropertyStringValue(property.Name, xmlValueString, sb, orgLength, false, ignorePropertiesElementName); } else @@ -580,7 +637,7 @@ private void AppendXmlObjectPropertyValues(string propName, ref ObjectReflection AppendClosingPropertyTag(propNameElement, sb, ignorePropertiesElementName); } - private string AppendXmlPropertyValue(string propName, object propertyValue, StringBuilder sb, int orgLength, bool ignoreValue = false, bool ignorePropertiesElementName = false) + private string AppendXmlPropertyValue(string propName, object? propertyValue, StringBuilder sb, int orgLength, bool ignoreValue = false, bool ignorePropertiesElementName = false) { string xmlValueString = ignoreValue ? string.Empty : XmlHelper.XmlConvertToStringSafe(propertyValue); return AppendXmlPropertyStringValue(propName, xmlValueString, sb, orgLength, ignoreValue, ignorePropertiesElementName); @@ -591,7 +648,7 @@ private string AppendXmlPropertyStringValue(string propName, string xmlValueStri if (string.IsNullOrEmpty(PropertiesElementName)) return string.Empty; // Not supported - propName = propName?.Trim(); + propName = propName?.Trim() ?? string.Empty; if (string.IsNullOrEmpty(propName)) return string.Empty; // Not supported diff --git a/src/NLog/Layouts/XML/XmlLayout.cs b/src/NLog/Layouts/XML/XmlLayout.cs index 566bbec4e4..b025e3a6dd 100644 --- a/src/NLog/Layouts/XML/XmlLayout.cs +++ b/src/NLog/Layouts/XML/XmlLayout.cs @@ -52,7 +52,7 @@ public class XmlLayout : XmlElementBase /// Initializes a new instance of the class. /// public XmlLayout() - : this(DefaultRootElementName, null) + : this(DefaultRootElementName, Layout.Empty) { } @@ -68,9 +68,7 @@ public XmlLayout(string elementName, Layout elementValue) : base(elementName, el /// /// Name of the root XML element /// - /// - /// Default value "logevent" - /// + /// [Required] Default: logevent /// public string ElementName { @@ -81,6 +79,7 @@ public string ElementName /// /// Value inside the root XML element /// + /// Default: /// public Layout ElementValue { @@ -91,6 +90,7 @@ public Layout ElementValue /// /// Determines whether or not this attribute will be Xml encoded. /// + /// Default: /// public bool ElementEncode { diff --git a/src/NLog/LogEventBuilder.cs b/src/NLog/LogEventBuilder.cs index e98b4541a1..09fafaf502 100644 --- a/src/NLog/LogEventBuilder.cs +++ b/src/NLog/LogEventBuilder.cs @@ -47,13 +47,13 @@ namespace NLog public struct LogEventBuilder { private readonly ILogger _logger; - private readonly LogEventInfo _logEvent; + private readonly LogEventInfo? _logEvent; /// /// Initializes a new instance of the class. /// /// The to send the log event. - public LogEventBuilder([NotNull] ILogger logger) + public LogEventBuilder(ILogger logger) { _logger = Guard.ThrowIfNull(logger); _logEvent = new LogEventInfo() { LoggerName = _logger.Name }; @@ -64,7 +64,7 @@ public LogEventBuilder([NotNull] ILogger logger) /// /// The to send the log event. /// The log level. LogEvent is only created when is enabled for - public LogEventBuilder([NotNull] ILogger logger, [NotNull] LogLevel logLevel) + public LogEventBuilder(ILogger logger, LogLevel logLevel) { _logger = Guard.ThrowIfNull(logger); @@ -83,28 +83,25 @@ public LogEventBuilder([NotNull] ILogger logger, [NotNull] LogLevel logLevel) /// /// The logger to write the log event to /// - [NotNull] public ILogger Logger => _logger; /// /// Logging event that will be written /// - [CanBeNull] - public LogEventInfo LogEvent => _logEvent is null ? null : ResolveLogEvent(_logEvent); + public LogEventInfo? LogEvent => _logEvent is null ? null : ResolveLogEvent(_logEvent); /// /// Sets a per-event context property on the logging event. /// /// The name of the context property. /// The value of the context property. - public LogEventBuilder Property([NotNull] string propertyName, T propertyValue) + public LogEventBuilder Property(string propertyName, T? propertyValue) { Guard.ThrowIfNull(propertyName); - - if (_logEvent is null) - return this; - - _logEvent.Properties[propertyName] = propertyValue; + if (_logEvent != null) + { + _logEvent.Properties[propertyName] = propertyValue; + } return this; } @@ -112,23 +109,39 @@ public LogEventBuilder Property([NotNull] string propertyName, T propertyValu /// Sets multiple per-event context properties on the logging event. /// /// The properties to set. - public LogEventBuilder Properties([NotNull] IEnumerable> properties) + public LogEventBuilder Properties(IEnumerable> properties) { Guard.ThrowIfNull(properties); + if (_logEvent != null) + { + foreach (var property in properties) + _logEvent.Properties[property.Key] = property.Value; + } + return this; + } - if (_logEvent is null) - return this; - - foreach (var property in properties) - _logEvent.Properties[property.Key] = property.Value; +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Sets multiple per-event context properties on the logging event. + /// + /// The properties to set. + public LogEventBuilder Properties(ReadOnlySpan<(string, object?)> properties) + { + if (_logEvent != null) + { + var dictionary = _logEvent.CreatePropertiesInternal(initialCapacity: properties.Length); + foreach (var property in properties) + dictionary[property.Item1] = property.Item2; + } return this; } +#endif /// /// Sets the information of the logging event. /// /// The exception information of the logging event. - public LogEventBuilder Exception(Exception exception) + public LogEventBuilder Exception(Exception? exception) { if (_logEvent != null) { @@ -171,12 +184,12 @@ public LogEventBuilder Message([Localizable(false)] string message) /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public LogEventBuilder Message([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public LogEventBuilder Message([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (_logEvent != null) { _logEvent.Message = message; - _logEvent.Parameters = new object[] { argument }; + _logEvent.Parameters = new object?[] { argument }; } return this; } @@ -190,12 +203,12 @@ public LogEventBuilder Message([Localizable(false)][StructuredMessage /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public LogEventBuilder Message([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public LogEventBuilder Message([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (_logEvent != null) { _logEvent.Message = message; - _logEvent.Parameters = new object[] { argument1, argument2 }; + _logEvent.Parameters = new object?[] { argument1, argument2 }; } return this; } @@ -211,12 +224,12 @@ public LogEventBuilder Message([Localizable(false)][Stru /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public LogEventBuilder Message([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public LogEventBuilder Message([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (_logEvent != null) { _logEvent.Message = message; - _logEvent.Parameters = new object[] { argument1, argument2, argument3 }; + _logEvent.Parameters = new object?[] { argument1, argument2, argument3 }; } return this; } @@ -227,7 +240,7 @@ public LogEventBuilder Message([Localizable( /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public LogEventBuilder Message([Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public LogEventBuilder Message([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (_logEvent != null) { @@ -237,6 +250,24 @@ public LogEventBuilder Message([Localizable(false)][StructuredMessageTemplate] s return this; } +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Sets the log message and parameters for formatting on the logging event. + /// + /// A containing format items. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public LogEventBuilder Message([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (_logEvent != null) + { + _logEvent.Message = message; + _logEvent.Parameters = args.IsEmpty ? null : args.ToArray(); + } + return this; + } +#endif + /// /// Sets the log message and parameters for formatting on the logging event. /// @@ -244,7 +275,7 @@ public LogEventBuilder Message([Localizable(false)][StructuredMessageTemplate] s /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public LogEventBuilder Message(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public LogEventBuilder Message(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (_logEvent != null) { @@ -263,14 +294,14 @@ public LogEventBuilder Message(IFormatProvider formatProvider, [Localizable(fals /// The full path of the source file that contains the caller. This is set at by the compiler. /// The line number in the source file at which the method is called. This is set at by the compiler. #if !NET35 - public LogEventBuilder Callsite(string callerClassName = null, - [CallerMemberName] string callerMemberName = null, - [CallerFilePath] string callerFilePath = null, + public LogEventBuilder Callsite(string? callerClassName = null, + [CallerMemberName] string? callerMemberName = null, + [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) #else - public LogEventBuilder Callsite(string callerClassName = null, - string callerMemberName = null, - string callerFilePath = null, + public LogEventBuilder Callsite(string? callerClassName = null, + string? callerMemberName = null, + string? callerFilePath = null, int callerLineNumber = 0) #endif { @@ -289,14 +320,14 @@ public LogEventBuilder Callsite(string callerClassName = null, /// The full path of the source file that contains the caller. This is set at by the compiler. /// The line number in the source file at which the method is called. This is set at by the compiler. #if !NET35 - public void Log(LogLevel logLevel = null, - [CallerMemberName] string callerMemberName = null, - [CallerFilePath] string callerFilePath = null, + public void Log(LogLevel? logLevel = null, + [CallerMemberName] string? callerMemberName = null, + [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) #else - public void Log(LogLevel logLevel = null, - string callerMemberName = null, - string callerFilePath = null, + public void Log(LogLevel? logLevel = null, + string? callerMemberName = null, + string? callerFilePath = null, int callerLineNumber = 0) #endif { @@ -328,7 +359,7 @@ public void Log(Type wrapperType) } } - private LogEventInfo ResolveLogEvent(LogEventInfo logEvent, LogLevel logLevel = null) + private LogEventInfo ResolveLogEvent(LogEventInfo logEvent, LogLevel? logLevel = null) { if (logLevel is null) { @@ -340,7 +371,7 @@ private LogEventInfo ResolveLogEvent(LogEventInfo logEvent, LogLevel logLevel = logEvent.Level = logLevel; } - if (logEvent.Message is null && logEvent.Exception != null && _logger.IsEnabled(logEvent.Level)) + if ((logEvent.Message is null || ReferenceEquals(logEvent.Message, string.Empty)) && logEvent.Exception != null && _logger.IsEnabled(logEvent.Level)) { logEvent.FormatProvider = NLog.Internal.ExceptionMessageFormatProvider.Instance; logEvent.Message = "{0}"; diff --git a/src/NLog/LogEventInfo.cs b/src/NLog/LogEventInfo.cs index d668f30600..2e5ac0ef75 100644 --- a/src/NLog/LogEventInfo.cs +++ b/src/NLog/LogEventInfo.cs @@ -59,18 +59,18 @@ public class LogEventInfo /// /// The formatted log message. /// - private string _formattedMessage; + private string? _formattedMessage; /// /// The log message including any parameter placeholders /// private string _message; - private object[] _parameters; - private IFormatProvider _formatProvider; - private LogMessageFormatter _messageFormatter; - private IDictionary _layoutCache; - private PropertiesDictionary _properties; + private object?[]? _parameters; + private IFormatProvider? _formatProvider; + private LogMessageFormatter? _messageFormatter; + private IDictionary? _layoutCache; + private PropertiesDictionary? _properties; private int _sequenceId; /// @@ -79,6 +79,9 @@ public class LogEventInfo public LogEventInfo() { TimeStamp = TimeSource.Current.Time; + _message = string.Empty; + LoggerName = string.Empty; + Level = LogLevel.Off; } /// @@ -87,7 +90,7 @@ public LogEventInfo() /// Log level. /// Override default Logger name. Default is used when null /// Log message including parameter placeholders. - public LogEventInfo(LogLevel level, string loggerName, [Localizable(false)] string message) + public LogEventInfo(LogLevel level, string? loggerName, [Localizable(false)] string message) : this(level, loggerName, null, message, null, null) { } @@ -99,7 +102,7 @@ public LogEventInfo(LogLevel level, string loggerName, [Localizable(false)] stri /// Override default Logger name. Default is used when null /// Log message including parameter placeholders. /// Already parsed message template parameters. - public LogEventInfo(LogLevel level, string loggerName, [Localizable(false)] string message, IList messageTemplateParameters) + public LogEventInfo(LogLevel level, string? loggerName, [Localizable(false)] string message, IList? messageTemplateParameters) : this(level, loggerName, null, message, null, null) { if (messageTemplateParameters != null) @@ -123,13 +126,38 @@ public LogEventInfo(LogLevel level, string loggerName, [Localizable(false)] stri /// Pre-formatted log message for ${message}. /// Log message-template including parameter placeholders for ${message:raw=true}. /// Already parsed message template parameters. - public LogEventInfo(LogLevel level, string loggerName, [Localizable(false)] string formattedMessage, [Localizable(false)] string messageTemplate, IList messageTemplateParameters) + public LogEventInfo(LogLevel level, string? loggerName, [Localizable(false)] string formattedMessage, [Localizable(false)] string messageTemplate, IList? messageTemplateParameters) : this(level, loggerName, messageTemplate, messageTemplateParameters) { _formattedMessage = formattedMessage; _messageFormatter = (l) => l._formattedMessage ?? l.Message ?? string.Empty; } +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER + /// + /// Initializes a new instance of the class. + /// + /// Log level. + /// Override default Logger name. Default is used when null + /// Pre-formatted log message for ${message}. + /// Log message-template including parameter placeholders for ${message:raw=true}. + /// Already parsed message template parameters. + public LogEventInfo(LogLevel level, string? loggerName, [Localizable(false)] string formattedMessage, [Localizable(false)] string messageTemplate, ReadOnlySpan messageTemplateParameters) + : this(level, loggerName, messageTemplate) + { + _formattedMessage = formattedMessage; + _messageFormatter = (l) => l._formattedMessage ?? l.Message ?? string.Empty; + + if (messageTemplateParameters.Length > 0) + { + var messageProperties = new MessageTemplateParameter[messageTemplateParameters.Length]; + for (int i = 0; i < messageTemplateParameters.Length; ++i) + messageProperties[i] = messageTemplateParameters[i]; + _properties = new PropertiesDictionary(messageProperties); + } + } +#endif + #if !NET35 /// /// Initializes a new instance of the class. @@ -138,12 +166,21 @@ public LogEventInfo(LogLevel level, string loggerName, [Localizable(false)] stri /// Override default Logger name. Default is used when null /// Log message. /// List of event-properties - public LogEventInfo(LogLevel level, string loggerName, [Localizable(false)] string message, IReadOnlyList> eventProperties) + public LogEventInfo(LogLevel level, string? loggerName, [Localizable(false)] string message, IReadOnlyList>? eventProperties) : this(level, loggerName, null, message, null, null) { - if (eventProperties?.Count > 0) + if (eventProperties != null) { - _properties = new PropertiesDictionary(eventProperties); + var propertyCount = eventProperties.Count; + if (propertyCount > 0) + { + _properties = new PropertiesDictionary(propertyCount); + for (int i = 0; i < propertyCount; ++i) + { + var property = eventProperties[i]; + _properties[property.Key] = property.Value; + } + } } } #endif @@ -156,7 +193,7 @@ public LogEventInfo(LogLevel level, string loggerName, [Localizable(false)] stri /// An IFormatProvider that supplies culture-specific formatting information. /// Log message including parameter placeholders. /// Parameter array. - public LogEventInfo(LogLevel level, string loggerName, IFormatProvider formatProvider, [Localizable(false)] string message, object[] parameters) + public LogEventInfo(LogLevel level, string? loggerName, IFormatProvider? formatProvider, [Localizable(false)] string message, object?[]? parameters) : this(level, loggerName, formatProvider, message, parameters, null) { } @@ -170,11 +207,11 @@ public LogEventInfo(LogLevel level, string loggerName, IFormatProvider formatPro /// Log message including parameter placeholders. /// Parameter array. /// Exception information. - public LogEventInfo(LogLevel level, string loggerName, IFormatProvider formatProvider, [Localizable(false)] string message, object[] parameters, Exception exception) - : this() + public LogEventInfo(LogLevel level, string? loggerName, IFormatProvider? formatProvider, [Localizable(false)] string message, object?[]? parameters, Exception? exception) { + TimeStamp = TimeSource.Current.Time; Level = level; - LoggerName = loggerName; + LoggerName = loggerName ?? string.Empty; _formatProvider = formatProvider; _message = message; Parameters = parameters; @@ -182,10 +219,13 @@ public LogEventInfo(LogLevel level, string loggerName, IFormatProvider formatPro } /// - /// Gets the unique identifier of log event which is automatically generated - /// and monotonously increasing. + /// Gets the sequence number for this LogEvent, which monotonously increasing for each LogEvent until int-overflow + /// + /// Marked obsolete with NLog 6.0, instead use ${counter:sequence=global} or ${guid:GeneratedFromLogEvent=true} /// // ReSharper disable once InconsistentNaming + [Obsolete("Use ${counter:sequence=global} instead of ${sequenceid}. Marked obsolete with NLog 6.0")] + [EditorBrowsable(EditorBrowsableState.Never)] public int SequenceID { get @@ -206,9 +246,8 @@ public int SequenceID /// public LogLevel Level { get; set; } - [CanBeNull] internal CallSiteInformation CallSiteInformation { get; private set; } + internal CallSiteInformation? CallSiteInformation { get; private set; } - [NotNull] internal CallSiteInformation GetCallSiteInformationInternal() { return CallSiteInformation ?? (CallSiteInformation = new CallSiteInformation()); } /// @@ -221,34 +260,35 @@ public int SequenceID /// /// Gets the stack frame of the method that did the logging. /// - [Obsolete("Instead use ${callsite} or CallerMemberName. Marked obsolete on NLog 5.3")] - public StackFrame UserStackFrame => CallSiteInformation?.UserStackFrame; + [Obsolete("Instead use ${callsite} or CallerMemberName. Marked obsolete with NLog 5.3")] + public StackFrame? UserStackFrame => CallSiteInformation?.UserStackFrame; /// /// Gets the number index of the stack frame that represents the user /// code (not the NLog code). /// + [Obsolete("Instead use ${callsite} or CallerMemberName. Marked obsolete with NLog 5.4")] public int UserStackFrameNumber => CallSiteInformation?.UserStackFrameNumberLegacy ?? CallSiteInformation?.UserStackFrameNumber ?? 0; /// /// Gets the entire stack trace. /// - public StackTrace StackTrace => CallSiteInformation?.StackTrace; + public StackTrace? StackTrace => CallSiteInformation?.StackTrace; /// /// Gets the callsite class name /// - public string CallerClassName => CallSiteInformation?.GetCallerClassName(null, true, true, true); + public string? CallerClassName => CallSiteInformation?.GetCallerClassName(null, true, true, true); /// /// Gets the callsite member function name /// - public string CallerMemberName => CallSiteInformation?.GetCallerMethodName(null, false, true, true); + public string? CallerMemberName => CallSiteInformation?.GetCallerMethodName(null, false, true, true); /// /// Gets the callsite source file path /// - public string CallerFilePath => CallSiteInformation?.GetCallerFilePath(0); + public string? CallerFilePath => CallSiteInformation?.GetCallerFilePath(0); /// /// Gets the callsite source file line number @@ -258,13 +298,11 @@ public int SequenceID /// /// Gets or sets the exception information. /// - [CanBeNull] - public Exception Exception { get; set; } + public Exception? Exception { get; set; } /// /// Gets or sets the logger name. /// - [CanBeNull] public string LoggerName { get; set; } /// @@ -284,7 +322,7 @@ public string Message /// /// Gets or sets the parameter values or null if no parameters have been specified. /// - public object[] Parameters + public object?[]? Parameters { get => _parameters; set @@ -299,7 +337,7 @@ public object[] Parameters /// Gets or sets the format provider that was provided while logging or /// when no formatProvider was specified. /// - public IFormatProvider FormatProvider + public IFormatProvider? FormatProvider { get => _formatProvider; set @@ -343,7 +381,7 @@ public string FormattedMessage CalcFormattedMessage(); } - return _formattedMessage; + return _formattedMessage ?? Message; } } @@ -354,49 +392,53 @@ public bool HasProperties { get { - if (_properties != null) + if (_properties is null) { - return _properties.Count > 0; - } - else - { - return CreateOrUpdatePropertiesInternal(false)?.Count > 0; + return TryCreatePropertiesInternal()?.Count > 0; } + return _properties.Count > 0; } } /// /// Gets the dictionary of per-event context properties. /// - public IDictionary Properties => CreateOrUpdatePropertiesInternal(); + public IDictionary Properties => _properties ?? CreatePropertiesInternal(); /// /// Gets the dictionary of per-event context properties. - /// Internal helper for the PropertiesDictionary type. /// - /// Create the event-properties dictionary, even if no initial template parameters /// Provided when having parsed the message template and capture template parameters (else null) - /// - internal PropertiesDictionary CreateOrUpdatePropertiesInternal(bool forceCreate = true, IList templateParameters = null) + internal PropertiesDictionary? TryCreatePropertiesInternal(IList? templateParameters = null) { var properties = _properties; if (properties is null) { - if (forceCreate || templateParameters?.Count > 0 || (templateParameters is null && HasMessageTemplateParameters)) + if (templateParameters?.Count > 0 || (templateParameters is null && HasMessageTemplateParameters)) { - properties = new PropertiesDictionary(templateParameters); - Interlocked.CompareExchange(ref _properties, properties, null); - if (templateParameters is null && (!forceCreate || HasMessageTemplateParameters)) - { - // Trigger capture of MessageTemplateParameters from logevent-message - CalcFormattedMessage(); - } + return CreatePropertiesInternal(templateParameters); } } else if (templateParameters != null) { - properties.MessageProperties = templateParameters; + properties.ResetMessageProperties(templateParameters); + } + return properties; + } + + internal PropertiesDictionary CreatePropertiesInternal(IList? templateParameters = null, int initialCapacity = 0) + { + if (_properties is null) + { + PropertiesDictionary properties = templateParameters is null ? new PropertiesDictionary(initialCapacity) : new PropertiesDictionary(templateParameters); + Interlocked.CompareExchange(ref _properties, properties, null); + if (templateParameters is null && HasMessageTemplateParameters) + { + // Trigger capture of MessageTemplateParameters from logevent-message + CalcFormattedMessage(); + } } + return _properties; } @@ -421,7 +463,7 @@ public MessageTemplateParameters MessageTemplateParameters { get { - if (_properties != null && _properties.MessageProperties.Count > 0) + if (_properties?.MessageProperties.Count > 0) { return new MessageTemplateParameters(_properties.MessageProperties, _message, _parameters); } @@ -452,7 +494,7 @@ public static LogEventInfo CreateNullEvent() /// Override default Logger name. Default is used when null /// The message. /// Instance of . - public static LogEventInfo Create(LogLevel logLevel, string loggerName, [Localizable(false)] string message) + public static LogEventInfo Create(LogLevel logLevel, string? loggerName, [Localizable(false)] string message) { return new LogEventInfo(logLevel, loggerName, null, message, null, null); } @@ -467,7 +509,7 @@ public static LogEventInfo Create(LogLevel logLevel, string loggerName, [Localiz /// The parameters. /// Instance of . [MessageTemplateFormatMethod("message")] - public static LogEventInfo Create(LogLevel logLevel, string loggerName, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object[] parameters) + public static LogEventInfo Create(LogLevel logLevel, string? loggerName, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object?[]? parameters) { return new LogEventInfo(logLevel, loggerName, formatProvider, message, parameters, null); } @@ -480,12 +522,12 @@ public static LogEventInfo Create(LogLevel logLevel, string loggerName, IFormatP /// The format provider. /// The message. /// Instance of . - public static LogEventInfo Create(LogLevel logLevel, string loggerName, IFormatProvider formatProvider, object message) + public static LogEventInfo Create(LogLevel logLevel, string? loggerName, IFormatProvider? formatProvider, object? message) { - Exception exception = message as Exception; + Exception? exception = message as Exception; if (exception is null && message is LogEventInfo logEvent) { - logEvent.LoggerName = loggerName; + logEvent.LoggerName = loggerName ?? logEvent.LoggerName; logEvent.Level = logLevel; logEvent.FormatProvider = formatProvider ?? logEvent.FormatProvider; return logEvent; @@ -503,7 +545,7 @@ public static LogEventInfo Create(LogLevel logLevel, string loggerName, IFormatP /// The format provider. /// The message. /// Instance of . - public static LogEventInfo Create(LogLevel logLevel, string loggerName, Exception exception, IFormatProvider formatProvider, [Localizable(false)] string message) + public static LogEventInfo Create(LogLevel logLevel, string? loggerName, Exception? exception, IFormatProvider? formatProvider, [Localizable(false)] string message) { return new LogEventInfo(logLevel, loggerName, formatProvider, message, null, exception); } @@ -519,7 +561,7 @@ public static LogEventInfo Create(LogLevel logLevel, string loggerName, Exceptio /// The parameters. /// Instance of . [MessageTemplateFormatMethod("message")] - public static LogEventInfo Create(LogLevel logLevel, string loggerName, Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object[] parameters) + public static LogEventInfo Create(LogLevel logLevel, string? loggerName, Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object?[]? parameters) { return new LogEventInfo(logLevel, loggerName, formatProvider, message, parameters, exception); } @@ -543,11 +585,21 @@ public override string ToString() return $"Log Event: Logger='{LoggerName}' Level={Level} Message='{FormattedMessage}'"; } + /// + /// Sets the stack trace for the event info. + /// + /// The stack trace. + public void SetStackTrace(StackTrace stackTrace) + { + GetCallSiteInformationInternal().SetStackTrace(stackTrace, default(int?)); + } + /// /// Sets the stack trace for the event info. /// /// The stack trace. /// Index of the first user stack frame within the stack trace (Negative means NLog should skip stackframes from System-assemblies). + [Obsolete("Instead use SetStackTrace or SetCallerInfo. Marked obsolete with NLog 5.4")] public void SetStackTrace(StackTrace stackTrace, int userStackFrame) { GetCallSiteInformationInternal().SetStackTrace(stackTrace, userStackFrame >= 0 ? userStackFrame : (int?)null); @@ -560,16 +612,16 @@ public void SetStackTrace(StackTrace stackTrace, int userStackFrame) /// /// /// - public void SetCallerInfo(string callerClassName, string callerMemberName, string callerFilePath, int callerLineNumber) + public void SetCallerInfo(string? callerClassName, string? callerMemberName, string? callerFilePath, int callerLineNumber) { GetCallSiteInformationInternal().SetCallerInfo(callerClassName, callerMemberName, callerFilePath, callerLineNumber); } - internal void AddCachedLayoutValue(Layout layout, object value) + internal void AddCachedLayoutValue(Layout layout, object? value) { if (_layoutCache is null) { - var dictionary = new Dictionary(); + var dictionary = new Dictionary(); dictionary[layout] = value; // Faster than collection initializer if (Interlocked.CompareExchange(ref _layoutCache, dictionary, null) is null) { @@ -582,7 +634,7 @@ internal void AddCachedLayoutValue(Layout layout, object value) } } - internal bool TryGetCachedLayoutValue(Layout layout, out object value) + internal bool TryGetCachedLayoutValue(Layout layout, out object? value) { if (_layoutCache is null) { @@ -598,21 +650,34 @@ internal bool TryGetCachedLayoutValue(Layout layout, out object value) } } - private static bool NeedToPreformatMessage(object[] parameters) + private static bool NeedToPreformatMessage(object?[]? parameters) { // we need to preformat message if it contains any parameters which could possibly // do logging in their ToString() if (parameters is null || parameters.Length == 0) - { return false; - } if (parameters.Length > 5) + return true; // too many parameters, too costly to check + + foreach (var parameter in parameters) { - // too many parameters, too costly to check - return true; + if (!IsSafeToDeferFormatting(parameter)) + return true; } + return false; + } + +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + internal static bool NeedToPreformatMessage(in ReadOnlySpan parameters) + { + if (parameters.IsEmpty) + return false; + + if (parameters.Length > 5) + return true; // too many parameters, too costly to check + foreach (var parameter in parameters) { if (!IsSafeToDeferFormatting(parameter)) @@ -621,8 +686,9 @@ private static bool NeedToPreformatMessage(object[] parameters) return false; } +#endif - private static bool IsSafeToDeferFormatting(object value) + private static bool IsSafeToDeferFormatting(object? value) { return Convert.GetTypeCode(value) != TypeCode.Object; } @@ -635,7 +701,7 @@ internal bool IsLogEventThreadAgnosticImmutable() if (_formattedMessage != null && _parameters?.Length > 0) return false; - var properties = CreateOrUpdatePropertiesInternal(false); + var properties = TryCreatePropertiesInternal(); if (properties is null || properties.Count == 0) return true; // No mutable state, no need to precalculate @@ -663,17 +729,20 @@ private static bool HasImmutableProperties(PropertiesDictionary properties) else { // Already spent the time on allocating a Dictionary, also have time for an enumerator - foreach (var property in properties) + using (var propertyEnumerator = properties.GetPropertyEnumerator()) { - if (!IsSafeToDeferFormatting(property.Value)) - return false; + while (propertyEnumerator.MoveNext()) + { + if (!IsSafeToDeferFormatting(propertyEnumerator.Current.Value)) + return false; + } } } return true; } - internal void SetMessageFormatter([NotNull] LogMessageFormatter messageFormatter, [CanBeNull] LogMessageFormatter singleTargetMessageFormatter) + internal void SetMessageFormatter(LogMessageFormatter messageFormatter, LogMessageFormatter? singleTargetMessageFormatter) { bool hasCustomMessageFormatter = _messageFormatter != null; if (!hasCustomMessageFormatter) @@ -758,14 +827,16 @@ private void ResetFormattedMessage(bool rebuildMessageTemplateParameters) private bool ResetMessageTemplateParameters() { - if (_properties != null) - { - if (HasMessageTemplateParameters) - _properties.MessageProperties = null; + if (_properties is null) + return false; - return _properties.MessageProperties.Count == 0; + if (HasMessageTemplateParameters) + { + _properties.ResetMessageProperties(); + return true; } - return false; + + return _properties.MessageProperties.Count == 0; } } } diff --git a/src/NLog/LogFactory-T.cs b/src/NLog/LogFactory-T.cs index b86c9e9619..dbd2649b5a 100644 --- a/src/NLog/LogFactory-T.cs +++ b/src/NLog/LogFactory-T.cs @@ -63,11 +63,7 @@ public class LogFactory : LogFactory [MethodImpl(MethodImplOptions.NoInlining)] public new T GetCurrentClassLogger() { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 var className = Internal.StackTraceUsageUtils.GetClassFullName(new StackFrame(1, false)); -#else - var className = Internal.StackTraceUsageUtils.GetClassFullName(); -#endif return GetLogger(className); } } diff --git a/src/NLog/LogFactory.cs b/src/NLog/LogFactory.cs index 6861832c2d..72336b1ceb 100644 --- a/src/NLog/LogFactory.cs +++ b/src/NLog/LogFactory.cs @@ -36,12 +36,9 @@ namespace NLog using System; using System.Collections.Generic; using System.ComponentModel; - using System.Diagnostics; using System.Globalization; using System.Linq; - using System.Reflection; using System.Runtime.CompilerServices; - using System.Security; using System.Text; using System.Threading; using JetBrains.Annotations; @@ -54,23 +51,25 @@ namespace NLog /// Creates and manages instances of objects. /// public class LogFactory : IDisposable +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +, IAsyncDisposable +#endif { private static readonly TimeSpan DefaultFlushTimeout = TimeSpan.FromSeconds(15); - [Obsolete("For unit testing only. Marked obsolete on NLog 5.0")] - private static IAppDomain currentAppDomain; - private static AppEnvironmentWrapper defaultAppEnvironment; + private static AppEnvironmentWrapper? defaultAppEnvironment; /// /// Internal for unit tests /// internal readonly object _syncRoot = new object(); private readonly LoggerCache _loggerCache = new LoggerCache(); - [NotNull] private ServiceRepositoryInternal _serviceRepository = new ServiceRepositoryInternal(); - private IAppEnvironment _currentAppEnvironment; - internal LoggingConfiguration _config; + private readonly ServiceRepositoryInternal _serviceRepository; + private readonly IAppEnvironment _currentAppEnvironment; + internal LoggingConfiguration? _config; internal LogMessageFormatter ActiveMessageFormatter; - internal LogMessageFormatter SingleTargetMessageFormatter; + internal LogMessageFormatter? SingleTargetMessageFormatter; + internal LogMessageTemplateFormatter? AutoMessageTemplateFormatter; private LogLevel _globalThreshold = LogLevel.MinLevel; private bool _configLoaded; private int _supendLoggingCounter; @@ -80,7 +79,7 @@ public class LogFactory : IDisposable /// When this property is null, the default file paths ( are used. /// [Obsolete("Replaced by LogFactory.Setup().LoadConfigurationFromFile(). Marked obsolete on NLog 5.2")] - private List _candidateConfigFilePaths; + private List? _candidateConfigFilePaths; private readonly ILoggingConfigurationLoader _configLoader; @@ -90,44 +89,40 @@ public class LogFactory : IDisposable /// /// Note can be null when unloading configuration at shutdown. /// - public event EventHandler ConfigurationChanged; + public event EventHandler? ConfigurationChanged; -#if !NETSTANDARD1_3 /// - /// Obsolete and replaced by with NLog v5.2. - /// Occurs when logging gets reloaded. + /// Event that is raised when the current Process / AppDomain terminates. /// - [Obsolete("Replaced by ConfigurationChanged, but check args.ActivatedConfiguration != null. Marked obsolete on NLog 5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public event EventHandler ConfigurationReloaded; -#endif - - private static event EventHandler LoggerShutdown; - -#if !NETSTANDARD1_3 - /// - /// Initializes static members of the LogManager class. - /// - static LogFactory() + private static event EventHandler LoggerShutdown { - RegisterEvents(DefaultAppEnvironment); + add + { + if (_loggerShutdown is null) + { + InternalLogger.Debug("Registered shutdown event handler for ProcessExit."); + DefaultAppEnvironment.ProcessExit += OnLoggerShutdown; + } + _loggerShutdown += value; + } + remove + { + _loggerShutdown -= value; + if (_loggerShutdown is null && defaultAppEnvironment != null) + { + InternalLogger.Debug("Unregistered shutdown event handler for ProcessExit."); + defaultAppEnvironment.ProcessExit -= OnLoggerShutdown; + } + } } -#endif + private static event EventHandler? _loggerShutdown; /// /// Initializes a new instance of the class. /// public LogFactory() -#pragma warning disable CS0618 // Type or member is obsolete -#if !NETSTANDARD1_3 - : this(new LoggingConfigurationWatchableFileLoader(DefaultAppEnvironment)) -#else : this(new LoggingConfigurationFileLoader(DefaultAppEnvironment)) -#endif -#pragma warning restore CS0618 // Type or member is obsolete { - _serviceRepository.TypeRegistered += ServiceRepository_TypeRegistered; - RefreshMessageFormatter(); } /// @@ -149,65 +144,35 @@ public LogFactory(LoggingConfiguration config) /// /// The config loader /// The custom AppEnvironmnet override - internal LogFactory(ILoggingConfigurationLoader configLoader, IAppEnvironment appEnvironment = null) + internal LogFactory(ILoggingConfigurationLoader configLoader, IAppEnvironment? appEnvironment = null) { _configLoader = configLoader; - _currentAppEnvironment = appEnvironment; -#if !NETSTANDARD1_3 + _currentAppEnvironment = appEnvironment ?? DefaultAppEnvironment; LoggerShutdown += OnStopLogging; -#endif - } - - /// - /// Gets the current . - /// - [Obsolete("For unit testing only. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IAppDomain CurrentAppDomain - { - get => currentAppDomain ?? DefaultAppEnvironment.AppDomain; - set - { - if (defaultAppEnvironment != null) - UnregisterEvents(defaultAppEnvironment); - - currentAppDomain = value; - - if (value != null && defaultAppEnvironment != null) - { - defaultAppEnvironment.AppDomain = value; - UnregisterEvents(defaultAppEnvironment); - RegisterEvents(defaultAppEnvironment); - } - } + _serviceRepository = new ServiceRepositoryInternal(this); + _serviceRepository.TypeRegistered += ServiceRepository_TypeRegistered; + ActiveMessageFormatter = RefreshMessageFormatter(); } internal static IAppEnvironment DefaultAppEnvironment { get { -#pragma warning disable CS0618 // Type or member is obsolete - return defaultAppEnvironment ?? (defaultAppEnvironment = new AppEnvironmentWrapper(currentAppDomain ?? (currentAppDomain = -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - new AppDomainWrapper(AppDomain.CurrentDomain) -#else - new FakeAppDomain() -#endif - ))); -#pragma warning restore CS0618 // Type or member is obsolete + return defaultAppEnvironment ?? (defaultAppEnvironment = new AppEnvironmentWrapper()); } } - internal IAppEnvironment CurrentAppEnvironment - { - get => _currentAppEnvironment ?? DefaultAppEnvironment; - set => _currentAppEnvironment = value; - } + internal IAppEnvironment CurrentAppEnvironment => _currentAppEnvironment; + + /// + /// Repository of interfaces used by NLog to allow override for dependency injection + /// + public ServiceRepository ServiceRepository => _serviceRepository; /// /// Gets or sets a value indicating whether exceptions should be thrown. See also . /// - /// A value of true if exception should be thrown; otherwise, false. + /// A value of if exception should be thrown; otherwise, . /// By default exceptions are not thrown under any circumstances. public bool ThrowExceptions { get; set; } @@ -216,7 +181,7 @@ internal IAppEnvironment CurrentAppEnvironment /// /// If null then is used. /// - /// A value of true if exception should be thrown; otherwise, false. + /// A value of if exception should be thrown; otherwise, . /// /// This option is for backwards-compatibility. /// By default exceptions are not thrown under any circumstances. @@ -240,11 +205,9 @@ public bool AutoShutdown if (value != _autoShutdown) { _autoShutdown = value; -#if !NETSTANDARD1_3 LoggerShutdown -= OnStopLogging; if (value) LoggerShutdown += OnStopLogging; -#endif } } } @@ -256,7 +219,8 @@ public bool AutoShutdown /// /// Setter will re-configure all -objects, so no need to also call /// - public LoggingConfiguration Configuration + [CanBeNull] + public LoggingConfiguration? Configuration { get { @@ -282,7 +246,7 @@ public LoggingConfiguration Configuration { lock (_syncRoot) { - LoggingConfiguration oldConfig = _config; + LoggingConfiguration? oldConfig = _config; if (oldConfig != null) { InternalLogger.Info("Closing old configuration."); @@ -295,7 +259,6 @@ public LoggingConfiguration Configuration { _config = value; _configLoaded = false; - _configLoader.Activated(this, value); } else { @@ -311,35 +274,17 @@ private void ActivateLoggingConfiguration(LoggingConfiguration config) { if (_config is null) { -#pragma warning disable CS0618 // Type or member is obsolete LogNLogAssemblyVersion(); -#pragma warning restore CS0618 // Type or member is obsolete } _config = config; _configLoaded = true; _config.OnConfigurationAssigned(this); - _configLoader.Activated(this, _config); _config.Dump(); ReconfigExistingLoggers(); InternalLogger.Info("Configuration initialized."); } - /// - /// Repository of interfaces used by NLog to allow override for dependency injection - /// - [NotNull] - public ServiceRepository ServiceRepository - { - get => _serviceRepository; - internal set - { - _serviceRepository.TypeRegistered -= ServiceRepository_TypeRegistered; - _serviceRepository = (value as ServiceRepositoryInternal) ?? new ServiceRepositoryInternal(true); - _serviceRepository.TypeRegistered += ServiceRepository_TypeRegistered; - } - } - private void ServiceRepository_TypeRegistered(object sender, ServiceRepositoryUpdateEventArgs e) { _config?.CheckForMissingServiceTypes(e.ServiceType); @@ -350,18 +295,22 @@ private void ServiceRepository_TypeRegistered(object sender, ServiceRepositoryUp } } - private void RefreshMessageFormatter() + private LogMessageFormatter RefreshMessageFormatter() { var messageFormatter = _serviceRepository.GetService(); ActiveMessageFormatter = messageFormatter.FormatMessage; if (messageFormatter is LogMessageTemplateFormatter templateFormatter) { - SingleTargetMessageFormatter = new LogMessageTemplateFormatter(_serviceRepository, templateFormatter.EnableMessageTemplateParser == true, true).FormatMessage; + var logMessageTemplateFormatter = new LogMessageTemplateFormatter(this, templateFormatter.EnableMessageTemplateParser == true, true); + SingleTargetMessageFormatter = logMessageTemplateFormatter.FormatMessage; + AutoMessageTemplateFormatter = templateFormatter.EnableMessageTemplateParser == true ? null : logMessageTemplateFormatter; } else { SingleTargetMessageFormatter = null; + AutoMessageTemplateFormatter = null; } + return ActiveMessageFormatter; } /// @@ -391,8 +340,7 @@ public LogLevel GlobalThreshold /// /// Specific culture info or null to use /// - [CanBeNull] - public CultureInfo DefaultCultureInfo + public CultureInfo? DefaultCultureInfo { get => _config is null ? _defaultCultureInfo : _config.DefaultCultureInfo; set @@ -402,26 +350,22 @@ public CultureInfo DefaultCultureInfo _defaultCultureInfo = value; } } - internal CultureInfo _defaultCultureInfo; + internal CultureInfo? _defaultCultureInfo; - [Obsolete("LogFactory should be minimal. Marked obsolete with NLog v5.3")] internal static void LogNLogAssemblyVersion() { - if (!InternalLogger.IsInfoEnabled) - return; - try { - InternalLogger.LogAssemblyVersion(typeof(LogFactory).GetAssembly()); + AssemblyHelpers.LogAssemblyVersion(typeof(LogFactory).Assembly); } catch (Exception ex) { InternalLogger.Debug(ex, "Not running in full trust"); } } + /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting - /// unmanaged resources. + /// Shutdown logging /// public void Dispose() { @@ -429,6 +373,43 @@ public void Dispose() GC.SuppressFinalize(this); } +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER + /// + /// Flush any pending log messages, and shutdown logging + /// + public async System.Threading.Tasks.ValueTask DisposeAsync() + { + lock (_syncRoot) + { + if (_config is null || !_configLoaded || _isDisposing) + { + DisposeInternal(false); + return; + } + } + + try + { + await FlushAsync(CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + InternalLogger.Error(ex, "LogFactory DisposeAsync Flush Failed."); + } + + try + { + var disposeTimeout = System.Threading.Tasks.Task.Delay(DefaultFlushTimeout).ContinueWith(prevTask => throw new System.Threading.Tasks.TaskCanceledException("NLog LogFactory Dispose Timeout")); + await System.Threading.Tasks.TaskExtensions.Unwrap(System.Threading.Tasks.Task.WhenAny(System.Threading.Tasks.Task.Run(() => DisposeInternal()), disposeTimeout)).ConfigureAwait(false); + GC.SuppressFinalize(this); + } + catch (Exception ex) + { + InternalLogger.Error(ex, "LogFactory DisposeAsync Failed."); + } + } +#endif + /// /// Begins configuration of the LogFactory options using fluent interface /// @@ -465,11 +446,7 @@ public Logger CreateNullLogger() [MethodImpl(MethodImplOptions.NoInlining)] public Logger GetCurrentClassLogger() { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - var className = StackTraceUsageUtils.GetClassFullName(new StackFrame(1, false)); -#else - var className = StackTraceUsageUtils.GetClassFullName(); -#endif + var className = StackTraceUsageUtils.GetClassFullName(new System.Diagnostics.StackFrame(1, false)); return GetLogger(className); } @@ -485,11 +462,7 @@ public Logger GetCurrentClassLogger() [MethodImpl(MethodImplOptions.NoInlining)] public T GetCurrentClassLogger() where T : Logger, new() { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - var className = StackTraceUsageUtils.GetClassFullName(new StackFrame(1, false)); -#else - var className = StackTraceUsageUtils.GetClassFullName(); -#endif + var className = StackTraceUsageUtils.GetClassFullName(new System.Diagnostics.StackFrame(1, false)); return GetLogger(className); } @@ -508,11 +481,7 @@ public Logger GetCurrentClassLogger() [EditorBrowsable(EditorBrowsableState.Never)] public Logger GetCurrentClassLogger([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type loggerType) { -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - var className = StackTraceUsageUtils.GetClassFullName(new StackFrame(1, false)); -#else - var className = StackTraceUsageUtils.GetClassFullName(); -#endif + var className = StackTraceUsageUtils.GetClassFullName(new System.Diagnostics.StackFrame(1, false)); return GetLogger(className, loggerType ?? typeof(Logger)); } @@ -571,10 +540,9 @@ private bool RefreshExistingLoggers() purgeObsoleteLoggers = loggers.Count != _loggerCache.Count; } - var loggingRules = _config?.GetLoggingRulesThreadSafe(); foreach (var logger in loggers) { - logger.SetConfiguration(BuildLoggerConfiguration(logger.Name, loggingRules)); + logger.SetConfiguration(BuildLoggerConfiguration(logger.Name)); } return purgeObsoleteLoggers; @@ -661,36 +629,98 @@ public void Flush(AsyncContinuation asyncContinuation, int timeoutMilliseconds) /// Maximum time to allow for the flush. Any messages after that time will be discarded. public void Flush(AsyncContinuation asyncContinuation, TimeSpan timeout) { - FlushInternal(timeout, asyncContinuation ?? (ex => { })); + FlushInternal(timeout, asyncContinuation); } - private void FlushInternal(TimeSpan flushTimeout, AsyncContinuation asyncContinuation) + private bool FlushInternal(TimeSpan flushTimeout, AsyncContinuation? asyncContinuation) { - InternalLogger.Debug("LogFactory Flush with timeout={0} secs", flushTimeout.TotalSeconds); + InternalLogger.Debug("LogFactory Starting Flush with timeout={0} secs", flushTimeout.TotalSeconds); try { - LoggingConfiguration config; + LoggingConfiguration? config; lock (_syncRoot) { - config = _config; // Flush should not attempt to auto-load Configuration + config = _config; // Flush should not attempt to auto-load Configuration + if (!_configLoaded) + config = null; } + if (config is null) { asyncContinuation?.Invoke(null); + return true; } - else + + asyncContinuation = asyncContinuation is null ? null : AsyncHelpers.PreventMultipleCalls(asyncContinuation); + + using (var waitForFlush = new ManualResetEvent(false)) { - config.FlushAllTargets(flushTimeout, asyncContinuation, ThrowExceptions); + var ev = waitForFlush; + var flushTimeoutHandler = config.FlushAllTargets((ex) => { asyncContinuation?.Invoke(ex); ev?.Set(); }); + if (flushTimeoutHandler is null) + { + asyncContinuation?.Invoke(null); + return true; + } + else + { + bool flushCompleted = waitForFlush.WaitOne(flushTimeout); + ev = null; // Avoid object-disposed on late flush completion + flushTimeoutHandler.Invoke(null); + asyncContinuation?.Invoke(flushCompleted ? null : new NLogRuntimeException("LogFactory Flush Timeout")); + return flushCompleted; + } } } catch (Exception ex) { InternalLogger.Error(ex, "LogFactory failed to flush targets."); asyncContinuation?.Invoke(ex); + return false; } } +#if !NET35 && !NET40 + /// + /// Flush any pending log messages + /// + public System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) + { + InternalLogger.Debug("LogFactory Starting Flush Async"); + + LoggingConfiguration? config; + lock (_syncRoot) + { + config = _config; + if (config is null || !_configLoaded) + { +#if NET45 + return System.Threading.Tasks.Task.FromResult(null); +#else + return System.Threading.Tasks.Task.CompletedTask; +#endif + } + } + + var flushCompleted = new System.Threading.Tasks.TaskCompletionSource(); + var flushTimeoutHandler = config.FlushAllTargets((ex) => flushCompleted.SetResult(true)); + if (flushTimeoutHandler is null) + { +#if NET45 + return System.Threading.Tasks.Task.FromResult(null); +#else + return System.Threading.Tasks.Task.CompletedTask; +#endif + } + + var timeout = cancellationToken.CanBeCanceled ? -1 : (int)DefaultFlushTimeout.TotalMilliseconds; + var flushTimeout = System.Threading.Tasks.Task.Delay(timeout, cancellationToken).ContinueWith(prevTask => throw new System.Threading.Tasks.TaskCanceledException("NLog LogFactory Flush Timeout")); + flushTimeout.ContinueWith(prevTask => { flushTimeoutHandler.Invoke(null); flushCompleted.TrySetCanceled(); }); + return System.Threading.Tasks.TaskExtensions.Unwrap(System.Threading.Tasks.Task.WhenAny(flushCompleted.Task, flushTimeout)); + } +#endif + /// /// Suspends the logging, and returns object for using-scope so scope-exit calls /// @@ -756,35 +786,10 @@ protected virtual void OnConfigurationChanged(LoggingConfigurationChangedEventAr ConfigurationChanged?.Invoke(this, e); } -#if !NETSTANDARD1_3 - /// - /// Obsolete and replaced by with NLog 5.2. - /// - /// Raises the event when the configuration is reloaded. - /// - /// Event arguments - [Obsolete("Replaced by ConfigurationChanged, but check args.ActivatedConfiguration != null. Marked obsolete on NLog 5.2")] - protected virtual void OnConfigurationReloaded(LoggingConfigurationReloadedEventArgs e) - { - ConfigurationReloaded?.Invoke(this, e); - } - - [Obsolete("Replaced by ConfigurationChanged, but check args.ActivatedConfiguration != null. Marked obsolete on NLog 5.2")] - internal void NotifyConfigurationReloaded(LoggingConfigurationReloadedEventArgs eventArgs) - { - OnConfigurationReloaded(eventArgs); - } -#endif - - /// - /// Change this method with NLog v6 to completely disconnect LogFactory from Targets/Layouts - /// - Remove LoggingRule-List-parameter - /// - Return ITargetWithFilterChain[] - /// - internal TargetWithFilterChain[] BuildLoggerConfiguration(string loggerName, List loggingRules) + internal ITargetWithFilterChain[] BuildLoggerConfiguration(string loggerName) { var globalThreshold = IsLoggingEnabled() ? GlobalThreshold : LogLevel.Off; - return _config?.BuildLoggerConfiguration(loggerName, globalThreshold, loggingRules) ?? TargetWithFilterChain.NoTargetsByLevel; + return _config?.BuildLoggerConfiguration(loggerName, globalThreshold) ?? TargetWithFilterChain.NoTargetsByLevel; } /// @@ -792,7 +797,7 @@ internal TargetWithFilterChain[] BuildLoggerConfiguration(string loggerName, Lis /// private bool _isDisposing; - private void Close(TimeSpan flushTimeout) + private void DisposeInternal(bool closeConfig = true) { if (_isDisposing) { @@ -803,10 +808,7 @@ private void Close(TimeSpan flushTimeout) _serviceRepository.TypeRegistered -= ServiceRepository_TypeRegistered; -#if !NETSTANDARD1_3 LoggerShutdown -= OnStopLogging; - ConfigurationReloaded = null; // Release event listeners -#endif if (Monitor.TryEnter(_syncRoot, 500)) { @@ -817,45 +819,38 @@ private void Close(TimeSpan flushTimeout) var oldConfig = _config; if (_configLoaded && oldConfig != null) { - CloseOldConfig(flushTimeout, oldConfig); + if (closeConfig) + CloseOldConfig(oldConfig); + else + InternalLogger.Warn("Target flush timeout. One or more targets did not complete flush operation, skipping target close."); } } finally { Monitor.Exit(_syncRoot); + ConfigurationChanged = null; // Release event listeners } } + else + { + ConfigurationChanged = null; // Release event listeners + } - ConfigurationChanged = null; // Release event listeners + InternalLogger.Info("LogFactory has been disposed."); } - private void CloseOldConfig(TimeSpan flushTimeout, LoggingConfiguration oldConfig) + private void CloseOldConfig(LoggingConfiguration oldConfig) { try { oldConfig.OnConfigurationAssigned(null); - bool attemptClose = true; - - if (flushTimeout > TimeSpan.Zero) - { - attemptClose = oldConfig.FlushAllTargets(flushTimeout, null, false); - } - // Disable all loggers, so things become quiet _config = null; _configLoaded = true; ReconfigExistingLoggers(); - if (!attemptClose) - { - InternalLogger.Warn("Target flush timeout. One or more targets did not complete flush operation, skipping target close."); - } - else - { - // Flush completed within timeout, lets try and close down - oldConfig.Close(); - } + oldConfig.Close(); OnConfigurationChanged(new LoggingConfigurationChangedEventArgs(null, oldConfig)); } @@ -866,15 +861,15 @@ private void CloseOldConfig(TimeSpan flushTimeout, LoggingConfiguration oldConfi } /// - /// Releases unmanaged and - optionally - managed resources. + /// Shutdown logging without flushing async /// - /// True to release both managed and unmanaged resources; - /// false to release only unmanaged resources. + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (disposing) { - Close(DefaultFlushTimeout); + DisposeInternal(); } } @@ -883,7 +878,7 @@ protected virtual void Dispose(bool disposing) /// public void Shutdown() { - InternalLogger.Info("Shutdown() called. Logger closing..."); + InternalLogger.Info("LogFactory shutting down ..."); if (!_isDisposing && _configLoaded) { lock (_syncRoot) @@ -896,7 +891,7 @@ public void Shutdown() ReconfigExistingLoggers(); // Disable all loggers, so things become quiet } } - InternalLogger.Info("Logger has been closed down."); + InternalLogger.Info("LogFactory shutdown completed."); } /// @@ -924,7 +919,7 @@ public IEnumerable GetCandidateConfigFilePaths() /// /// The file paths to the possible config file [Obsolete("Replaced by chaining LogFactory.Setup().LoadConfigurationFromFile(). Marked obsolete on NLog 5.2")] - internal IEnumerable GetCandidateConfigFilePaths(string filename) + internal IEnumerable GetCandidateConfigFilePaths(string? filename) { if (_candidateConfigFilePaths != null) return GetCandidateConfigFilePaths(); @@ -962,7 +957,7 @@ public void ResetCandidateConfigFilePath() _candidateConfigFilePaths = null; } - private Logger GetLoggerThreadSafe(string name, Type loggerType, Func loggerCreator) + private Logger GetLoggerThreadSafe(string name, Type loggerType, Func loggerCreator) { if (name is null) throw new ArgumentNullException(nameof(name), "Name of logger cannot be null"); @@ -971,7 +966,7 @@ private Logger GetLoggerThreadSafe(string name, Type loggerType, Func loggerCreator) + internal Logger CreateNewLogger(Type loggerType, Func loggerCreator) { try { - Logger newLogger = loggerCreator(loggerType); + Logger? newLogger = loggerCreator(loggerType); if (newLogger is null) { if (Logger.DefaultLoggerType.IsAssignableFrom(loggerType)) { throw new NLogRuntimeException($"GetLogger / GetCurrentClassLogger with type '{loggerType}' could not create instance of NLog Logger"); } - else if (ThrowExceptions) + else if (ThrowExceptions || LogManager.ThrowExceptions) { throw new NLogRuntimeException($"GetLogger / GetCurrentClassLogger with type '{loggerType}' does not inherit from NLog Logger"); } @@ -1045,9 +1039,9 @@ public LogFactory LoadConfiguration(string configFile) return LoadConfiguration(configFile, optional: false); } - internal LogFactory LoadConfiguration(string configFile, bool optional) + internal LogFactory LoadConfiguration(string? configFile, bool optional) { - var actualConfigFile = string.IsNullOrEmpty(configFile) ? "NLog.config" : configFile; + var actualConfigFile = (configFile is null || string.IsNullOrEmpty(configFile)) ? "NLog.config" : configFile; if (optional && string.Equals(actualConfigFile.Trim(), "NLog.config", StringComparison.OrdinalIgnoreCase) && _config != null) { return this; // Skip optional loading of default config, when config is already loaded @@ -1071,7 +1065,7 @@ internal LogFactory LoadConfiguration(string configFile, bool optional) return this; } - private string CreateFileNotFoundMessage(string configFile) + private string CreateFileNotFoundMessage(string? configFile) { var messageBuilder = new StringBuilder("Failed to load NLog LoggingConfiguration."); try @@ -1096,7 +1090,11 @@ private string CreateFileNotFoundMessage(string configFile) /// /// Logger cache key. /// - private struct LoggerCacheKey : IEquatable + private +#if !NETFRAMEWORK + readonly +#endif + struct LoggerCacheKey : IEquatable { public readonly string Name; public readonly Type ConcreteType; @@ -1107,29 +1105,16 @@ public LoggerCacheKey(string name, Type concreteType) ConcreteType = concreteType; } - /// - /// Serves as a hash function for a particular type. - /// public override int GetHashCode() { return ConcreteType.GetHashCode() ^ Name.GetHashCode(); } - /// - /// Determines if two objects are equal in value. - /// - /// Other object to compare to. - /// True if objects are equal, false otherwise. public override bool Equals(object obj) { return obj is LoggerCacheKey key && Equals(key); } - /// - /// Determines if two objects of the same type are equal in value. - /// - /// Other object to compare to. - /// True if objects are equal, false otherwise. public bool Equals(LoggerCacheKey other) { return (ConcreteType == other.ConcreteType) && string.Equals(other.Name, Name, StringComparison.Ordinal); @@ -1157,7 +1142,7 @@ public void InsertOrUpdate(LoggerCacheKey cacheKey, Logger logger) _loggerCache[cacheKey] = new WeakReference(logger); } - public Logger Retrieve(LoggerCacheKey cacheKey) + public Logger? Retrieve(LoggerCacheKey cacheKey) { if (_loggerCache.TryGetValue(cacheKey, out var loggerReference)) { @@ -1239,37 +1224,11 @@ void IDisposable.Dispose() } } - private static void RegisterEvents(IAppEnvironment appEnvironment) - { - if (appEnvironment is null) return; - - try - { - appEnvironment.ProcessExit += OnLoggerShutdown; - } - catch (Exception exception) - { - InternalLogger.Warn(exception, "Error setting up termination events."); - - if (exception.MustBeRethrown()) - { - throw; - } - } - } - - private static void UnregisterEvents(IAppEnvironment appEnvironment) - { - if (appEnvironment is null) return; - - appEnvironment.ProcessExit -= OnLoggerShutdown; - } - private static void OnLoggerShutdown(object sender, EventArgs args) { try { - LoggerShutdown?.Invoke(sender, args); + _loggerShutdown?.Invoke(null, args); } catch (Exception ex) { @@ -1279,7 +1238,7 @@ private static void OnLoggerShutdown(object sender, EventArgs args) } finally { - LoggerShutdown = null; + _loggerShutdown = null; if (defaultAppEnvironment != null) { defaultAppEnvironment.ProcessExit -= OnLoggerShutdown; // Unregister from AppDomain @@ -1295,22 +1254,22 @@ private void OnStopLogging(object sender, EventArgs args) //Exception: System.AppDomainUnloadedException //Message: Attempted to access an unloaded AppDomain. InternalLogger.Info("AppDomain Shutting down. LogFactory closing..."); - // Domain-Unload has to complete in about 2 secs on Windows-platform, before being terminated. - // Other platforms like Linux will fail when trying to spin up new threads at domain unload. - var flushTimeout = -#if !NETSTANDARD1_3 - PlatformDetector.IsWin32 ? TimeSpan.FromMilliseconds(1500) : -#endif - TimeSpan.Zero; - Close(flushTimeout); - InternalLogger.Info("LogFactory has been closed."); + bool closeConfig = true; + if (PlatformDetector.IsWin32) + { + // Domain-Unload has to complete in about 2 secs on Windows-platform, before being terminated. + // Other platforms like Linux will fail when trying to spin up new threads at domain unload. + closeConfig = FlushInternal(TimeSpan.FromMilliseconds(1500), null); + } + + DisposeInternal(closeConfig); } catch (Exception ex) { if (ex.MustBeRethrownImmediately()) throw; - InternalLogger.Error(ex, "LogFactory failed to close properly."); + InternalLogger.Error(ex, "LogFactory failed to close down."); } } } diff --git a/src/NLog/LogLevel.cs b/src/NLog/LogLevel.cs index 826e0095d4..260b967e29 100644 --- a/src/NLog/LogLevel.cs +++ b/src/NLog/LogLevel.cs @@ -157,7 +157,7 @@ private LogLevel(string name, int ordinal) /// The first level. /// The second level. /// The value of level1.Ordinal == level2.Ordinal. - public static bool operator ==(LogLevel level1, LogLevel level2) + public static bool operator ==(LogLevel? level1, LogLevel? level2) { if (ReferenceEquals(level1, level2)) return true; @@ -173,7 +173,7 @@ private LogLevel(string name, int ordinal) /// The first level. /// The second level. /// The value of level1.Ordinal != level2.Ordinal. - public static bool operator !=(LogLevel level1, LogLevel level2) + public static bool operator !=(LogLevel? level1, LogLevel? level2) { if (ReferenceEquals(level1, level2)) return false; @@ -189,7 +189,7 @@ private LogLevel(string name, int ordinal) /// The first level. /// The second level. /// The value of level1.Ordinal > level2.Ordinal. - public static bool operator >(LogLevel level1, LogLevel level2) + public static bool operator >(LogLevel? level1, LogLevel? level2) { if (ReferenceEquals(level1, level2)) return false; @@ -205,7 +205,7 @@ private LogLevel(string name, int ordinal) /// The first level. /// The second level. /// The value of level1.Ordinal >= level2.Ordinal. - public static bool operator >=(LogLevel level1, LogLevel level2) + public static bool operator >=(LogLevel? level1, LogLevel? level2) { if (ReferenceEquals(level1, level2)) return true; @@ -221,7 +221,7 @@ private LogLevel(string name, int ordinal) /// The first level. /// The second level. /// The value of level1.Ordinal < level2.Ordinal. - public static bool operator <(LogLevel level1, LogLevel level2) + public static bool operator <(LogLevel? level1, LogLevel? level2) { if (ReferenceEquals(level1, level2)) return false; @@ -237,7 +237,7 @@ private LogLevel(string name, int ordinal) /// The first level. /// The second level. /// The value of level1.Ordinal <= level2.Ordinal. - public static bool operator <=(LogLevel level1, LogLevel level2) + public static bool operator <=(LogLevel? level1, LogLevel? level2) { if (ReferenceEquals(level1, level2)) return true; @@ -345,7 +345,7 @@ public override string ToString() return _name; } - string IFormattable.ToString(string format, IFormatProvider formatProvider) + string IFormattable.ToString(string? format, IFormatProvider? formatProvider) { if (format is null || (!"D".Equals(format, StringComparison.OrdinalIgnoreCase))) return _name; @@ -360,7 +360,7 @@ public override int GetHashCode() } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return Equals(obj as LogLevel); } @@ -369,9 +369,9 @@ public override bool Equals(object obj) /// Determines whether the specified instance is equal to this instance. /// /// The to compare with this instance. - /// Value of true if the specified is equal to - /// this instance; otherwise, false. - public bool Equals(LogLevel other) + /// Value of if the specified is equal to + /// this instance; otherwise, . + public bool Equals(LogLevel? other) { return _ordinal == other?._ordinal; } @@ -386,9 +386,9 @@ public bool Equals(LogLevel other) /// greater than zero when this ordinal is greater than the /// other ordinal. /// - public int CompareTo(object obj) + public int CompareTo(object? obj) { - return CompareTo((LogLevel)obj); + return CompareTo(obj as LogLevel); } /// @@ -401,7 +401,7 @@ public int CompareTo(object obj) /// greater than zero when this ordinal is greater than the /// other ordinal. /// - public int CompareTo(LogLevel other) + public int CompareTo(LogLevel? other) { return _ordinal - (other ?? LogLevel.Off)._ordinal; } diff --git a/src/NLog/LogLevelTypeConverter.cs b/src/NLog/LogLevelTypeConverter.cs index f5d5ae3af0..0d6b01fe1e 100644 --- a/src/NLog/LogLevelTypeConverter.cs +++ b/src/NLog/LogLevelTypeConverter.cs @@ -53,7 +53,7 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c { var valueType = value?.GetType(); if (typeof(string).Equals(valueType)) - return LogLevel.FromString(value?.ToString()); + return LogLevel.FromString(value?.ToString() ?? string.Empty); else if (IsNumericType(valueType)) return LogLevel.FromOrdinal(Convert.ToInt32(value, CultureInfo.InvariantCulture)); else @@ -79,8 +79,10 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul return base.ConvertTo(context, culture, value, destinationType); } - private static bool IsNumericType(Type sourceType) + private static bool IsNumericType(Type? sourceType) { + if (sourceType is null) + return false; if (typeof(int).Equals(sourceType)) return true; if (typeof(uint).Equals(sourceType)) diff --git a/src/NLog/LogManager.cs b/src/NLog/LogManager.cs index 0c5279d6fd..18ee4fa998 100644 --- a/src/NLog/LogManager.cs +++ b/src/NLog/LogManager.cs @@ -38,7 +38,7 @@ namespace NLog using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; - + using JetBrains.Annotations; using NLog.Common; using NLog.Config; using NLog.Internal; @@ -51,13 +51,19 @@ namespace NLog /// public static class LogManager { - private static readonly LogFactory factory = new LogFactory(); - /// /// Gets the instance used in the . /// - /// Could be used to pass the to other methods - public static LogFactory LogFactory => factory; + public static LogFactory LogFactory => _logFactory ?? CreateLogFactorySingleton(); + private static LogFactory? _logFactory; + + private static LogFactory CreateLogFactorySingleton() + { + var logFactory = new LogFactory(); + if (!(System.Threading.Interlocked.CompareExchange(ref _logFactory, logFactory, null) is null)) + logFactory.Dispose(); // Raced by other thread, so dispose instance not needed + return _logFactory; + } /// /// Occurs when logging changes. Both when assigned to new config or config unloaded. @@ -67,37 +73,24 @@ public static class LogManager /// public static event EventHandler ConfigurationChanged { - add => factory.ConfigurationChanged += value; - remove => factory.ConfigurationChanged -= value; + add => LogFactory.ConfigurationChanged += value; + remove => LogFactory.ConfigurationChanged -= value; } -#if !NETSTANDARD1_3 - /// - /// Obsolete and replaced by with NLog v5.2. - /// Occurs when logging gets reloaded. - /// - [Obsolete("Replaced by ConfigurationChanged, but check args.ActivatedConfiguration != null. Marked obsolete on NLog 5.2")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static event EventHandler ConfigurationReloaded - { - add => factory.ConfigurationReloaded += value; - remove => factory.ConfigurationReloaded -= value; - } -#endif /// /// Gets or sets a value indicating whether NLog should throw exceptions. /// By default exceptions are not thrown under any circumstances. /// public static bool ThrowExceptions { - get => factory.ThrowExceptions; - set => factory.ThrowExceptions = value; + get => LogFactory.ThrowExceptions; + set => LogFactory.ThrowExceptions = value; } /// /// Gets or sets a value indicating whether should be thrown. /// - /// A value of true if exception should be thrown; otherwise, false. + /// A value of if exception should be thrown; otherwise, . /// /// This option is for backwards-compatibility. /// By default exceptions are not thrown under any circumstances. @@ -105,8 +98,8 @@ public static bool ThrowExceptions /// public static bool? ThrowConfigExceptions { - get => factory.ThrowConfigExceptions; - set => factory.ThrowConfigExceptions = value; + get => LogFactory.ThrowConfigExceptions; + set => LogFactory.ThrowConfigExceptions = value; } /// @@ -114,8 +107,8 @@ public static bool? ThrowConfigExceptions /// public static bool KeepVariablesOnReload { - get => factory.KeepVariablesOnReload; - set => factory.KeepVariablesOnReload = value; + get => LogFactory.KeepVariablesOnReload; + set => LogFactory.KeepVariablesOnReload = value; } /// @@ -124,8 +117,8 @@ public static bool KeepVariablesOnReload /// public static bool AutoShutdown { - get => factory.AutoShutdown; - set => factory.AutoShutdown = value; + get => LogFactory.AutoShutdown; + set => LogFactory.AutoShutdown = value; } /// @@ -134,10 +127,11 @@ public static bool AutoShutdown /// /// Setter will re-configure all -objects, so no need to also call /// - public static LoggingConfiguration Configuration + [CanBeNull] + public static LoggingConfiguration? Configuration { - get => factory.Configuration; - set => factory.Configuration = value; + get => LogFactory.Configuration; + set => LogFactory.Configuration = value; } /// @@ -145,8 +139,8 @@ public static LoggingConfiguration Configuration /// public static LogLevel GlobalThreshold { - get => factory.GlobalThreshold; - set => factory.GlobalThreshold = value; + get => LogFactory.GlobalThreshold; + set => LogFactory.GlobalThreshold = value; } /// @@ -175,8 +169,7 @@ public static LogFactory Setup(Action setupBuilder) [EditorBrowsable(EditorBrowsableState.Never)] public static LogFactory LoadConfiguration(string configFile) { - factory.LoadConfiguration(configFile); - return factory; + return LogFactory.LoadConfiguration(configFile); } /// @@ -200,7 +193,8 @@ public static void AddHiddenAssembly(Assembly assembly) [MethodImpl(MethodImplOptions.NoInlining)] public static Logger GetCurrentClassLogger() { - return factory.GetLogger(StackTraceUsageUtils.GetClassFullName()); + var className = StackTraceUsageUtils.GetClassFullName(new System.Diagnostics.StackFrame(1, false)); + return LogFactory.GetLogger(className); } /// @@ -219,7 +213,8 @@ public static Logger GetCurrentClassLogger() [EditorBrowsable(EditorBrowsableState.Never)] public static Logger GetCurrentClassLogger([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type loggerType) { - return factory.GetLogger(StackTraceUsageUtils.GetClassFullName(), loggerType); + var className = StackTraceUsageUtils.GetClassFullName(new System.Diagnostics.StackFrame(1, false)); + return LogFactory.GetLogger(className, loggerType); } /// @@ -228,7 +223,7 @@ public static Logger GetCurrentClassLogger([DynamicallyAccessedMembers(Dynamical /// Null logger which discards all log messages. public static Logger CreateNullLogger() { - return factory.CreateNullLogger(); + return LogFactory.CreateNullLogger(); } /// @@ -238,7 +233,7 @@ public static Logger CreateNullLogger() /// The logger reference. Multiple calls to GetLogger with the same argument aren't guaranteed to return the same logger reference. public static Logger GetLogger(string name) { - return factory.GetLogger(name); + return LogFactory.GetLogger(name); } /// @@ -254,7 +249,7 @@ public static Logger GetLogger(string name) [EditorBrowsable(EditorBrowsableState.Never)] public static Logger GetLogger(string name, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type loggerType) { - return factory.GetLogger(name, loggerType); + return LogFactory.GetLogger(name, loggerType); } /// @@ -264,7 +259,7 @@ public static Logger GetLogger(string name, [DynamicallyAccessedMembers(Dynamica /// public static void ReconfigExistingLoggers() { - factory.ReconfigExistingLoggers(); + LogFactory.ReconfigExistingLoggers(); } /// @@ -275,7 +270,7 @@ public static void ReconfigExistingLoggers() /// Purge garbage collected logger-items from the cache public static void ReconfigExistingLoggers(bool purgeObsoleteLoggers) { - factory.ReconfigExistingLoggers(purgeObsoleteLoggers); + LogFactory.ReconfigExistingLoggers(purgeObsoleteLoggers); } /// @@ -283,7 +278,7 @@ public static void ReconfigExistingLoggers(bool purgeObsoleteLoggers) /// public static void Flush() { - factory.Flush(); + LogFactory.Flush(); } /// @@ -292,7 +287,7 @@ public static void Flush() /// Maximum time to allow for the flush. Any messages after that time will be discarded. public static void Flush(TimeSpan timeout) { - factory.Flush(timeout); + LogFactory.Flush(timeout); } /// @@ -301,7 +296,7 @@ public static void Flush(TimeSpan timeout) /// Maximum time to allow for the flush. Any messages after that time will be discarded. public static void Flush(int timeoutMilliseconds) { - factory.Flush(timeoutMilliseconds); + LogFactory.Flush(timeoutMilliseconds); } /// @@ -310,7 +305,7 @@ public static void Flush(int timeoutMilliseconds) /// The asynchronous continuation. public static void Flush(AsyncContinuation asyncContinuation) { - factory.Flush(asyncContinuation); + LogFactory.Flush(asyncContinuation); } /// @@ -320,7 +315,7 @@ public static void Flush(AsyncContinuation asyncContinuation) /// Maximum time to allow for the flush. Any messages after that time will be discarded. public static void Flush(AsyncContinuation asyncContinuation, TimeSpan timeout) { - factory.Flush(asyncContinuation, timeout); + LogFactory.Flush(asyncContinuation, timeout); } /// @@ -330,7 +325,7 @@ public static void Flush(AsyncContinuation asyncContinuation, TimeSpan timeout) /// Maximum time to allow for the flush. Any messages after that time will be discarded. public static void Flush(AsyncContinuation asyncContinuation, int timeoutMilliseconds) { - factory.Flush(asyncContinuation, timeoutMilliseconds); + LogFactory.Flush(asyncContinuation, timeoutMilliseconds); } /// @@ -347,7 +342,7 @@ public static void Flush(AsyncContinuation asyncContinuation, int timeoutMillise [EditorBrowsable(EditorBrowsableState.Never)] public static IDisposable DisableLogging() { - return factory.SuspendLogging(); + return LogFactory.SuspendLogging(); } /// @@ -362,7 +357,7 @@ public static IDisposable DisableLogging() [EditorBrowsable(EditorBrowsableState.Never)] public static void EnableLogging() { - factory.ResumeLogging(); + LogFactory.ResumeLogging(); } /// @@ -376,7 +371,7 @@ public static void EnableLogging() /// To be used with C# using () statement. public static IDisposable SuspendLogging() { - return factory.SuspendLogging(); + return LogFactory.SuspendLogging(); } /// @@ -388,7 +383,7 @@ public static IDisposable SuspendLogging() /// public static void ResumeLogging() { - factory.ResumeLogging(); + LogFactory.ResumeLogging(); } /// @@ -402,7 +397,7 @@ public static void ResumeLogging() /// otherwise. public static bool IsLoggingEnabled() { - return factory.IsLoggingEnabled(); + return LogFactory.IsLoggingEnabled(); } /// @@ -410,7 +405,7 @@ public static bool IsLoggingEnabled() /// public static void Shutdown() { - factory.Shutdown(); + LogFactory.Shutdown(); } } } diff --git a/src/NLog/LogMessageFormatter.cs b/src/NLog/LogMessageFormatter.cs index 5cd1650340..b115dd4f62 100644 --- a/src/NLog/LogMessageFormatter.cs +++ b/src/NLog/LogMessageFormatter.cs @@ -38,5 +38,5 @@ namespace NLog /// /// Log event. /// Formatted message - public delegate string LogMessageFormatter(LogEventInfo logEvent); + public delegate string? LogMessageFormatter(LogEventInfo logEvent); } diff --git a/src/NLog/Logger-Conditional.cs b/src/NLog/Logger-Conditional.cs index c23b04c164..7aa4135693 100644 --- a/src/NLog/Logger-Conditional.cs +++ b/src/NLog/Logger-Conditional.cs @@ -36,6 +36,7 @@ namespace NLog using System; using System.ComponentModel; using System.Diagnostics; + using System.Runtime.CompilerServices; using JetBrains.Annotations; /// @@ -62,7 +63,7 @@ public partial class Logger /// Type of the value. /// The value to be written. [Conditional("DEBUG")] - public void ConditionalDebug(T value) + public void ConditionalDebug(T? value) { Debug(value); } @@ -74,7 +75,7 @@ public void ConditionalDebug(T value) /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. [Conditional("DEBUG")] - public void ConditionalDebug(IFormatProvider formatProvider, T value) + public void ConditionalDebug(IFormatProvider? formatProvider, T? value) { Debug(formatProvider, value); } @@ -97,11 +98,26 @@ public void ConditionalDebug(LogMessageGenerator messageFunc) /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void ConditionalDebug(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { Debug(exception, message, args); } +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message and exception at the Debug level. + /// Only executed when the DEBUG conditional compilation symbol is set. + /// A to be written. + /// An exception to be logged. + /// Arguments to format. + [Conditional("DEBUG")] + [MessageTemplateFormatMethod("message")] + public void ConditionalDebug(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + Debug(exception, message, args); + } +#endif + /// /// Writes the diagnostic message and exception at the Debug level. /// Only executed when the DEBUG conditional compilation symbol is set. @@ -111,7 +127,7 @@ public void ConditionalDebug(Exception exception, [Localizable(false)][Structure /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void ConditionalDebug(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { Debug(exception, formatProvider, message, args); } @@ -124,7 +140,7 @@ public void ConditionalDebug(Exception exception, IFormatProvider formatProvider /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { Debug(formatProvider, message, args); } @@ -146,11 +162,24 @@ public void ConditionalDebug([Localizable(false)] string message) /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { Debug(message, args); } +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message at the Debug level using the specified parameters. + /// Only executed when the DEBUG conditional compilation symbol is set. + /// A containing format items. + /// Arguments to format. + [Conditional("DEBUG")] + [MessageTemplateFormatMethod("message")] + public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + Debug(message, args); + } +#endif /// /// Writes the diagnostic message at the Debug level using the specified parameter and formatting it with the supplied format provider. @@ -161,7 +190,7 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// The argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { Debug(formatProvider, message, argument); } @@ -174,7 +203,7 @@ public void ConditionalDebug(IFormatProvider formatProvider, [Localiz /// The argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { Debug(message, argument); } @@ -190,7 +219,7 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTe /// The second argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { Debug(formatProvider, message, argument1, argument2); } @@ -205,7 +234,7 @@ public void ConditionalDebug(IFormatProvider formatProvi /// The second argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { Debug(message, argument1, argument2); } @@ -223,7 +252,7 @@ public void ConditionalDebug([Localizable(false)][Struct /// The third argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { Debug(formatProvider, message, argument1, argument2, argument3); } @@ -240,7 +269,7 @@ public void ConditionalDebug(IFormatProvider /// The third argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { Debug(message, argument1, argument2, argument3); } @@ -250,9 +279,13 @@ public void ConditionalDebug([Localizable(fa /// Only executed when the DEBUG conditional compilation symbol is set. /// A to be written. [Conditional("DEBUG")] - public void ConditionalDebug(object value) + [EditorBrowsable(EditorBrowsableState.Never)] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(object? value) { - Debug(value); + Debug(value); } /// @@ -261,9 +294,13 @@ public void ConditionalDebug(object value) /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [Conditional("DEBUG")] - public void ConditionalDebug(IFormatProvider formatProvider, object value) + [EditorBrowsable(EditorBrowsableState.Never)] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(IFormatProvider? formatProvider, object? value) { - Debug(formatProvider, value); + Debug(formatProvider, value); } /// @@ -273,10 +310,14 @@ public void ConditionalDebug(IFormatProvider formatProvider, object value) /// First argument to format. /// Second argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2) { - Debug(message, arg1, arg2); + Debug(message, arg1, arg2); } /// @@ -287,10 +328,14 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// Second argument to format. /// Third argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3) { - Debug(message, arg1, arg2, arg3); + Debug(message, arg1, arg2, arg3); } /// @@ -300,10 +345,14 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) { - Debug(formatProvider, message, argument); + Debug(formatProvider, message, argument); } /// @@ -312,10 +361,14 @@ public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, bool argument) { - Debug(message, argument); + Debug(message, argument); } /// @@ -325,10 +378,14 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) { - Debug(formatProvider, message, argument); + Debug(formatProvider, message, argument); } /// @@ -337,10 +394,14 @@ public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, char argument) { - Debug(message, argument); + Debug(message, argument); } /// @@ -350,10 +411,14 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) { - Debug(formatProvider, message, argument); + Debug(formatProvider, message, argument); } /// @@ -362,10 +427,14 @@ public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, byte argument) { - Debug(message, argument); + Debug(message, argument); } /// @@ -375,10 +444,14 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument) { - Debug(formatProvider, message, argument); + Debug(formatProvider, message, argument); } /// @@ -387,10 +460,14 @@ public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, string? argument) { - Debug(message, argument); + Debug(message, argument); } /// @@ -400,10 +477,14 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) { - Debug(formatProvider, message, argument); + Debug(formatProvider, message, argument); } /// @@ -412,10 +493,14 @@ public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, int argument) { - Debug(message, argument); + Debug(message, argument); } /// @@ -425,10 +510,14 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) { - Debug(formatProvider, message, argument); + Debug(formatProvider, message, argument); } /// @@ -437,10 +526,14 @@ public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, long argument) { - Debug(message, argument); + Debug(message, argument); } /// @@ -450,10 +543,14 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) { - Debug(formatProvider, message, argument); + Debug(formatProvider, message, argument); } /// @@ -462,10 +559,14 @@ public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, float argument) { - Debug(message, argument); + Debug(message, argument); } /// @@ -475,10 +576,14 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) { - Debug(formatProvider, message, argument); + Debug(formatProvider, message, argument); } /// @@ -487,10 +592,14 @@ public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, double argument) { - Debug(message, argument); + Debug(message, argument); } /// @@ -500,10 +609,14 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { - Debug(formatProvider, message, argument); + Debug(formatProvider, message, argument); } /// @@ -512,10 +625,14 @@ public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { - Debug(message, argument); + Debug(message, argument); } /// @@ -525,10 +642,14 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument) { - Debug(formatProvider, message, argument); + Debug(formatProvider, message, argument); } /// @@ -537,10 +658,14 @@ public void ConditionalDebug(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] string message, object? argument) { - Debug(message, argument); + Debug(message, argument); } #endregion @@ -556,7 +681,7 @@ public void ConditionalDebug([Localizable(false)][StructuredMessageTemplate] str /// Type of the value. /// The value to be written. [Conditional("DEBUG")] - public void ConditionalTrace(T value) + public void ConditionalTrace(T? value) { Trace(value); } @@ -568,7 +693,7 @@ public void ConditionalTrace(T value) /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. [Conditional("DEBUG")] - public void ConditionalTrace(IFormatProvider formatProvider, T value) + public void ConditionalTrace(IFormatProvider? formatProvider, T? value) { Trace(formatProvider, value); } @@ -591,11 +716,26 @@ public void ConditionalTrace(LogMessageGenerator messageFunc) /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void ConditionalTrace(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { Trace(exception, message, args); } +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message and exception at the Trace level. + /// Only executed when the DEBUG conditional compilation symbol is set. + /// A to be written. + /// An exception to be logged. + /// Arguments to format. + [Conditional("DEBUG")] + [MessageTemplateFormatMethod("message")] + public void ConditionalTrace(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + Trace(exception, message, args); + } +#endif + /// /// Writes the diagnostic message and exception at the Trace level. /// Only executed when the DEBUG conditional compilation symbol is set. @@ -605,7 +745,7 @@ public void ConditionalTrace(Exception exception, [Localizable(false)][Structure /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void ConditionalTrace(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { Trace(exception, formatProvider, message, args); } @@ -619,7 +759,7 @@ public void ConditionalTrace(Exception exception, IFormatProvider formatProvider /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { Trace(formatProvider, message, args); } @@ -641,10 +781,24 @@ public void ConditionalTrace([Localizable(false)] string message) /// Arguments to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) + { + Trace(message, args); + } + +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message at the Trace level using the specified parameters. + /// Only executed when the DEBUG conditional compilation symbol is set. + /// A containing format items. + /// Arguments to format. + [Conditional("DEBUG")] + [MessageTemplateFormatMethod("message")] + public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) { Trace(message, args); } +#endif /// /// Writes the diagnostic message at the Trace level using the specified parameter and formatting it with the supplied format provider. @@ -655,7 +809,7 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// The argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { Trace(formatProvider, message, argument); } @@ -668,7 +822,7 @@ public void ConditionalTrace(IFormatProvider formatProvider, [Localiz /// The argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { Trace(message, argument); } @@ -684,7 +838,7 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTe /// The second argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { Trace(formatProvider, message, argument1, argument2); } @@ -699,7 +853,7 @@ public void ConditionalTrace(IFormatProvider formatProvi /// The second argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { Trace(message, argument1, argument2); } @@ -717,7 +871,7 @@ public void ConditionalTrace([Localizable(false)][Struct /// The third argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { Trace(formatProvider, message, argument1, argument2, argument3); } @@ -734,7 +888,7 @@ public void ConditionalTrace(IFormatProvider /// The third argument to format. [Conditional("DEBUG")] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { Trace(message, argument1, argument2, argument3); } @@ -744,9 +898,13 @@ public void ConditionalTrace([Localizable(fa /// Only executed when the DEBUG conditional compilation symbol is set. /// A to be written. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalTrace(object value) { - Trace(value); + Trace(value); } /// @@ -755,9 +913,13 @@ public void ConditionalTrace(object value) /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [Conditional("DEBUG")] - public void ConditionalTrace(IFormatProvider formatProvider, object value) + [EditorBrowsable(EditorBrowsableState.Never)] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace(IFormatProvider? formatProvider, object? value) { - Trace(formatProvider, value); + Trace(formatProvider, value); } /// @@ -767,10 +929,14 @@ public void ConditionalTrace(IFormatProvider formatProvider, object value) /// First argument to format. /// Second argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2) { - Trace(message, arg1, arg2); + Trace(message, arg1, arg2); } /// @@ -781,10 +947,14 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// Second argument to format. /// Third argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3) { - Trace(message, arg1, arg2, arg3); + Trace(message, arg1, arg2, arg3); } /// @@ -794,10 +964,14 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) { - Trace(formatProvider, message, argument); + Trace(formatProvider, message, argument); } /// @@ -806,10 +980,14 @@ public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, bool argument) { - Trace(message, argument); + Trace(message, argument); } /// @@ -819,10 +997,14 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) { - Trace(formatProvider, message, argument); + Trace(formatProvider, message, argument); } /// @@ -831,10 +1013,14 @@ public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, char argument) { - Trace(message, argument); + Trace(message, argument); } /// @@ -844,10 +1030,14 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) { - Trace(formatProvider, message, argument); + Trace(formatProvider, message, argument); } /// @@ -856,10 +1046,14 @@ public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, byte argument) { - Trace(message, argument); + Trace(message, argument); } /// @@ -869,10 +1063,14 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument) { - Trace(formatProvider, message, argument); + Trace(formatProvider, message, argument); } /// @@ -881,10 +1079,14 @@ public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, string? argument) { - Trace(message, argument); + Trace(message, argument); } /// @@ -894,10 +1096,14 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) { - Trace(formatProvider, message, argument); + Trace(formatProvider, message, argument); } /// @@ -906,10 +1112,14 @@ public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, int argument) { - Trace(message, argument); + Trace(message, argument); } /// @@ -919,10 +1129,14 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) { - Trace(formatProvider, message, argument); + Trace(formatProvider, message, argument); } /// @@ -931,10 +1145,14 @@ public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, long argument) { - Trace(message, argument); + Trace(message, argument); } /// @@ -944,10 +1162,14 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) { - Trace(formatProvider, message, argument); + Trace(formatProvider, message, argument); } /// @@ -956,10 +1178,14 @@ public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, float argument) { - Trace(message, argument); + Trace(message, argument); } /// @@ -969,10 +1195,14 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) { - Trace(formatProvider, message, argument); + Trace(formatProvider, message, argument); } /// @@ -981,10 +1211,14 @@ public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, double argument) { - Trace(message, argument); + Trace(message, argument); } /// @@ -994,10 +1228,14 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { - Trace(formatProvider, message, argument); + Trace(formatProvider, message, argument); } /// @@ -1006,10 +1244,14 @@ public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { - Trace(message, argument); + Trace(message, argument); } /// @@ -1019,10 +1261,14 @@ public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] str /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument) { - Trace(formatProvider, message, argument); + Trace(formatProvider, message, argument); } /// @@ -1031,10 +1277,14 @@ public void ConditionalTrace(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [Conditional("DEBUG")] + [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void ConditionalTrace([Localizable(false)][StructuredMessageTemplate] string message, object? argument) { - Trace(message, argument); + Trace(message, argument); } diff --git a/src/NLog/Logger-V1Compat.cs b/src/NLog/Logger-V1Compat.cs index 60c818ce7c..748dd32a98 100644 --- a/src/NLog/Logger-V1Compat.cs +++ b/src/NLog/Logger-V1Compat.cs @@ -35,6 +35,7 @@ namespace NLog { using System; using System.ComponentModel; + using System.Runtime.CompilerServices; using JetBrains.Annotations; /// @@ -51,7 +52,10 @@ public partial class Logger : ILogger /// The log level. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Log(LogLevel level, object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, object? value) { if (IsEnabled(level)) { @@ -66,7 +70,10 @@ public void Log(LogLevel level, object value) /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Log(LogLevel level, IFormatProvider formatProvider, object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, object? value) { if (IsEnabled(level)) { @@ -83,11 +90,19 @@ public void Log(LogLevel level, IFormatProvider formatProvider, object value) /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, message, new[] { arg1, arg2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, arg1, arg2); +#else + WriteToTargets(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2 }); +#endif } } @@ -101,11 +116,19 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, message, new[] { arg1, arg2, arg3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, arg1, arg2, arg3); +#else + WriteToTargets(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2, arg3 }); +#endif } } @@ -118,7 +141,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsEnabled(level)) { @@ -134,11 +160,14 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -151,7 +180,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsEnabled(level)) { @@ -167,11 +199,14 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -184,7 +219,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsEnabled(level)) { @@ -200,11 +238,14 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -217,11 +258,14 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsEnabled(level)) { - WriteToTargets(level, formatProvider, message, new object[] { argument }); + WriteToTargets(level, formatProvider, message, new object?[] { argument }); } } @@ -233,11 +277,14 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object?[] { argument }); } } @@ -250,7 +297,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, string message, int argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, string message, int argument) { if (IsEnabled(level)) { @@ -266,11 +316,14 @@ public void Log(LogLevel level, IFormatProvider formatProvider, string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -283,7 +336,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsEnabled(level)) { @@ -299,11 +355,14 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -316,7 +375,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsEnabled(level)) { @@ -332,11 +394,14 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -349,7 +414,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsEnabled(level)) { @@ -365,11 +433,14 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -382,7 +453,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsEnabled(level)) { @@ -398,11 +472,14 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -415,11 +492,19 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(targetsForLevel, level, null, formatProvider, message, argument); +#else + WriteToTargets(targetsForLevel, level, null, formatProvider, message, new object?[] { argument }); +#endif } } @@ -431,11 +516,19 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, object? argument) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -449,7 +542,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsEnabled(level)) { @@ -466,11 +562,14 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -484,7 +583,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsEnabled(level)) { @@ -501,11 +603,14 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -519,7 +624,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsEnabled(level)) { @@ -536,15 +644,18 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsEnabled(level)) { - WriteToTargets(level, message, new object[] { argument }); + WriteToTargets(level, Factory.DefaultCultureInfo, message, new object[] { argument }); } } - #endregion +#endregion #region Trace() overloads @@ -553,7 +664,10 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Trace(object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(object? value) { if (IsTraceEnabled) { @@ -567,7 +681,10 @@ public void Trace(object value) /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Trace(IFormatProvider formatProvider, object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, object? value) { if (IsTraceEnabled) { @@ -583,11 +700,18 @@ public void Trace(IFormatProvider formatProvider, object value) /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new[] { arg1, arg2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Trace, null, Factory.DefaultCultureInfo, message, arg1, arg2); +#else + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2 }); +#endif } } @@ -600,11 +724,18 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new[] { arg1, arg2, arg3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Trace, null, Factory.DefaultCultureInfo, message, arg1, arg2, arg3); +#else + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2, arg3 }); +#endif } } @@ -616,7 +747,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsTraceEnabled) { @@ -631,11 +765,14 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Trace([Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -647,7 +784,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsTraceEnabled) { @@ -662,11 +802,14 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Trace([Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -678,7 +821,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsTraceEnabled) { @@ -693,11 +839,14 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Trace([Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -709,11 +858,14 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, formatProvider, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, formatProvider, message, new object?[] { argument }); } } @@ -724,11 +876,14 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace([Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace([Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object?[] { argument }); } } @@ -740,7 +895,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, string message, int argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, string message, int argument) { if (IsTraceEnabled) { @@ -755,11 +913,14 @@ public void Trace(IFormatProvider formatProvider, string message, int argument) /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Trace([Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -771,7 +932,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsTraceEnabled) { @@ -786,11 +950,14 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Trace([Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -802,7 +969,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsTraceEnabled) { @@ -817,11 +987,14 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Trace([Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -833,7 +1006,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsTraceEnabled) { @@ -848,11 +1024,14 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Trace([Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -864,7 +1043,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsTraceEnabled) { @@ -879,11 +1061,14 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Trace([Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -895,11 +1080,18 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Trace, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Trace, formatProvider, message, new object?[] { argument }); +#endif } } @@ -910,11 +1102,18 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace([Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace([Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Trace, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -927,7 +1126,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsTraceEnabled) { @@ -943,11 +1145,14 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Trace([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -960,7 +1165,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsTraceEnabled) { @@ -976,11 +1184,14 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Trace([Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -993,7 +1204,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsTraceEnabled) { @@ -1009,11 +1223,14 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Trace([Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1026,7 +1243,10 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Debug(object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(object? value) { if (IsDebugEnabled) { @@ -1040,7 +1260,10 @@ public void Debug(object value) /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Debug(IFormatProvider formatProvider, object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, object? value) { if (IsDebugEnabled) { @@ -1056,11 +1279,18 @@ public void Debug(IFormatProvider formatProvider, object value) /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new[] { arg1, arg2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Debug, null, Factory.DefaultCultureInfo, message, arg1, arg2); +#else + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2 }); +#endif } } @@ -1073,11 +1303,18 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new[] { arg1, arg2, arg3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Debug, null, Factory.DefaultCultureInfo, message, arg1, arg2, arg3); +#else + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2, arg3 }); +#endif } } @@ -1089,7 +1326,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsDebugEnabled) { @@ -1104,11 +1344,14 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Debug([Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1120,7 +1363,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsDebugEnabled) { @@ -1135,11 +1381,14 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Debug([Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1151,7 +1400,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsDebugEnabled) { @@ -1166,11 +1418,14 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Debug([Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1182,11 +1437,14 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, formatProvider, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, formatProvider, message, new object?[] { argument }); } } @@ -1197,11 +1455,14 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug([Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug([Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object?[] { argument }); } } @@ -1213,7 +1474,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsDebugEnabled) { @@ -1228,11 +1492,14 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Debug([Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1244,7 +1511,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsDebugEnabled) { @@ -1259,11 +1529,14 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Debug([Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1275,7 +1548,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsDebugEnabled) { @@ -1290,11 +1566,14 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Debug([Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1306,7 +1585,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsDebugEnabled) { @@ -1321,11 +1603,14 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Debug([Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1337,7 +1622,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsDebugEnabled) { @@ -1352,11 +1640,14 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Debug([Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1368,11 +1659,18 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Debug, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Debug, formatProvider, message, new object?[] { argument }); +#endif } } @@ -1383,11 +1681,18 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug([Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug([Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Debug, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -1400,7 +1705,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsDebugEnabled) { @@ -1416,11 +1724,14 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Debug([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1433,7 +1744,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsDebugEnabled) { @@ -1449,11 +1763,14 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Debug([Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1466,7 +1783,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsDebugEnabled) { @@ -1482,15 +1802,18 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Debug([Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object[] { argument }); } } - #endregion +#endregion #region Info() overloads @@ -1499,7 +1822,10 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Info(object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(object? value) { if (IsInfoEnabled) { @@ -1513,7 +1839,10 @@ public void Info(object value) /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Info(IFormatProvider formatProvider, object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, object? value) { if (IsInfoEnabled) { @@ -1529,11 +1858,18 @@ public void Info(IFormatProvider formatProvider, object value) /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new[] { arg1, arg2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Info, null, Factory.DefaultCultureInfo, message, arg1, arg2); +#else + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2 }); +#endif } } @@ -1546,11 +1882,18 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new[] { arg1, arg2, arg3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Info, null, Factory.DefaultCultureInfo, message, arg1, arg2, arg3); +#else + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2, arg3 }); +#endif } } @@ -1562,7 +1905,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsInfoEnabled) { @@ -1577,11 +1923,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Info([Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1593,7 +1942,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsInfoEnabled) { @@ -1608,11 +1960,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Info([Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1624,7 +1979,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsInfoEnabled) { @@ -1639,11 +1997,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Info([Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1655,11 +2016,14 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, formatProvider, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, formatProvider, message, new object?[] { argument }); } } @@ -1670,11 +2034,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info([Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info([Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object?[] { argument }); } } @@ -1686,7 +2053,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsInfoEnabled) { @@ -1701,11 +2071,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Info([Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1717,7 +2090,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsInfoEnabled) { @@ -1732,11 +2108,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Info([Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1748,7 +2127,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsInfoEnabled) { @@ -1763,11 +2145,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Info([Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1779,7 +2164,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsInfoEnabled) { @@ -1794,11 +2182,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Info([Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1810,7 +2201,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsInfoEnabled) { @@ -1825,11 +2219,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Info([Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1841,11 +2238,18 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Info, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Info, formatProvider, message, new object?[] { argument }); +#endif } } @@ -1856,11 +2260,18 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info([Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info([Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Info, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -1873,7 +2284,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsInfoEnabled) { @@ -1889,11 +2303,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Info([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1906,7 +2323,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsInfoEnabled) { @@ -1922,11 +2342,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Info([Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1939,7 +2362,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsInfoEnabled) { @@ -1955,11 +2381,14 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)][Structured [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Info([Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -1972,7 +2401,10 @@ public void Info([Localizable(false)][StructuredMessageTemplate] string message, /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Warn(object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(object? value) { if (IsWarnEnabled) { @@ -1986,7 +2418,10 @@ public void Warn(object value) /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Warn(IFormatProvider formatProvider, object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, object? value) { if (IsWarnEnabled) { @@ -2002,11 +2437,18 @@ public void Warn(IFormatProvider formatProvider, object value) /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new[] { arg1, arg2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Warn, null, Factory.DefaultCultureInfo, message, arg1, arg2); +#else + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2 }); +#endif } } @@ -2019,11 +2461,18 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new[] { arg1, arg2, arg3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Warn, null, Factory.DefaultCultureInfo, message, arg1, arg2, arg3); +#else + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2, arg3 }); +#endif } } @@ -2035,7 +2484,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsWarnEnabled) { @@ -2050,11 +2502,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Warn([Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2066,7 +2521,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsWarnEnabled) { @@ -2081,11 +2539,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Warn([Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2097,7 +2558,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsWarnEnabled) { @@ -2112,11 +2576,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Warn([Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2128,11 +2595,14 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, formatProvider, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, formatProvider, message, new object?[] { argument }); } } @@ -2143,11 +2613,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn([Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn([Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object?[] { argument }); } } @@ -2159,7 +2632,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsWarnEnabled) { @@ -2174,11 +2650,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Warn([Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2190,7 +2669,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsWarnEnabled) { @@ -2205,11 +2687,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Warn([Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2221,7 +2706,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsWarnEnabled) { @@ -2236,11 +2724,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Warn([Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2252,7 +2743,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsWarnEnabled) { @@ -2267,11 +2761,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Warn([Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2283,7 +2780,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsWarnEnabled) { @@ -2298,11 +2798,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Warn([Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2314,11 +2817,18 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Warn, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Warn, formatProvider, message, new object?[] { argument }); +#endif } } @@ -2329,11 +2839,18 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn([Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn([Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Warn, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -2346,7 +2863,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsWarnEnabled) { @@ -2362,11 +2882,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Warn([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2379,7 +2902,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsWarnEnabled) { @@ -2395,11 +2921,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Warn([Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2412,7 +2941,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsWarnEnabled) { @@ -2428,11 +2960,14 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)][Structured [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Warn([Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2445,7 +2980,10 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Error(object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(object? value) { if (IsErrorEnabled) { @@ -2459,7 +2997,10 @@ public void Error(object value) /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Error(IFormatProvider formatProvider, object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, object? value) { if (IsErrorEnabled) { @@ -2475,11 +3016,18 @@ public void Error(IFormatProvider formatProvider, object value) /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new[] { arg1, arg2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Error, null, Factory.DefaultCultureInfo, message, arg1, arg2); +#else + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2 }); +#endif } } @@ -2492,11 +3040,18 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new[] { arg1, arg2, arg3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Error, null, Factory.DefaultCultureInfo, message, arg1, arg2, arg3); +#else + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2, arg3 }); +#endif } } @@ -2508,7 +3063,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsErrorEnabled) { @@ -2523,11 +3081,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Error([Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2539,7 +3100,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsErrorEnabled) { @@ -2554,11 +3118,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Error([Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2570,7 +3137,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsErrorEnabled) { @@ -2585,11 +3155,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Error([Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2601,11 +3174,14 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, formatProvider, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, formatProvider, message, new object?[] { argument }); } } @@ -2616,11 +3192,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error([Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error([Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object?[] { argument }); } } @@ -2632,7 +3211,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsErrorEnabled) { @@ -2647,11 +3229,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Error([Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2663,7 +3248,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsErrorEnabled) { @@ -2678,11 +3266,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Error([Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2694,7 +3285,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsErrorEnabled) { @@ -2709,11 +3303,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Error([Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2725,7 +3322,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsErrorEnabled) { @@ -2740,11 +3340,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Error([Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2756,7 +3359,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsErrorEnabled) { @@ -2771,11 +3377,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Error([Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2787,11 +3396,18 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Error, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Error, formatProvider, message, new object?[] { argument }); +#endif } } @@ -2802,11 +3418,18 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error([Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error([Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Error, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -2819,7 +3442,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsErrorEnabled) { @@ -2835,11 +3461,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Error([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2852,7 +3481,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsErrorEnabled) { @@ -2868,11 +3500,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Error([Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2885,7 +3520,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsErrorEnabled) { @@ -2901,11 +3539,14 @@ public void Error(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Error([Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -2918,7 +3559,10 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Fatal(object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(object? value) { if (IsFatalEnabled) { @@ -2932,7 +3576,10 @@ public void Fatal(object value) /// An IFormatProvider that supplies culture-specific formatting information. /// A to be written. [EditorBrowsable(EditorBrowsableState.Never)] - public void Fatal(IFormatProvider formatProvider, object value) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, object? value) { if (IsFatalEnabled) { @@ -2948,11 +3595,18 @@ public void Fatal(IFormatProvider formatProvider, object value) /// Second argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new[] { arg1, arg2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Fatal, null, Factory.DefaultCultureInfo, message, arg1, arg2); +#else + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2 }); +#endif } } @@ -2965,11 +3619,18 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message /// Third argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object arg1, object arg2, object arg3) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object? arg1, object? arg2, object? arg3) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new[] { arg1, arg2, arg3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Fatal, null, Factory.DefaultCultureInfo, message, arg1, arg2, arg3); +#else + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object?[] { arg1, arg2, arg3 }); +#endif } } @@ -2981,7 +3642,10 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsFatalEnabled) { @@ -2996,11 +3660,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, bool argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -3012,7 +3679,10 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsFatalEnabled) { @@ -3027,11 +3697,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, char argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -3043,7 +3716,10 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsFatalEnabled) { @@ -3058,11 +3734,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, byte argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -3074,11 +3753,14 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, formatProvider, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, formatProvider, message, new object?[] { argument }); } } @@ -3089,11 +3771,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, string argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, string? argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object?[] { argument }); } } @@ -3105,7 +3790,10 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsFatalEnabled) { @@ -3120,11 +3808,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, int argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -3136,7 +3827,10 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsFatalEnabled) { @@ -3151,11 +3845,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, long argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -3167,7 +3864,10 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsFatalEnabled) { @@ -3182,11 +3882,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, float argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -3198,7 +3901,10 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsFatalEnabled) { @@ -3213,11 +3919,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, double argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -3229,7 +3938,10 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsFatalEnabled) { @@ -3244,11 +3956,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, decimal argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -3260,11 +3975,18 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Fatal, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Fatal, formatProvider, message, new object?[] { argument }); +#endif } } @@ -3275,11 +3997,18 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure /// The argument to format. [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, object? argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Fatal, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -3292,7 +4021,10 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsFatalEnabled) { @@ -3308,11 +4040,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, sbyte argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -3325,7 +4060,10 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsFatalEnabled) { @@ -3341,11 +4079,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, uint argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -3358,7 +4099,10 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsFatalEnabled) { @@ -3374,11 +4118,14 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false)][Structure [CLSCompliant(false)] [EditorBrowsable(EditorBrowsableState.Never)] [MessageTemplateFormatMethod("message")] +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, ulong argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object[] { argument }); } } @@ -3386,12 +4133,12 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] string message // end of generated code - void ILoggerBase.LogException(LogLevel level, [Localizable(false)] string message, Exception exception) + void ILoggerBase.LogException(LogLevel level, [Localizable(false)] string message, Exception? exception) { Log(level, exception, message, NLog.Internal.ArrayHelper.Empty()); } - void ILoggerBase.Log(LogLevel level, [Localizable(false)] string message, Exception exception) + void ILoggerBase.Log(LogLevel level, [Localizable(false)] string message, Exception? exception) { Log(level, exception, message, NLog.Internal.ArrayHelper.Empty()); } @@ -3399,12 +4146,12 @@ void ILoggerBase.Log(LogLevel level, [Localizable(false)] string message, Except /// [Obsolete("Use Trace(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] [EditorBrowsable(EditorBrowsableState.Never)] - public void TraceException([Localizable(false)] string message, Exception exception) + public void TraceException([Localizable(false)] string message, Exception? exception) { Trace(exception, message); } - void ILogger.Trace([Localizable(false)] string message, Exception exception) + void ILogger.Trace([Localizable(false)] string message, Exception? exception) { Trace(exception, message); } @@ -3412,12 +4159,12 @@ void ILogger.Trace([Localizable(false)] string message, Exception exception) /// [Obsolete("Use Debug(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] [EditorBrowsable(EditorBrowsableState.Never)] - public void DebugException([Localizable(false)] string message, Exception exception) + public void DebugException([Localizable(false)] string message, Exception? exception) { Debug(exception, message); } - void ILogger.Debug([Localizable(false)] string message, Exception exception) + void ILogger.Debug([Localizable(false)] string message, Exception? exception) { Debug(exception, message); } @@ -3425,12 +4172,12 @@ void ILogger.Debug([Localizable(false)] string message, Exception exception) /// [Obsolete("Use Info(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] [EditorBrowsable(EditorBrowsableState.Never)] - public void InfoException([Localizable(false)] string message, Exception exception) + public void InfoException([Localizable(false)] string message, Exception? exception) { Info(exception, message); } - void ILogger.Info([Localizable(false)] string message, Exception exception) + void ILogger.Info([Localizable(false)] string message, Exception? exception) { Info(exception, message); } @@ -3438,12 +4185,12 @@ void ILogger.Info([Localizable(false)] string message, Exception exception) /// [Obsolete("Use Warn(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] [EditorBrowsable(EditorBrowsableState.Never)] - public void WarnException([Localizable(false)] string message, Exception exception) + public void WarnException([Localizable(false)] string message, Exception? exception) { Warn(exception, message); } - void ILogger.Warn([Localizable(false)] string message, Exception exception) + void ILogger.Warn([Localizable(false)] string message, Exception? exception) { Warn(exception, message); } @@ -3451,12 +4198,12 @@ void ILogger.Warn([Localizable(false)] string message, Exception exception) /// [Obsolete("Use Error(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] [EditorBrowsable(EditorBrowsableState.Never)] - public void ErrorException([Localizable(false)] string message, Exception exception) + public void ErrorException([Localizable(false)] string message, Exception? exception) { Error(exception, message); } - void ILogger.Error([Localizable(false)] string message, Exception exception) + void ILogger.Error([Localizable(false)] string message, Exception? exception) { Error(exception, message); } @@ -3464,12 +4211,12 @@ void ILogger.Error([Localizable(false)] string message, Exception exception) /// [Obsolete("Use Fatal(Exception exception, string message) method instead. Marked obsolete with v4.3.11 (Only here because of LibLog)")] [EditorBrowsable(EditorBrowsableState.Never)] - public void FatalException([Localizable(false)] string message, Exception exception) + public void FatalException([Localizable(false)] string message, Exception? exception) { Fatal(exception, message); } - void ILogger.Fatal([Localizable(false)] string message, Exception exception) + void ILogger.Fatal([Localizable(false)] string message, Exception? exception) { Fatal(exception, message); } diff --git a/src/NLog/Logger-generated.cs b/src/NLog/Logger-generated.cs index 1e3e735623..cb88e62038 100644 --- a/src/NLog/Logger-generated.cs +++ b/src/NLog/Logger-generated.cs @@ -108,7 +108,7 @@ public bool IsFatalEnabled /// /// Type of the value. /// The value to be written. - public void Trace(T value) + public void Trace(T? value) { if (IsTraceEnabled) { @@ -122,7 +122,7 @@ public void Trace(T value) /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - public void Trace(IFormatProvider formatProvider, T value) + public void Trace(IFormatProvider? formatProvider, T? value) { if (IsTraceEnabled) { @@ -139,7 +139,6 @@ public void Trace(LogMessageGenerator messageFunc) if (IsTraceEnabled) { Guard.ThrowIfNull(messageFunc); - WriteToTargets(LogLevel.Trace, messageFunc()); } } @@ -151,7 +150,7 @@ public void Trace(LogMessageGenerator messageFunc) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsTraceEnabled) { @@ -177,11 +176,26 @@ public void Trace([Localizable(false)] string message) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Trace([Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Trace([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) + { + if (IsTraceEnabled) + { + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, args); + } + } + +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message at the Trace level using the specified parameters. + /// + /// A containing format items. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Trace([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, args); + WriteToTargetsWithSpan(LogLevel.Trace, null, Factory.DefaultCultureInfo, message, args); } } @@ -190,11 +204,27 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] string message /// /// A to be written. /// An exception to be logged. - public void Trace(Exception exception, [Localizable(false)] string message) + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Trace(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, exception, message, null); + WriteToTargetsWithSpan(LogLevel.Trace, exception, Factory.DefaultCultureInfo, message, args); + } + } +#endif + + /// + /// Writes the diagnostic message and exception at the Trace level. + /// + /// A to be written. + /// An exception to be logged. + public void Trace(Exception? exception, [Localizable(false)] string message) + { + if (IsTraceEnabled) + { + WriteToTargets(LogLevel.Trace, exception, Factory.DefaultCultureInfo, message, null); } } @@ -205,11 +235,11 @@ public void Trace(Exception exception, [Localizable(false)] string message) /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Trace(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Trace(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, exception, message, args); + WriteToTargets(LogLevel.Trace, exception, Factory.DefaultCultureInfo, message, args); } } @@ -221,7 +251,7 @@ public void Trace(Exception exception, [Localizable(false)][StructuredMessageTem /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Trace(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Trace(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsTraceEnabled) { @@ -237,11 +267,15 @@ public void Trace(Exception exception, IFormatProvider formatProvider, [Localiza /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Trace, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Trace, formatProvider, message, new object?[] { argument }); +#endif } } @@ -252,11 +286,15 @@ public void Trace(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Trace, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -270,11 +308,15 @@ public void Trace([Localizable(false)][StructuredMessageTemplate] str /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, formatProvider, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Trace, null, formatProvider, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Trace, formatProvider, message, new object?[] { argument1, argument2 }); +#endif } } @@ -287,11 +329,15 @@ public void Trace(IFormatProvider formatProvider, [Local /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Trace, null, Factory.DefaultCultureInfo, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2 }); +#endif } } @@ -307,11 +353,15 @@ public void Trace([Localizable(false)][StructuredMessage /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Trace(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Trace(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, formatProvider, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Trace, null, formatProvider, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Trace, formatProvider, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -326,11 +376,15 @@ public void Trace(IFormatProvider formatProv /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Trace([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsTraceEnabled) { - WriteToTargets(LogLevel.Trace, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Trace, null, Factory.DefaultCultureInfo, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Trace, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -346,7 +400,7 @@ public void Trace([Localizable(false)][Struc /// /// Type of the value. /// The value to be written. - public void Debug(T value) + public void Debug(T? value) { if (IsDebugEnabled) { @@ -360,7 +414,7 @@ public void Debug(T value) /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - public void Debug(IFormatProvider formatProvider, T value) + public void Debug(IFormatProvider? formatProvider, T? value) { if (IsDebugEnabled) { @@ -377,7 +431,6 @@ public void Debug(LogMessageGenerator messageFunc) if (IsDebugEnabled) { Guard.ThrowIfNull(messageFunc); - WriteToTargets(LogLevel.Debug, messageFunc()); } } @@ -389,7 +442,7 @@ public void Debug(LogMessageGenerator messageFunc) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsDebugEnabled) { @@ -415,11 +468,26 @@ public void Debug([Localizable(false)] string message) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Debug([Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Debug([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) + { + if (IsDebugEnabled) + { + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, args); + } + } + +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message at the Debug level using the specified parameters. + /// + /// A containing format items. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Debug([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, args); + WriteToTargetsWithSpan(LogLevel.Debug, null, Factory.DefaultCultureInfo, message, args); } } @@ -428,11 +496,27 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] string message /// /// A to be written. /// An exception to be logged. - public void Debug(Exception exception, [Localizable(false)] string message) + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Debug(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (IsDebugEnabled) + { + WriteToTargetsWithSpan(LogLevel.Debug, exception, Factory.DefaultCultureInfo, message, args); + } + } +#endif + + /// + /// Writes the diagnostic message and exception at the Debug level. + /// + /// A to be written. + /// An exception to be logged. + public void Debug(Exception? exception, [Localizable(false)] string message) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, exception, message, null); + WriteToTargets(LogLevel.Debug, exception, Factory.DefaultCultureInfo, message, null); } } @@ -443,11 +527,11 @@ public void Debug(Exception exception, [Localizable(false)] string message) /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Debug(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Debug(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, exception, message, args); + WriteToTargets(LogLevel.Debug, exception, Factory.DefaultCultureInfo, message, args); } } @@ -459,7 +543,7 @@ public void Debug(Exception exception, [Localizable(false)][StructuredMessageTem /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Debug(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Debug(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsDebugEnabled) { @@ -475,11 +559,15 @@ public void Debug(Exception exception, IFormatProvider formatProvider, [Localiza /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Debug, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Debug, formatProvider, message, new object?[] { argument }); +#endif } } @@ -490,11 +578,15 @@ public void Debug(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Debug, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -508,11 +600,15 @@ public void Debug([Localizable(false)][StructuredMessageTemplate] str /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, formatProvider, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Debug, null, formatProvider, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Debug, formatProvider, message, new object?[] { argument1, argument2 }); +#endif } } @@ -525,11 +621,15 @@ public void Debug(IFormatProvider formatProvider, [Local /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Debug, null, Factory.DefaultCultureInfo, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2 }); +#endif } } @@ -545,11 +645,15 @@ public void Debug([Localizable(false)][StructuredMessage /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Debug(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Debug(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, formatProvider, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Debug, null, formatProvider, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Debug, formatProvider, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -564,11 +668,15 @@ public void Debug(IFormatProvider formatProv /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Debug([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsDebugEnabled) { - WriteToTargets(LogLevel.Debug, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Debug, null, Factory.DefaultCultureInfo, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Debug, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -584,7 +692,7 @@ public void Debug([Localizable(false)][Struc /// /// Type of the value. /// The value to be written. - public void Info(T value) + public void Info(T? value) { if (IsInfoEnabled) { @@ -598,7 +706,7 @@ public void Info(T value) /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - public void Info(IFormatProvider formatProvider, T value) + public void Info(IFormatProvider? formatProvider, T? value) { if (IsInfoEnabled) { @@ -615,7 +723,6 @@ public void Info(LogMessageGenerator messageFunc) if (IsInfoEnabled) { Guard.ThrowIfNull(messageFunc); - WriteToTargets(LogLevel.Info, messageFunc()); } } @@ -627,7 +734,7 @@ public void Info(LogMessageGenerator messageFunc) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsInfoEnabled) { @@ -653,24 +760,55 @@ public void Info([Localizable(false)] string message) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Info([Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Info([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) + { + if (IsInfoEnabled) + { + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, args); + } + } + +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message at the Info level using the specified parameters. + /// + /// A containing format items. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Info([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (IsInfoEnabled) + { + WriteToTargetsWithSpan(LogLevel.Info, null, Factory.DefaultCultureInfo, message, args); + } + } + + /// + /// Writes the diagnostic message and exception at the Info level. + /// + /// A to be written. + /// An exception to be logged. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Info(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, args); + WriteToTargetsWithSpan(LogLevel.Info, exception, Factory.DefaultCultureInfo, message, args); } } +#endif /// /// Writes the diagnostic message and exception at the Info level. /// /// A to be written. /// An exception to be logged. - public void Info(Exception exception, [Localizable(false)] string message) + public void Info(Exception? exception, [Localizable(false)] string message) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, exception, message, null); + WriteToTargets(LogLevel.Info, exception, Factory.DefaultCultureInfo, message, null); } } @@ -681,11 +819,11 @@ public void Info(Exception exception, [Localizable(false)] string message) /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Info(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Info(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, exception, message, args); + WriteToTargets(LogLevel.Info, exception, Factory.DefaultCultureInfo, message, args); } } @@ -697,7 +835,7 @@ public void Info(Exception exception, [Localizable(false)][StructuredMessageTemp /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Info(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Info(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsInfoEnabled) { @@ -713,11 +851,15 @@ public void Info(Exception exception, IFormatProvider formatProvider, [Localizab /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Info, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Info, formatProvider, message, new object?[] { argument }); +#endif } } @@ -728,11 +870,15 @@ public void Info(IFormatProvider formatProvider, [Localizable(false)] /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Info([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Info([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Info, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -746,11 +892,15 @@ public void Info([Localizable(false)][StructuredMessageTemplate] stri /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, formatProvider, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Info, null, formatProvider, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Info, formatProvider, message, new object?[] { argument1, argument2 }); +#endif } } @@ -763,11 +913,15 @@ public void Info(IFormatProvider formatProvider, [Locali /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Info([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Info([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Info, null, Factory.DefaultCultureInfo, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2 }); +#endif } } @@ -783,11 +937,15 @@ public void Info([Localizable(false)][StructuredMessageT /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Info(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Info(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, formatProvider, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Info, null, formatProvider, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Info, formatProvider, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -802,11 +960,15 @@ public void Info(IFormatProvider formatProvi /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Info([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Info([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsInfoEnabled) { - WriteToTargets(LogLevel.Info, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Info, null, Factory.DefaultCultureInfo, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Info, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -822,7 +984,7 @@ public void Info([Localizable(false)][Struct /// /// Type of the value. /// The value to be written. - public void Warn(T value) + public void Warn(T? value) { if (IsWarnEnabled) { @@ -836,7 +998,7 @@ public void Warn(T value) /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - public void Warn(IFormatProvider formatProvider, T value) + public void Warn(IFormatProvider? formatProvider, T? value) { if (IsWarnEnabled) { @@ -853,7 +1015,6 @@ public void Warn(LogMessageGenerator messageFunc) if (IsWarnEnabled) { Guard.ThrowIfNull(messageFunc); - WriteToTargets(LogLevel.Warn, messageFunc()); } } @@ -865,7 +1026,7 @@ public void Warn(LogMessageGenerator messageFunc) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsWarnEnabled) { @@ -891,11 +1052,26 @@ public void Warn([Localizable(false)] string message) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Warn([Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Warn([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) + { + if (IsWarnEnabled) + { + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, args); + } + } + +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message at the Warn level using the specified parameters. + /// + /// A containing format items. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Warn([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, args); + WriteToTargetsWithSpan(LogLevel.Warn, null, Factory.DefaultCultureInfo, message, args); } } @@ -904,11 +1080,27 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] string message, /// /// A to be written. /// An exception to be logged. - public void Warn(Exception exception, [Localizable(false)] string message) + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Warn(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, exception, message, null); + WriteToTargetsWithSpan(LogLevel.Warn, exception, Factory.DefaultCultureInfo, message, args); + } + } +#endif + + /// + /// Writes the diagnostic message and exception at the Warn level. + /// + /// A to be written. + /// An exception to be logged. + public void Warn(Exception? exception, [Localizable(false)] string message) + { + if (IsWarnEnabled) + { + WriteToTargets(LogLevel.Warn, exception, Factory.DefaultCultureInfo, message, null); } } @@ -919,11 +1111,11 @@ public void Warn(Exception exception, [Localizable(false)] string message) /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Warn(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Warn(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, exception, message, args); + WriteToTargets(LogLevel.Warn, exception, Factory.DefaultCultureInfo, message, args); } } @@ -935,7 +1127,7 @@ public void Warn(Exception exception, [Localizable(false)][StructuredMessageTemp /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Warn(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Warn(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsWarnEnabled) { @@ -951,11 +1143,15 @@ public void Warn(Exception exception, IFormatProvider formatProvider, [Localizab /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Warn, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Warn, formatProvider, message, new object?[] { argument }); +#endif } } @@ -966,11 +1162,15 @@ public void Warn(IFormatProvider formatProvider, [Localizable(false)] /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Warn, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -984,11 +1184,15 @@ public void Warn([Localizable(false)][StructuredMessageTemplate] stri /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, formatProvider, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Warn, null, formatProvider, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Warn, formatProvider, message, new object?[] { argument1, argument2 }); +#endif } } @@ -1001,11 +1205,15 @@ public void Warn(IFormatProvider formatProvider, [Locali /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Warn, null, Factory.DefaultCultureInfo, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2 }); +#endif } } @@ -1021,11 +1229,15 @@ public void Warn([Localizable(false)][StructuredMessageT /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Warn(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Warn(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, formatProvider, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Warn, null, formatProvider, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Warn, formatProvider, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -1040,11 +1252,15 @@ public void Warn(IFormatProvider formatProvi /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Warn([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsWarnEnabled) { - WriteToTargets(LogLevel.Warn, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Warn, null, Factory.DefaultCultureInfo, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Warn, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -1060,7 +1276,7 @@ public void Warn([Localizable(false)][Struct /// /// Type of the value. /// The value to be written. - public void Error(T value) + public void Error(T? value) { if (IsErrorEnabled) { @@ -1074,7 +1290,7 @@ public void Error(T value) /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - public void Error(IFormatProvider formatProvider, T value) + public void Error(IFormatProvider? formatProvider, T? value) { if (IsErrorEnabled) { @@ -1091,7 +1307,6 @@ public void Error(LogMessageGenerator messageFunc) if (IsErrorEnabled) { Guard.ThrowIfNull(messageFunc); - WriteToTargets(LogLevel.Error, messageFunc()); } } @@ -1103,7 +1318,7 @@ public void Error(LogMessageGenerator messageFunc) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsErrorEnabled) { @@ -1129,11 +1344,26 @@ public void Error([Localizable(false)] string message) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Error([Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Error([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) + { + if (IsErrorEnabled) + { + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, args); + } + } + +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message at the Error level using the specified parameters. + /// + /// A containing format items. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Error([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, args); + WriteToTargetsWithSpan(LogLevel.Error, null, Factory.DefaultCultureInfo, message, args); } } @@ -1142,11 +1372,27 @@ public void Error([Localizable(false)][StructuredMessageTemplate] string message /// /// A to be written. /// An exception to be logged. - public void Error(Exception exception, [Localizable(false)] string message) + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Error(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (IsErrorEnabled) + { + WriteToTargetsWithSpan(LogLevel.Error, exception, Factory.DefaultCultureInfo, message, args); + } + } +#endif + + /// + /// Writes the diagnostic message and exception at the Error level. + /// + /// A to be written. + /// An exception to be logged. + public void Error(Exception? exception, [Localizable(false)] string message) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, exception, message, null); + WriteToTargets(LogLevel.Error, exception, Factory.DefaultCultureInfo, message, null); } } @@ -1157,11 +1403,11 @@ public void Error(Exception exception, [Localizable(false)] string message) /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Error(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Error(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, exception, message, args); + WriteToTargets(LogLevel.Error, exception, Factory.DefaultCultureInfo, message, args); } } @@ -1173,7 +1419,7 @@ public void Error(Exception exception, [Localizable(false)][StructuredMessageTem /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Error(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Error(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsErrorEnabled) { @@ -1189,11 +1435,15 @@ public void Error(Exception exception, IFormatProvider formatProvider, [Localiza /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Error, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Error, formatProvider, message, new object?[] { argument }); +#endif } } @@ -1204,11 +1454,15 @@ public void Error(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Error, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -1222,11 +1476,15 @@ public void Error([Localizable(false)][StructuredMessageTemplate] str /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, formatProvider, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Error, null, formatProvider, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Error, formatProvider, message, new object?[] { argument1, argument2 }); +#endif } } @@ -1239,11 +1497,15 @@ public void Error(IFormatProvider formatProvider, [Local /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Error, null, Factory.DefaultCultureInfo, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2 }); +#endif } } @@ -1259,11 +1521,15 @@ public void Error([Localizable(false)][StructuredMessage /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Error(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Error(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, formatProvider, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Error, null, formatProvider, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Error, formatProvider, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -1278,11 +1544,15 @@ public void Error(IFormatProvider formatProv /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Error([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsErrorEnabled) { - WriteToTargets(LogLevel.Error, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Error, null, Factory.DefaultCultureInfo, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Error, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -1298,7 +1568,7 @@ public void Error([Localizable(false)][Struc /// /// Type of the value. /// The value to be written. - public void Fatal(T value) + public void Fatal(T? value) { if (IsFatalEnabled) { @@ -1312,7 +1582,7 @@ public void Fatal(T value) /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - public void Fatal(IFormatProvider formatProvider, T value) + public void Fatal(IFormatProvider? formatProvider, T? value) { if (IsFatalEnabled) { @@ -1329,7 +1599,6 @@ public void Fatal(LogMessageGenerator messageFunc) if (IsFatalEnabled) { Guard.ThrowIfNull(messageFunc); - WriteToTargets(LogLevel.Fatal, messageFunc()); } } @@ -1341,7 +1610,7 @@ public void Fatal(LogMessageGenerator messageFunc) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsFatalEnabled) { @@ -1367,24 +1636,55 @@ public void Fatal([Localizable(false)] string message) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) + { + if (IsFatalEnabled) + { + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, args); + } + } + +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message at the Fatal level using the specified parameters. + /// + /// A containing format items. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (IsFatalEnabled) + { + WriteToTargetsWithSpan(LogLevel.Fatal, null, Factory.DefaultCultureInfo, message, args); + } + } + + /// + /// Writes the diagnostic message and exception at the Fatal level. + /// + /// A to be written. + /// An exception to be logged. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Fatal(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, args); + WriteToTargetsWithSpan(LogLevel.Fatal, exception, Factory.DefaultCultureInfo, message, args); } } +#endif /// /// Writes the diagnostic message and exception at the Fatal level. /// /// A to be written. /// An exception to be logged. - public void Fatal(Exception exception, [Localizable(false)] string message) + public void Fatal(Exception? exception, [Localizable(false)] string message) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, exception, message, null); + WriteToTargets(LogLevel.Fatal, exception, Factory.DefaultCultureInfo, message, null); } } @@ -1395,11 +1695,11 @@ public void Fatal(Exception exception, [Localizable(false)] string message) /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Fatal(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Fatal(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, exception, message, args); + WriteToTargets(LogLevel.Fatal, exception, Factory.DefaultCultureInfo, message, args); } } @@ -1411,7 +1711,7 @@ public void Fatal(Exception exception, [Localizable(false)][StructuredMessageTem /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Fatal(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Fatal(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (IsFatalEnabled) { @@ -1427,11 +1727,15 @@ public void Fatal(Exception exception, IFormatProvider formatProvider, [Localiza /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Fatal, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.Fatal, formatProvider, message, new object?[] { argument }); +#endif } } @@ -1442,11 +1746,15 @@ public void Fatal(IFormatProvider formatProvider, [Localizable(false) /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Fatal, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -1460,11 +1768,15 @@ public void Fatal([Localizable(false)][StructuredMessageTemplate] str /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, formatProvider, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Fatal, null, formatProvider, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Fatal, formatProvider, message, new object?[] { argument1, argument2 }); +#endif } } @@ -1477,11 +1789,15 @@ public void Fatal(IFormatProvider formatProvider, [Local /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Fatal, null, Factory.DefaultCultureInfo, message, argument1, argument2); +#else + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2 }); +#endif } } @@ -1497,11 +1813,15 @@ public void Fatal([Localizable(false)][StructuredMessage /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Fatal(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Fatal(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, formatProvider, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Fatal, null, formatProvider, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Fatal, formatProvider, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -1516,14 +1836,18 @@ public void Fatal(IFormatProvider formatProv /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Fatal([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (IsFatalEnabled) { - WriteToTargets(LogLevel.Fatal, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.Fatal, null, Factory.DefaultCultureInfo, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.Fatal, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2, argument3 }); +#endif } } #endregion } -} +} \ No newline at end of file diff --git a/src/NLog/Logger-generated.tt b/src/NLog/Logger-generated.tt index 2744e1ec59..3734e77d57 100644 --- a/src/NLog/Logger-generated.tt +++ b/src/NLog/Logger-generated.tt @@ -45,6 +45,7 @@ namespace NLog using System; using System.ComponentModel; using JetBrains.Annotations; + using NLog.Internal; /// /// Provides logging interface and utility functions. @@ -83,7 +84,7 @@ namespace NLog /// /// Type of the value. /// The value to be written. - public void <#=level#>(T value) + public void <#=level#>(T? value) { if (Is<#=level#>Enabled) { @@ -97,7 +98,7 @@ namespace NLog /// Type of the value. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - public void <#=level#>(IFormatProvider formatProvider, T value) + public void <#=level#>(IFormatProvider? formatProvider, T? value) { if (Is<#=level#>Enabled) { @@ -113,11 +114,7 @@ namespace NLog { if (Is<#=level#>Enabled) { - if (messageFunc is null) - { - throw new ArgumentNullException(nameof(messageFunc)); - } - + Guard.ThrowIfNull(messageFunc); WriteToTargets(LogLevel.<#=level#>, messageFunc()); } } @@ -129,7 +126,7 @@ namespace NLog /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void <#=level#>(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void <#=level#>(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (Is<#=level#>Enabled) { @@ -155,24 +152,55 @@ namespace NLog /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) + { + if (Is<#=level#>Enabled) + { + WriteToTargets(LogLevel.<#=level#>, Factory.DefaultCultureInfo, message, args); + } + } + +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message at the <#=level#> level using the specified parameters. + /// + /// A containing format items. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + if (Is<#=level#>Enabled) + { + WriteToTargetsWithSpan(LogLevel.<#=level#>, null, Factory.DefaultCultureInfo, message, args); + } + } + + /// + /// Writes the diagnostic message and exception at the <#=level#> level. + /// + /// A to be written. + /// An exception to be logged. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void <#=level#>(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) { if (Is<#=level#>Enabled) { - WriteToTargets(LogLevel.<#=level#>, message, args); + WriteToTargetsWithSpan(LogLevel.<#=level#>, exception, Factory.DefaultCultureInfo, message, args); } } +#endif /// /// Writes the diagnostic message and exception at the <#=level#> level. /// /// A to be written. /// An exception to be logged. - public void <#=level#>(Exception exception, [Localizable(false)] string message) + public void <#=level#>(Exception? exception, [Localizable(false)] string message) { if (Is<#=level#>Enabled) { - WriteToTargets(LogLevel.<#=level#>, exception, message, null); + WriteToTargets(LogLevel.<#=level#>, exception, Factory.DefaultCultureInfo, message, null); } } @@ -183,11 +211,11 @@ namespace NLog /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void <#=level#>(Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void <#=level#>(Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (Is<#=level#>Enabled) { - WriteToTargets(LogLevel.<#=level#>, exception, message, args); + WriteToTargets(LogLevel.<#=level#>, exception, Factory.DefaultCultureInfo, message, args); } } @@ -199,7 +227,7 @@ namespace NLog /// An exception to be logged. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void <#=level#>(Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void <#=level#>(Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { if (Is<#=level#>Enabled) { @@ -215,11 +243,15 @@ namespace NLog /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void <#=level#>(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void <#=level#>(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (Is<#=level#>Enabled) { - WriteToTargets(LogLevel.<#=level#>, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.<#=level#>, null, formatProvider, message, argument); +#else + WriteToTargets(LogLevel.<#=level#>, formatProvider, message, new object?[] { argument }); +#endif } } @@ -230,11 +262,15 @@ namespace NLog /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { if (Is<#=level#>Enabled) { - WriteToTargets(LogLevel.<#=level#>, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.<#=level#>, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(LogLevel.<#=level#>, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -248,11 +284,15 @@ namespace NLog /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void <#=level#>(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void <#=level#>(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (Is<#=level#>Enabled) { - WriteToTargets(LogLevel.<#=level#>, formatProvider, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.<#=level#>, null, formatProvider, message, argument1, argument2); +#else + WriteToTargets(LogLevel.<#=level#>, formatProvider, message, new object?[] { argument1, argument2 }); +#endif } } @@ -265,11 +305,15 @@ namespace NLog /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { if (Is<#=level#>Enabled) { - WriteToTargets(LogLevel.<#=level#>, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.<#=level#>, null, Factory.DefaultCultureInfo, message, argument1, argument2); +#else + WriteToTargets(LogLevel.<#=level#>, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2 }); +#endif } } @@ -285,11 +329,15 @@ namespace NLog /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void <#=level#>(IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void <#=level#>(IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (Is<#=level#>Enabled) { - WriteToTargets(LogLevel.<#=level#>, formatProvider, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.<#=level#>, null, formatProvider, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.<#=level#>, formatProvider, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -304,11 +352,15 @@ namespace NLog /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { if (Is<#=level#>Enabled) { - WriteToTargets(LogLevel.<#=level#>, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(LogLevel.<#=level#>, null, Factory.DefaultCultureInfo, message, argument1, argument2, argument3); +#else + WriteToTargets(LogLevel.<#=level#>, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2, argument3 }); +#endif } } diff --git a/src/NLog/Logger.cs b/src/NLog/Logger.cs index 7c2c1352c1..1dd913062a 100644 --- a/src/NLog/Logger.cs +++ b/src/NLog/Logger.cs @@ -51,7 +51,7 @@ public partial class Logger : ILogger internal static readonly Type DefaultLoggerType = typeof(Logger); private ITargetWithFilterChain[] _targetsByLevel = TargetWithFilterChain.NoTargetsByLevel; private Logger _contextLogger; - private ThreadSafeDictionary _contextProperties; + private ThreadSafeDictionary? _contextProperties; private volatile bool _isTraceEnabled; private volatile bool _isDebugEnabled; private volatile bool _isInfoEnabled; @@ -65,12 +65,14 @@ public partial class Logger : ILogger protected internal Logger() { _contextLogger = this; + Name = string.Empty; + Factory = LogManager.LogFactory; } /// /// Occurs when logger configuration changes. /// - public event EventHandler LoggerReconfigured; + public event EventHandler? LoggerReconfigured; /// /// Gets the name of the logger. @@ -89,7 +91,7 @@ protected internal Logger() /// It is recommended to use for modifying context properties /// when same named logger is used at multiple locations or shared by different thread contexts. /// - public IDictionary Properties => _contextProperties ?? System.Threading.Interlocked.CompareExchange(ref _contextProperties, CreateContextPropertiesDictionary(null), null) ?? _contextProperties; + public IDictionary Properties => _contextProperties ?? System.Threading.Interlocked.CompareExchange(ref _contextProperties, CreateContextPropertiesDictionary(null), null) ?? _contextProperties; /// /// Gets a value indicating whether logging is enabled for the specified level. @@ -109,13 +111,13 @@ public bool IsEnabled(LogLevel level) /// Property Name /// Property Value /// New Logger object that automatically appends specified property - public Logger WithProperty(string propertyKey, object propertyValue) + public Logger WithProperty(string propertyKey, object? propertyValue) { if (string.IsNullOrEmpty(propertyKey)) throw new ArgumentException(nameof(propertyKey)); Logger newLogger = CreateChildLogger(); - newLogger._contextProperties[propertyKey] = propertyValue; + newLogger.Properties[propertyKey] = propertyValue; return newLogger; } @@ -126,18 +128,39 @@ public Logger WithProperty(string propertyKey, object propertyValue) /// /// Collection of key-value pair properties /// New Logger object that automatically appends specified properties - public Logger WithProperties(IEnumerable> properties) + public Logger WithProperties(IEnumerable> properties) { Guard.ThrowIfNull(properties); Logger newLogger = CreateChildLogger(); - foreach (KeyValuePair property in properties) + var newProperties = newLogger.Properties; + foreach (var property in properties) { - newLogger._contextProperties[property.Key] = property.Value; + newProperties[property.Key] = property.Value; } return newLogger; } +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Creates new logger that automatically appends the specified properties to all log events (without changing current logger) + /// + /// With property, all properties can be enumerated. + /// + /// Collection of key-value pair properties + /// New Logger object that automatically appends specified properties + public Logger WithProperties(params ReadOnlySpan<(string, object?)> properties) + { + Logger newLogger = CreateChildLogger(); + var newProperties = newLogger.Properties; + foreach (var property in properties) + { + newProperties[property.Item1] = property.Item2; + } + return newLogger; + } +#endif + /// /// Obsolete and replaced by that prevents unexpected side-effects in Logger-state. /// @@ -154,7 +177,7 @@ public Logger WithProperties(IEnumerable> propertie /// Property Value [Obsolete("Instead use WithProperty which is safe. If really necessary then one can use Properties-property. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] - public void SetProperty(string propertyKey, object propertyValue) + public void SetProperty(string propertyKey, object? propertyValue) { if (string.IsNullOrEmpty(propertyKey)) throw new ArgumentException(nameof(propertyKey)); @@ -162,11 +185,11 @@ public void SetProperty(string propertyKey, object propertyValue) Properties[propertyKey] = propertyValue; } - private static ThreadSafeDictionary CreateContextPropertiesDictionary(ThreadSafeDictionary contextProperties) + private static ThreadSafeDictionary CreateContextPropertiesDictionary(ThreadSafeDictionary? contextProperties) { - contextProperties = contextProperties != null - ? new ThreadSafeDictionary(contextProperties) - : new ThreadSafeDictionary(); + contextProperties = contextProperties is null + ? new ThreadSafeDictionary() + : new ThreadSafeDictionary(contextProperties); return contextProperties; } @@ -177,7 +200,7 @@ private static ThreadSafeDictionary CreateContextPropertiesDicti /// Value of property /// A disposable object that removes the properties from logical context scope on dispose. /// property-dictionary-keys are case-insensitive - public IDisposable PushScopeProperty(string propertyName, object propertyValue) + public IDisposable PushScopeProperty(string propertyName, object? propertyValue) { return ScopeContext.PushProperty(propertyName, propertyValue); } @@ -189,7 +212,7 @@ public IDisposable PushScopeProperty(string propertyName, object propertyValue) /// Value of property /// A disposable object that removes the properties from logical context scope on dispose. /// property-dictionary-keys are case-insensitive - public IDisposable PushScopeProperty(string propertyName, TValue propertyValue) + public IDisposable PushScopeProperty(string propertyName, TValue? propertyValue) { return ScopeContext.PushProperty(propertyName, propertyValue); } @@ -201,7 +224,7 @@ public IDisposable PushScopeProperty(string propertyName, TValue propert /// Properties being added to the scope dictionary /// A disposable object that removes the properties from logical context scope on dispose. /// property-dictionary-keys are case-insensitive - public IDisposable PushScopeProperties(IReadOnlyCollection> scopeProperties) + public IDisposable PushScopeProperties(IReadOnlyCollection> scopeProperties) { return ScopeContext.PushProperties(scopeProperties); } @@ -212,7 +235,7 @@ public IDisposable PushScopeProperties(IReadOnlyCollectionProperties being added to the scope dictionary /// A disposable object that removes the properties from logical context scope on dispose. /// property-dictionary-keys are case-insensitive - public IDisposable PushScopeProperties(IReadOnlyCollection> scopeProperties) + public IDisposable PushScopeProperties(IReadOnlyCollection> scopeProperties) { return ScopeContext.PushProperties(scopeProperties); } @@ -247,11 +270,11 @@ public void Log(LogEventInfo logEvent) var targetsForLevel = GetTargetsForLevelSafe(logEvent.Level); if (targetsForLevel != null) { - if (logEvent.LoggerName is null) + if (logEvent.LoggerName is null || ReferenceEquals(logEvent.LoggerName, string.Empty)) logEvent.LoggerName = Name; if (logEvent.FormatProvider is null) logEvent.FormatProvider = Factory.DefaultCultureInfo; - WriteToTargets(logEvent, targetsForLevel); + WriteLogEventToTargets(logEvent, targetsForLevel); } } @@ -265,11 +288,11 @@ public void Log(Type wrapperType, LogEventInfo logEvent) var targetsForLevel = GetTargetsForLevelSafe(logEvent.Level); if (targetsForLevel != null) { - if (logEvent.LoggerName is null) + if (logEvent.LoggerName is null || ReferenceEquals(logEvent.LoggerName, string.Empty)) logEvent.LoggerName = Name; if (logEvent.FormatProvider is null) logEvent.FormatProvider = Factory.DefaultCultureInfo; - WriteToTargets(wrapperType, logEvent, targetsForLevel); + WriteLogEventToTargets(wrapperType, logEvent, targetsForLevel); } } @@ -284,7 +307,7 @@ public void Log(Type wrapperType, LogEventInfo logEvent) /// Type of the value. /// The log level. /// The value to be written. - public void Log(LogLevel level, T value) + public void Log(LogLevel level, T? value) { if (IsEnabled(level)) { @@ -299,7 +322,7 @@ public void Log(LogLevel level, T value) /// The log level. /// An IFormatProvider that supplies culture-specific formatting information. /// The value to be written. - public void Log(LogLevel level, IFormatProvider formatProvider, T value) + public void Log(LogLevel level, IFormatProvider? formatProvider, T? value) { if (IsEnabled(level)) { @@ -314,11 +337,11 @@ public void Log(LogLevel level, IFormatProvider formatProvider, T value) /// A function returning message to be written. Function is not evaluated if logging is not enabled. public void Log(LogLevel level, LogMessageGenerator messageFunc) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { Guard.ThrowIfNull(messageFunc); - - WriteToTargets(level, messageFunc()); + WriteToTargets(targetsForLevel, level, null, Factory.DefaultCultureInfo, messageFunc(), null); } } @@ -330,11 +353,12 @@ public void Log(LogLevel level, LogMessageGenerator messageFunc) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, formatProvider, message, args); + WriteToTargets(targetsForLevel, level, null, formatProvider, message, args); } } @@ -345,9 +369,10 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(fal /// Log message. public void Log(LogLevel level, [Localizable(false)] string message) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, message); + WriteToTargets(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, null); } } @@ -358,11 +383,12 @@ public void Log(LogLevel level, [Localizable(false)] string message) /// A containing format items. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, message, args); + WriteToTargets(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, args); } } @@ -374,11 +400,12 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] /// A to be written. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, Exception exception, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Log(LogLevel level, Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, exception, message, args); + WriteToTargets(targetsForLevel, level, exception, Factory.DefaultCultureInfo, message, args); } } @@ -391,11 +418,12 @@ public void Log(LogLevel level, Exception exception, [Localizable(false)][Struct /// A to be written. /// Arguments to format. [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, Exception exception, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object[] args) + public void Log(LogLevel level, Exception? exception, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, params object?[] args) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, exception, formatProvider, message, args); + WriteToTargets(targetsForLevel, level, exception, formatProvider, message, args); } } @@ -408,11 +436,16 @@ public void Log(LogLevel level, Exception exception, IFormatProvider formatProvi /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, formatProvider, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(targetsForLevel, level, null, formatProvider, message, argument); +#else + WriteToTargets(targetsForLevel, level, null, formatProvider, message, new object?[] { argument }); +#endif } } @@ -424,11 +457,16 @@ public void Log(LogLevel level, IFormatProvider formatProvider, [Loca /// A containing one format item. /// The argument to format. [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument argument) + public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument? argument) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, message, new object[] { argument }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, argument); +#else + WriteToTargets(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, new object?[] { argument }); +#endif } } @@ -443,11 +481,16 @@ public void Log(LogLevel level, [Localizable(false)][StructuredMessag /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, formatProvider, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(targetsForLevel, level, null, formatProvider, message, argument1, argument2); +#else + WriteToTargets(targetsForLevel, level, null, formatProvider, message, new object?[] { argument1, argument2 }); +#endif } } @@ -461,11 +504,16 @@ public void Log(LogLevel level, IFormatProvider formatPr /// The first argument to format. /// The second argument to format. [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2) + public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, message, new object[] { argument1, argument2 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, argument1, argument2); +#else + WriteToTargets(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2 }); +#endif } } @@ -482,11 +530,16 @@ public void Log(LogLevel level, [Localizable(false)][Str /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, IFormatProvider formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Log(LogLevel level, IFormatProvider? formatProvider, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, formatProvider, message, new object[] { argument1, argument2, argument3 }); +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(targetsForLevel, level, null, formatProvider, message, argument1, argument2, argument3); +#else + WriteToTargets(targetsForLevel, level, null, formatProvider, message, new object?[] { argument1, argument2, argument3 }); +#endif } } @@ -502,14 +555,117 @@ public void Log(LogLevel level, IFormatProvi /// The second argument to format. /// The third argument to format. [MessageTemplateFormatMethod("message")] - public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1 argument1, TArgument2 argument2, TArgument3 argument3) + public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, TArgument1? argument1, TArgument2? argument2, TArgument3? argument3) { - if (IsEnabled(level)) + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) + { +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + WriteToTargetsWithSpan(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, argument1, argument2, argument3); +#else + WriteToTargets(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, new object?[] { argument1, argument2, argument3 }); +#endif + } + } + +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + /// + /// Writes the diagnostic message at the specified level using the specified parameters. + /// + /// The log level. + /// A containing format items. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) { - WriteToTargets(level, message, new object[] { argument1, argument2, argument3 }); + WriteToTargetsWithSpan(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, args); } } + /// + /// Writes the diagnostic message and exception at the specified level. + /// + /// The log level. + /// An exception to be logged. + /// A to be written. + /// Arguments to format. + [MessageTemplateFormatMethod("message")] + public void Log(LogLevel level, Exception? exception, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan args) + { + var targetsForLevel = GetTargetsForLevelSafe(level); + if (targetsForLevel != null) + { + WriteToTargetsWithSpan(targetsForLevel, level, exception, Factory.DefaultCultureInfo, message, args); + } + } + + private void WriteToTargetsWithSpan(LogLevel level, Exception? exception, IFormatProvider? formatProvider, string message, params ReadOnlySpan args) + { + var targetsForLevel = GetTargetsForLevel(level); + if (targetsForLevel != null) + { + WriteToTargetsWithSpan(targetsForLevel, level, exception, formatProvider, message, args); + } + } + + private void WriteToTargetsWithSpan(ITargetWithFilterChain targetsForLevel, LogLevel level, Exception? exception, IFormatProvider? formatProvider, string message, params ReadOnlySpan args) + { + if (Factory.AutoMessageTemplateFormatter is null || !LogEventInfo.NeedToPreformatMessage(args)) + { + // Deferred message formatting and capturing of Properties + var logEvent = LogEventInfo.Create(level, Name, exception, formatProvider, message, args.IsEmpty ? null : args.ToArray()); + WriteLogEventToTargets(logEvent, targetsForLevel); + } + else + { + // Pre-format upfront and skip parameter-object[]-array-allocation when possible + var templateEnumerator = new MessageTemplates.TemplateEnumerator(message); + if (templateEnumerator.MoveNext() && !templateEnumerator.Current.MaybePositionalTemplate) + { + // Convert parameters into Properties and skip Parameters-array-allocation (Like with Microsoft Extension Logging) + LogEventInfo logEvent = RenderPreformattedLogEvent(Factory.AutoMessageTemplateFormatter, level, exception, formatProvider, message, args, ref templateEnumerator); + WriteLogEventToTargets(logEvent, targetsForLevel); + } + else + { + // Pre-format using string.Format, and provide Parameters-array for Targets that fallback to Parameters-array when no Properties + var logEvent = LogEventInfo.Create(level, Name, exception, formatProvider, message, args.ToArray()); + logEvent.MessageFormatter = LogMessageStringFormatter.Default.MessageFormatter; // Force string.Format since confirmed + WriteLogEventToTargets(logEvent, targetsForLevel); + } + } + } + + private LogEventInfo RenderPreformattedLogEvent(LogMessageTemplateFormatter messageTemplateFormatter, LogLevel level, Exception? exception, IFormatProvider? formatProvider, string messageTemplate, in ReadOnlySpan args, ref MessageTemplates.TemplateEnumerator templateEnumerator) + { + string formattedMessage = messageTemplate; + IList? messageTemplateParameters = null; + + try + { + formattedMessage = messageTemplateFormatter.Render(ref templateEnumerator, formatProvider, in args, out messageTemplateParameters); + } + catch (Exception ex) + { + Common.InternalLogger.Warn(ex, "Error when formatting a message."); + if (ex.MustBeRethrown()) + throw; + } + + var logEvent = new LogEventInfo(level, Name, formattedMessage, messageTemplate, (IList?)null); + if (messageTemplateParameters is null) + logEvent.Parameters = args.ToArray(); // Failed to parse message-template, so lets captures message-format-parameters + else + logEvent.CreatePropertiesInternal(messageTemplateParameters); + logEvent.Exception = exception; + return logEvent; + } +#endif + #endregion + private LogEventInfo PrepareLogEventInfo(LogEventInfo logEvent) { if (_contextProperties != null) @@ -525,9 +681,6 @@ private LogEventInfo PrepareLogEventInfo(LogEventInfo logEvent) return logEvent; } - #endregion - - /// /// Runs the provided action. If the action throws, the exception is logged at Error level. The exception is not propagated outside of this method. /// @@ -551,7 +704,7 @@ public void Swallow(Action action) /// Return type of the provided function. /// Function to run. /// Result returned by the provided function or the default value of type in case of exception. - public T Swallow(Func func) + public T? Swallow(Func func) { return Swallow(func, default(T)); } @@ -564,7 +717,7 @@ public T Swallow(Func func) /// Function to run. /// Fallback value to return in case of exception. /// Result returned by the provided function or fallback value in case of exception. - public T Swallow(Func func, T fallback) + public T? Swallow(Func func, T? fallback) { try { @@ -635,7 +788,7 @@ public async Task SwallowAsync(Func asyncAction) /// Return type of the provided function. /// Async function to run. /// A task that represents the completion of the supplied task. If the supplied task ends in the state, the result of the new task will be the result of the supplied task; otherwise, the result of the new task will be the default value of type . - public async Task SwallowAsync(Func> asyncFunc) + public async Task SwallowAsync(Func> asyncFunc) { return await SwallowAsync(asyncFunc, default(TResult)).ConfigureAwait(false); } @@ -648,7 +801,7 @@ public async Task SwallowAsync(Func> asyncFunc) /// Async function to run. /// Fallback value to return if the task does not end in the state. /// A task that represents the completion of the supplied task. If the supplied task ends in the state, the result of the new task will be the result of the supplied task; otherwise, the result of the new task will be the fallback value. - public async Task SwallowAsync(Func> asyncFunc, TResult fallback) + public async Task SwallowAsync(Func> asyncFunc, TResult? fallback) { try { @@ -669,18 +822,19 @@ internal void Initialize(string name, ITargetWithFilterChain[] targetsByLevel, L SetConfiguration(targetsByLevel); } - private void WriteToTargets(LogLevel level, string message, object[] args) + private void WriteToTargets(ITargetWithFilterChain targetsForLevel, LogLevel level, Exception? exception, IFormatProvider? formatProvider, string message, object?[]? args) { - WriteToTargets(level, Factory.DefaultCultureInfo, message, args); + var logEvent = LogEventInfo.Create(level, Name, exception, formatProvider, message, args); + WriteLogEventToTargets(logEvent, targetsForLevel); } - private void WriteToTargets(LogLevel level, IFormatProvider formatProvider, string message, object[] args) + private void WriteToTargets(LogLevel level, IFormatProvider? formatProvider, string message, object?[] args) { var targetsForLevel = GetTargetsForLevel(level); if (targetsForLevel != null) { var logEvent = LogEventInfo.Create(level, Name, formatProvider, message, args); - WriteToTargets(logEvent, targetsForLevel); + WriteLogEventToTargets(logEvent, targetsForLevel); } } @@ -691,45 +845,33 @@ private void WriteToTargets(LogLevel level, string message) { // please note that this overload calls the overload of LogEventInfo.Create with object[] parameter on purpose - // to avoid unnecessary string.Format (in case of calling Create(LogLevel, string, IFormatProvider, object)) - var logEvent = LogEventInfo.Create(level, Name, Factory.DefaultCultureInfo, message, (object[])null); - WriteToTargets(logEvent, targetsForLevel); + var logEvent = LogEventInfo.Create(level, Name, Factory.DefaultCultureInfo, message, (object[]?)null); + WriteLogEventToTargets(logEvent, targetsForLevel); } } - private void WriteToTargets(LogLevel level, IFormatProvider formatProvider, T value) + private void WriteToTargets(LogLevel level, IFormatProvider? formatProvider, T? value) { var targetsForLevel = GetTargetsForLevel(level); if (targetsForLevel != null) { var logEvent = LogEventInfo.Create(level, Name, formatProvider, value); - WriteToTargets(logEvent, targetsForLevel); + WriteLogEventToTargets(logEvent, targetsForLevel); } } - private void WriteToTargets(LogLevel level, Exception ex, string message, object[] args) - { - var targetsForLevel = GetTargetsForLevel(level); - if (targetsForLevel != null) - { - // Translate Exception with missing LogEvent message as log single value - var logEvent = message is null && ex != null && !(args?.Length > 0) ? - LogEventInfo.Create(level, Name, ExceptionMessageFormatProvider.Instance, ex) : - LogEventInfo.Create(level, Name, ex, Factory.DefaultCultureInfo, message, args); - WriteToTargets(logEvent, targetsForLevel); - } - } - private void WriteToTargets(LogLevel level, Exception ex, IFormatProvider formatProvider, string message, object[] args) + private void WriteToTargets(LogLevel level, Exception? ex, IFormatProvider? formatProvider, string message, object?[]? args) { var targetsForLevel = GetTargetsForLevel(level); if (targetsForLevel != null) { var logEvent = LogEventInfo.Create(level, Name, ex, formatProvider, message, args); - WriteToTargets(logEvent, targetsForLevel); + WriteLogEventToTargets(logEvent, targetsForLevel); } } - private void WriteToTargets([NotNull] LogEventInfo logEvent, [NotNull] ITargetWithFilterChain targetsForLevel) + private void WriteLogEventToTargets(LogEventInfo logEvent, ITargetWithFilterChain targetsForLevel) { try { @@ -749,7 +891,7 @@ private void WriteToTargets([NotNull] LogEventInfo logEvent, [NotNull] ITargetWi } } - private void WriteToTargets(Type wrapperType, [NotNull] LogEventInfo logEvent, [NotNull] ITargetWithFilterChain targetsForLevel) + private void WriteLogEventToTargets(Type wrapperType, LogEventInfo logEvent, ITargetWithFilterChain targetsForLevel) { try { @@ -784,7 +926,7 @@ internal void SetConfiguration(ITargetWithFilterChain[] targetsByLevel) OnLoggerReconfigured(EventArgs.Empty); } - private ITargetWithFilterChain GetTargetsForLevelSafe(LogLevel level) + private ITargetWithFilterChain GetTargetsForLevelSafe(LogLevel? level) { if (level is null) { diff --git a/src/NLog/LoggerImpl.cs b/src/NLog/LoggerImpl.cs index 4117a614ee..5a585b3ef8 100644 --- a/src/NLog/LoggerImpl.cs +++ b/src/NLog/LoggerImpl.cs @@ -31,16 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 -#define CaptureCallSiteInfo -#endif - namespace NLog { using System; using System.Collections.Generic; using System.Diagnostics; - using JetBrains.Annotations; using NLog.Common; using NLog.Config; using NLog.Filters; @@ -53,18 +48,17 @@ internal static class LoggerImpl { private const int StackTraceSkipMethods = 0; - internal static void Write([NotNull] Type loggerType, [NotNull] TargetWithFilterChain targetsForLevel, LogEventInfo logEvent, LogFactory logFactory) + internal static void Write(Type loggerType, TargetWithFilterChain targetsForLevel, LogEventInfo logEvent, LogFactory logFactory) { logEvent.SetMessageFormatter(logFactory.ActiveMessageFormatter, targetsForLevel.NextInChain is null ? logFactory.SingleTargetMessageFormatter : null); -#if CaptureCallSiteInfo StackTraceUsage stu = targetsForLevel.StackTraceUsage; if (stu != StackTraceUsage.None) { bool attemptCallSiteOptimization = targetsForLevel.TryCallSiteClassNameOptimization(stu, logEvent); - if (attemptCallSiteOptimization && targetsForLevel.TryLookupCallSiteClassName(logEvent, out string callSiteClassName)) + if (attemptCallSiteOptimization && targetsForLevel.TryLookupCallSiteClassName(logEvent, out var callSiteClassName)) { - logEvent.CallSiteInformation.CallerClassName = callSiteClassName; + logEvent.GetCallSiteInformationInternal().CallerClassName = callSiteClassName; } else if (attemptCallSiteOptimization || targetsForLevel.MustCaptureStackTrace(stu, logEvent)) { @@ -76,7 +70,6 @@ internal static void Write([NotNull] Type loggerType, [NotNull] TargetWithFilter } } } -#endif AsyncContinuation exceptionHandler = SingleCallContinuation.Completed; if (logFactory.ThrowExceptions) @@ -108,18 +101,12 @@ internal static void Write([NotNull] Type loggerType, [NotNull] TargetWithFilter } } -#if CaptureCallSiteInfo private static void CaptureCallSiteInfo(LogFactory logFactory, Type loggerType, LogEventInfo logEvent, StackTraceUsage stackTraceUsage) { try { bool includeSource = (stackTraceUsage & StackTraceUsage.WithFileNameAndLineNumber) != 0; -#if NETSTANDARD1_5 - var stackTrace = (StackTrace)Activator.CreateInstance(typeof(StackTrace), new object[] { includeSource }); -#else var stackTrace = new StackTrace(StackTraceSkipMethods, includeSource); -#endif - logEvent.GetCallSiteInformationInternal().SetStackTrace(stackTrace, null, loggerType); } catch (Exception ex) @@ -135,7 +122,6 @@ private static void CaptureCallSiteInfo(LogFactory logFactory, Type loggerType, InternalLogger.Error(ex, "{0} Failed to capture CallSite. Platform might not support ${{callsite}}", logEvent.LoggerName); } } -#endif private static bool WriteToTargetWithFilterChain(Targets.Target target, FilterResult result, LogEventInfo logEvent, AsyncContinuation onException) { diff --git a/src/NLog/MappedDiagnosticsContext.cs b/src/NLog/MappedDiagnosticsContext.cs index 40d5da995e..51950cf100 100644 --- a/src/NLog/MappedDiagnosticsContext.cs +++ b/src/NLog/MappedDiagnosticsContext.cs @@ -54,8 +54,9 @@ public static class MappedDiagnosticsContext /// Item value. /// An that can be used to remove the item from the current thread MDC. [Obsolete("Replaced by ScopeContext.PushProperty or Logger.PushScopeProperty using ${scopeproperty}. Marked obsolete on NLog 5.0")] - public static IDisposable SetScoped(string item, string value) + public static IDisposable SetScoped(string item, string? value) { + Guard.ThrowIfNull(item); return ScopeContext.PushProperty(item, value); } @@ -66,8 +67,9 @@ public static IDisposable SetScoped(string item, string value) /// Item value. /// >An that can be used to remove the item from the current thread MDC. [Obsolete("Replaced by ScopeContext.PushProperty or Logger.PushScopeProperty using ${scopeproperty}. Marked obsolete on NLog 5.0")] - public static IDisposable SetScoped(string item, object value) + public static IDisposable SetScoped(string item, object? value) { + Guard.ThrowIfNull(item); return ScopeContext.PushProperty(item, value); } @@ -77,8 +79,9 @@ public static IDisposable SetScoped(string item, object value) /// Item name. /// Item value. [Obsolete("Replaced by ScopeContext.PushProperty or Logger.PushScopeProperty using ${scopeproperty}. Marked obsolete on NLog 5.0")] - public static void Set(string item, string value) + public static void Set(string item, string? value) { + Guard.ThrowIfNull(item); ScopeContext.SetMappedContextLegacy(item, value); } @@ -88,8 +91,9 @@ public static void Set(string item, string value) /// Item name. /// Item value. [Obsolete("Replaced by ScopeContext.PushProperty or Logger.PushScopeProperty using ${scopeproperty}. Marked obsolete on NLog 5.0")] - public static void Set(string item, object value) + public static void Set(string item, object? value) { + Guard.ThrowIfNull(item); ScopeContext.SetMappedContextLegacy(item, value); } @@ -102,6 +106,7 @@ public static void Set(string item, object value) [Obsolete("Replaced by ScopeContext.TryGetProperty. Marked obsolete on NLog 5.0")] public static string Get(string item) { + Guard.ThrowIfNull(item); return Get(item, null); } @@ -113,7 +118,7 @@ public static string Get(string item) /// The value of , if defined; otherwise . /// If is null and the value isn't a already, this call locks the for reading the needed for converting to . [Obsolete("Replaced by ScopeContext.TryGetProperty. Marked obsolete on NLog 5.0")] - public static string Get(string item, IFormatProvider formatProvider) + public static string Get(string item, IFormatProvider? formatProvider) { return FormatHelper.ConvertToString(GetObject(item), formatProvider); } @@ -124,8 +129,9 @@ public static string Get(string item, IFormatProvider formatProvider) /// Item name. /// The value of , if defined; otherwise null. [Obsolete("Replaced by ScopeContext.TryGetProperty. Marked obsolete on NLog 5.0")] - public static object GetObject(string item) + public static object? GetObject(string item) { + Guard.ThrowIfNull(item); if (ScopeContext.TryGetProperty(item, out var value)) return value; else @@ -150,6 +156,7 @@ public static ICollection GetNames() [Obsolete("Replaced by ScopeContext.TryGetProperty. Marked obsolete on NLog 5.0")] public static bool Contains(string item) { + Guard.ThrowIfNull(item); return ScopeContext.TryGetProperty(item, out var _); } @@ -160,6 +167,7 @@ public static bool Contains(string item) [Obsolete("Replaced by dispose of return value from ScopeContext.PushProperty. Marked obsolete on NLog 5.0")] public static void Remove(string item) { + Guard.ThrowIfNull(item); ScopeContext.RemoveMappedContextLegacy(item); } diff --git a/src/NLog/MappedDiagnosticsLogicalContext.cs b/src/NLog/MappedDiagnosticsLogicalContext.cs index 4dc464e844..77232ca825 100644 --- a/src/NLog/MappedDiagnosticsLogicalContext.cs +++ b/src/NLog/MappedDiagnosticsLogicalContext.cs @@ -71,7 +71,7 @@ public static string Get(string item) /// The value of , if defined; otherwise . /// If is null and the value isn't a already, this call locks the for reading the needed for converting to . [Obsolete("Replaced by ScopeContext.TryGetProperty. Marked obsolete on NLog 5.0")] - public static string Get(string item, IFormatProvider formatProvider) + public static string Get(string item, IFormatProvider? formatProvider) { return FormatHelper.ConvertToString(GetObject(item), formatProvider); } @@ -82,8 +82,9 @@ public static string Get(string item, IFormatProvider formatProvider) /// Item name. /// The value of , if defined; otherwise null. [Obsolete("Replaced by ScopeContext.TryGetProperty. Marked obsolete on NLog 5.0")] - public static object GetObject(string item) + public static object? GetObject(string item) { + Guard.ThrowIfNull(item); if (ScopeContext.TryGetProperty(item, out var value)) return value; else @@ -97,8 +98,9 @@ public static object GetObject(string item) /// Item value. /// >An that can be used to remove the item from the current logical context. [Obsolete("Replaced by ScopeContext.PushProperty or Logger.PushScopeProperty using ${scopeproperty}. Marked obsolete on NLog 5.0")] - public static IDisposable SetScoped(string item, string value) + public static IDisposable SetScoped(string item, string? value) { + Guard.ThrowIfNull(item); return SetScoped(item, value); } @@ -109,8 +111,9 @@ public static IDisposable SetScoped(string item, string value) /// Item value. /// >An that can be used to remove the item from the current logical context. [Obsolete("Replaced by ScopeContext.PushProperty or Logger.PushScopeProperty using ${scopeproperty}. Marked obsolete on NLog 5.0")] - public static IDisposable SetScoped(string item, object value) + public static IDisposable SetScoped(string item, object? value) { + Guard.ThrowIfNull(item); return SetScoped(item, value); } @@ -121,8 +124,9 @@ public static IDisposable SetScoped(string item, object value) /// Item value. /// >An that can be used to remove the item from the current logical context. [Obsolete("Replaced by ScopeContext.PushProperty or Logger.PushScopeProperty using ${scopeproperty}. Marked obsolete on NLog 5.0")] - public static IDisposable SetScoped(string item, T value) + public static IDisposable SetScoped(string item, T? value) { + Guard.ThrowIfNull(item); return ScopeContext.PushProperty(item, value); } @@ -133,7 +137,7 @@ public static IDisposable SetScoped(string item, T value) /// . /// >An that can be used to remove the item from the current logical context (null if no items). [Obsolete("Replaced by ScopeContext.PushProperties or Logger.PushScopeProperty using ${scopeproperty}. Marked obsolete on NLog 5.0")] - public static IDisposable SetScoped(IReadOnlyList> items) + public static IDisposable SetScoped(IReadOnlyList> items) { return ScopeContext.PushProperties(items); } @@ -144,7 +148,7 @@ public static IDisposable SetScoped(IReadOnlyList> /// Item name. /// Item value. [Obsolete("Replaced by ScopeContext.PushProperty or Logger.PushScopeProperty using ${scopeproperty}. Marked obsolete on NLog 5.0")] - public static void Set(string item, string value) + public static void Set(string item, string? value) { Set(item, value); } @@ -155,7 +159,7 @@ public static void Set(string item, string value) /// Item name. /// Item value. [Obsolete("Replaced by ScopeContext.PushProperty or Logger.PushScopeProperty using ${scopeproperty}. Marked obsolete on NLog 5.0")] - public static void Set(string item, object value) + public static void Set(string item, object? value) { Set(item, value); } @@ -166,8 +170,9 @@ public static void Set(string item, object value) /// Item name. /// Item value. [Obsolete("Replaced by ScopeContext.PushProperty or Logger.PushScopeProperty using ${scopeproperty}. Marked obsolete on NLog 5.0")] - public static void Set(string item, T value) + public static void Set(string item, T? value) { + Guard.ThrowIfNull(item); ScopeContext.SetMappedContextLegacy(item, value); } @@ -189,6 +194,7 @@ public static ICollection GetNames() [Obsolete("Replaced by ScopeContext.TryGetProperty. Marked obsolete on NLog 5.0")] public static bool Contains(string item) { + Guard.ThrowIfNull(item); return ScopeContext.TryGetProperty(item, out var _); } @@ -199,6 +205,7 @@ public static bool Contains(string item) [Obsolete("Replaced by dispose of return value from ScopeContext.PushProperty. Marked obsolete on NLog 5.0")] public static void Remove(string item) { + Guard.ThrowIfNull(item); ScopeContext.RemoveMappedContextLegacy(item); } diff --git a/src/NLog/MessageTemplates/Hole.cs b/src/NLog/MessageTemplates/Hole.cs index 64c2c123d8..80c7568959 100644 --- a/src/NLog/MessageTemplates/Hole.cs +++ b/src/NLog/MessageTemplates/Hole.cs @@ -36,12 +36,16 @@ namespace NLog.MessageTemplates /// /// A hole that will be replaced with a value /// - internal struct Hole + internal +#if !NETFRAMEWORK + readonly +#endif + struct Hole { /// /// Constructor /// - public Hole(string name, string format, CaptureType captureType, short parameterIndex, short alignment) + public Hole(string name, string? format, CaptureType captureType, short parameterIndex, short alignment) { Name = name; Format = format; @@ -56,7 +60,7 @@ public Hole(string name, string format, CaptureType captureType, short parameter public readonly string Name; /// Format to render the parameter. /// This is everything between ":" and the first unescaped "}" - public readonly string Format; + public readonly string? Format; /// /// Type /// diff --git a/src/NLog/MessageTemplates/Literal.cs b/src/NLog/MessageTemplates/Literal.cs index 84deda65e8..c14380df55 100644 --- a/src/NLog/MessageTemplates/Literal.cs +++ b/src/NLog/MessageTemplates/Literal.cs @@ -36,7 +36,11 @@ namespace NLog.MessageTemplates /// /// A fixed value /// - internal struct Literal + internal +#if !NETFRAMEWORK + readonly +#endif + struct Literal { /// Number of characters from the original template to copy at the current position. /// This can be 0 when the template starts with a hole or when there are multiple consecutive holes. diff --git a/src/NLog/MessageTemplates/LiteralHole.cs b/src/NLog/MessageTemplates/LiteralHole.cs index ccc72a2b46..7a86bb5bc1 100644 --- a/src/NLog/MessageTemplates/LiteralHole.cs +++ b/src/NLog/MessageTemplates/LiteralHole.cs @@ -36,15 +36,27 @@ namespace NLog.MessageTemplates /// /// Combines Literal and Hole /// - internal struct LiteralHole + internal +#if !NETFRAMEWORK + readonly +#endif + struct LiteralHole { /// Literal - public Literal Literal; // Not readonly to avoid struct-copy, and to avoid VerificationException when medium-trust AppDomain + public +#if !NETFRAMEWORK + readonly +#endif + Literal Literal; // Not readonly to avoid struct-copy, and to avoid VerificationException when medium-trust AppDomain /// Hole /// Uninitialized when = 0. - public Hole Hole; // Not readonly to avoid struct-copy, and to avoid VerificationException when medium-trust AppDomain + public +#if !NETFRAMEWORK + readonly +#endif + Hole Hole; // Not readonly to avoid struct-copy, and to avoid VerificationException when medium-trust AppDomain - public LiteralHole(Literal literal, Hole hole) + public LiteralHole(in Literal literal, in Hole hole) { Literal = literal; Hole = hole; diff --git a/src/NLog/MessageTemplates/MessageTemplateParameter.cs b/src/NLog/MessageTemplates/MessageTemplateParameter.cs index 4e97f96f13..13683d438d 100644 --- a/src/NLog/MessageTemplates/MessageTemplateParameter.cs +++ b/src/NLog/MessageTemplates/MessageTemplateParameter.cs @@ -31,35 +31,35 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using JetBrains.Annotations; -using NLog.Internal; - namespace NLog.MessageTemplates { + using NLog.Internal; + /// /// Description of a single parameter extracted from a MessageTemplate /// - public struct MessageTemplateParameter + public +#if !NETFRAMEWORK + readonly +#endif + struct MessageTemplateParameter { /// /// Parameter Name extracted from /// This is everything between "{" and the first of ",:}". /// - [NotNull] public string Name { get; } /// /// Parameter Value extracted from the -array /// - [CanBeNull] - public object Value { get; } + public object? Value { get; } /// /// Format to render the parameter. /// This is everything between ":" and the first unescaped "}" /// - [CanBeNull] - public string Format { get; } + public string? Format { get; } /// /// Parameter method that should be used to render the parameter @@ -102,7 +102,7 @@ public int? PositionalIndex /// Parameter Name /// Parameter Value /// Parameter Format - internal MessageTemplateParameter([NotNull] string name, object value, string format) + internal MessageTemplateParameter(string name, object? value, string? format) { Name = Guard.ThrowIfNull(name); Value = value; @@ -117,7 +117,7 @@ internal MessageTemplateParameter([NotNull] string name, object value, string fo /// Parameter Value /// Parameter Format /// Parameter CaptureType - public MessageTemplateParameter([NotNull] string name, object value, string format, CaptureType captureType) + public MessageTemplateParameter(string name, object? value, string? format, CaptureType captureType) { Name = Guard.ThrowIfNull(name); Value = value; diff --git a/src/NLog/MessageTemplates/MessageTemplateParameters.cs b/src/NLog/MessageTemplates/MessageTemplateParameters.cs index 65e4834bfd..6a85dc5b4d 100644 --- a/src/NLog/MessageTemplates/MessageTemplateParameters.cs +++ b/src/NLog/MessageTemplates/MessageTemplateParameters.cs @@ -78,20 +78,26 @@ public sealed class MessageTemplateParameters : IEnumerable /// including any parameter placeholders /// All - internal MessageTemplateParameters(string message, object[] parameters) + internal MessageTemplateParameters(string message, object?[] parameters) { - var hasParameters = parameters?.Length > 0; - bool isPositional = hasParameters; - bool isValidTemplate = !hasParameters; - _parameters = hasParameters ? ParseMessageTemplate(message, parameters, out isPositional, out isValidTemplate) : ArrayHelper.Empty(); - IsPositional = isPositional; - IsValidTemplate = isValidTemplate; + if (parameters is null || parameters.Length == 0) + { + _parameters = ArrayHelper.Empty(); + IsPositional = false; + IsValidTemplate = true; + } + else + { + _parameters = ParseMessageTemplate(message, parameters, out var isPositional, out var isValidTemplate); + IsPositional = isPositional; + IsValidTemplate = isValidTemplate; + } } /// /// Constructor for named parameters that already has been parsed /// - internal MessageTemplateParameters(IList templateParameters, string message, object[] parameters) + internal MessageTemplateParameters(IList templateParameters, string message, object?[]? parameters) { _parameters = templateParameters ?? ArrayHelper.Empty(); if (parameters != null && _parameters.Count != parameters.Length) @@ -103,7 +109,7 @@ internal MessageTemplateParameters(IList templateParam /// /// Create MessageTemplateParameter from /// - private static IList ParseMessageTemplate(string template, object[] parameters, out bool isPositional, out bool isValidTemplate) + private static IList ParseMessageTemplate(string template, object?[] parameters, out bool isPositional, out bool isValidTemplate) { isPositional = true; isValidTemplate = true; @@ -184,7 +190,7 @@ private static short GetMaxHoleIndex(short maxHoleIndex, short holeIndex) return maxHoleIndex; } - private static object GetHoleValueSafe(object[] parameters, short holeIndex, ref bool isValidTemplate) + private static object? GetHoleValueSafe(object?[] parameters, short holeIndex, ref bool isValidTemplate) { if (parameters.Length > holeIndex) { diff --git a/src/NLog/MessageTemplates/TemplateEnumerator.cs b/src/NLog/MessageTemplates/TemplateEnumerator.cs index 895185e2ac..31166f8575 100644 --- a/src/NLog/MessageTemplates/TemplateEnumerator.cs +++ b/src/NLog/MessageTemplates/TemplateEnumerator.cs @@ -31,14 +31,14 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using NLog.Internal; - namespace NLog.MessageTemplates { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using NLog.Internal; + /// /// Parse templates. /// @@ -54,6 +54,8 @@ internal struct TemplateEnumerator : IEnumerator private LiteralHole _current; private const short Zero = 0; + public string Template => _template; + /// /// Parse a template. /// @@ -183,7 +185,7 @@ private void ParseHole(CaptureType type) int start = _position; string name = ParseName(out var parameterIndex); int alignment = 0; - string format = null; + string? format = null; if (Peek() != '}') { alignment = Peek() == ',' ? ParseAlignment() : 0; diff --git a/src/NLog/MessageTemplates/TemplateParserException.cs b/src/NLog/MessageTemplates/TemplateParserException.cs index ea3a4108bc..4ef99e9d3a 100644 --- a/src/NLog/MessageTemplates/TemplateParserException.cs +++ b/src/NLog/MessageTemplates/TemplateParserException.cs @@ -31,10 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; - namespace NLog.MessageTemplates { + using System; + /// /// Error when parsing a template. /// diff --git a/src/NLog/MessageTemplates/ValueFormatter.cs b/src/NLog/MessageTemplates/ValueFormatter.cs index 69cf4acd76..87e39c6637 100644 --- a/src/NLog/MessageTemplates/ValueFormatter.cs +++ b/src/NLog/MessageTemplates/ValueFormatter.cs @@ -31,17 +31,16 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Text; -using JetBrains.Annotations; -using NLog.Config; -using NLog.Internal; - namespace NLog.MessageTemplates { + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.Text; + using NLog.Config; + using NLog.Internal; + /// /// Convert, Render or serialize a value, with optionally backwards-compatible with /// @@ -50,12 +49,48 @@ internal sealed class ValueFormatter : IValueFormatter private static readonly IEqualityComparer _referenceEqualsComparer = SingleItemOptimizedHashSet.ReferenceEqualityComparer.Default; private readonly MruCache _enumCache = new MruCache(2000); private readonly IServiceProvider _serviceProvider; - private IJsonConverter JsonConverter => _jsonConverter ?? (_jsonConverter = _serviceProvider.GetService()); - private IJsonConverter _jsonConverter; + private readonly bool _legacyStringQuotes; - public ValueFormatter([NotNull] IServiceProvider serviceProvider) + private IJsonConverter JsonConverter + { + get => _jsonConverter ?? (_jsonConverter = JsonConverterWithSpaces.CreateJsonConverter(_serviceProvider.GetService())); + } + private IJsonConverter? _jsonConverter; + + private sealed class JsonConverterWithSpaces : IJsonConverter + { + private readonly Targets.DefaultJsonSerializer _serializer; + private readonly Targets.JsonSerializeOptions _serializerOptions; + private readonly Targets.JsonSerializeOptions _exceptionSerializerOptions; + + public static IJsonConverter CreateJsonConverter(IJsonConverter jsonConverter) + { + if (jsonConverter is Targets.DefaultJsonSerializer defaultJsonConverter) + return new JsonConverterWithSpaces(defaultJsonConverter); + else + return jsonConverter; + } + + private JsonConverterWithSpaces(Targets.DefaultJsonSerializer jsonConverter) + { + _serializer = jsonConverter; + _serializerOptions = new Targets.JsonSerializeOptions() { SuppressSpaces = false }; + _exceptionSerializerOptions = new Targets.JsonSerializeOptions() { SuppressSpaces = false, SanitizeDictionaryKeys = true }; + } + + public bool SerializeObject(object? value, StringBuilder builder) + { + if (value is Exception) + return _serializer.SerializeObject(value, builder, _exceptionSerializerOptions); + else + return _serializer.SerializeObject(value, builder, _serializerOptions); + } + } + + public ValueFormatter(IServiceProvider serviceProvider, bool legacyStringQuotes) { _serviceProvider = serviceProvider; + _legacyStringQuotes = legacyStringQuotes; } private const int MaxRecursionDepth = 2; @@ -73,7 +108,7 @@ public ValueFormatter([NotNull] IServiceProvider serviceProvider) /// An object that supplies culture-specific formatting information. /// Output destination. /// Serialize succeeded (true/false) - public bool FormatValue(object value, string format, CaptureType captureType, IFormatProvider formatProvider, StringBuilder builder) + public bool FormatValue(object? value, string? format, CaptureType captureType, IFormatProvider? formatProvider, StringBuilder builder) { switch (captureType) { @@ -103,15 +138,14 @@ public bool FormatValue(object value, string format, CaptureType captureType, IF /// /// /// - public bool FormatObject(object value, string format, IFormatProvider formatProvider, StringBuilder builder) + public bool FormatObject(object? value, string? format, IFormatProvider? formatProvider, StringBuilder builder) { if (SerializeSimpleObject(value, format, formatProvider, builder, false)) { return true; } - IEnumerable collection = value as IEnumerable; - if (collection != null) + if (value is IEnumerable collection) { return SerializeWithoutCyclicLoop(collection, format, formatProvider, builder, default(SingleItemOptimizedHashSet), 0); } @@ -123,7 +157,7 @@ public bool FormatObject(object value, string format, IFormatProvider formatProv /// /// Try serializing a scalar (string, int, NULL) or simple type (IFormattable) /// - private bool SerializeSimpleObject(object value, string format, IFormatProvider formatProvider, StringBuilder builder, bool convertToString = true) + private bool SerializeSimpleObject(object? value, string? format, IFormatProvider? formatProvider, StringBuilder builder, bool convertToString = true) { if (value is string stringValue) { @@ -161,7 +195,7 @@ private bool SerializeSimpleObject(object value, string format, IFormatProvider } } - private void SerializeConvertibleObject(IConvertible value, string format, IFormatProvider formatProvider, StringBuilder builder) + private void SerializeConvertibleObject(IConvertible value, string? format, IFormatProvider? formatProvider, StringBuilder builder) { TypeCode convertibleTypeCode = value.GetTypeCode(); if (convertibleTypeCode == TypeCode.String) @@ -185,7 +219,7 @@ private void SerializeConvertibleObject(IConvertible value, string format, IForm } case TypeCode.Char: { - bool includeQuotes = format != LiteralFormatSymbol; + bool includeQuotes = _legacyStringQuotes && format != LiteralFormatSymbol; if (includeQuotes) builder.Append('"'); builder.Append(value.ToChar(CultureInfo.InvariantCulture)); if (includeQuotes) builder.Append('"'); @@ -219,19 +253,19 @@ private void SerializeConvertibleObject(IConvertible value, string format, IForm } } - private static void SerializeConvertToString(object value, IFormatProvider formatProvider, StringBuilder builder) + private static void SerializeConvertToString(object? value, IFormatProvider? formatProvider, StringBuilder builder) { -#if NETSTANDARD +#if !NETFRAMEWORK if (value is IFormattable) builder.AppendFormat(formatProvider, "{0}", value); // Support ISpanFormattable else #endif - builder.Append(Convert.ToString(value, formatProvider)); + builder.Append(Convert.ToString(value, formatProvider)); } - private static void SerializeStringObject(string stringValue, string format, StringBuilder builder) + private void SerializeStringObject(string stringValue, string? format, StringBuilder builder) { - bool includeQuotes = format != LiteralFormatSymbol; + bool includeQuotes = _legacyStringQuotes && format != LiteralFormatSymbol; if (includeQuotes) builder.Append('"'); builder.Append(stringValue); if (includeQuotes) builder.Append('"'); @@ -247,20 +281,16 @@ private void AppendEnumAsString(StringBuilder sb, Enum value) sb.Append(textValue); } - private bool SerializeWithoutCyclicLoop(IEnumerable collection, string format, IFormatProvider formatProvider, StringBuilder builder, + private bool SerializeWithoutCyclicLoop(IEnumerable collection, string? format, IFormatProvider? formatProvider, StringBuilder builder, SingleItemOptimizedHashSet objectsInPath, int depth) { if (objectsInPath.Contains(collection)) - { return false; // detected reference loop, skip serialization - } + if (depth > MaxRecursionDepth) - { return false; // reached maximum recursion level, no further serialization - } - IDictionary dictionary = collection as IDictionary; - if (dictionary != null) + if (collection is IDictionary dictionary) { using (new SingleItemOptimizedHashSet.SingleItemScopedInsert(dictionary, ref objectsInPath, true, _referenceEqualsComparer)) { @@ -287,7 +317,7 @@ private bool SerializeWithoutCyclicLoop(IEnumerable collection, string format, I /// /// /// - private bool SerializeDictionaryObject(IDictionary dictionary, string format, IFormatProvider formatProvider, StringBuilder builder, SingleItemOptimizedHashSet objectsInPath, int depth) + private bool SerializeDictionaryObject(IDictionary dictionary, string? format, IFormatProvider? formatProvider, StringBuilder builder, SingleItemOptimizedHashSet objectsInPath, int depth) { bool separator = false; foreach (var item in new DictionaryEntryEnumerable(dictionary)) @@ -305,7 +335,7 @@ private bool SerializeDictionaryObject(IDictionary dictionary, string format, IF return true; } - private bool SerializeCollectionObject(IEnumerable collection, string format, IFormatProvider formatProvider, StringBuilder builder, SingleItemOptimizedHashSet objectsInPath, int depth) + private bool SerializeCollectionObject(IEnumerable collection, string? format, IFormatProvider? formatProvider, StringBuilder builder, SingleItemOptimizedHashSet objectsInPath, int depth) { bool separator = false; foreach (var item in collection) @@ -322,7 +352,7 @@ private bool SerializeCollectionObject(IEnumerable collection, string format, IF return true; } - private void SerializeCollectionItem(object item, string format, IFormatProvider formatProvider, StringBuilder builder, ref SingleItemOptimizedHashSet objectsInPath, int depth) + private void SerializeCollectionItem(object item, string? format, IFormatProvider? formatProvider, StringBuilder builder, ref SingleItemOptimizedHashSet objectsInPath, int depth) { if (item is IConvertible convertible) SerializeConvertibleObject(convertible, format, formatProvider, builder); @@ -339,7 +369,7 @@ private void SerializeCollectionItem(object item, string format, IFormatProvider /// Format sting for the value. /// Format provider for the value. /// Append to this - public static void FormatToString(object value, string format, IFormatProvider formatProvider, StringBuilder builder) + public static void FormatToString(object? value, string? format, IFormatProvider? formatProvider, StringBuilder builder) { var stringValue = value as string; if (stringValue != null) @@ -350,12 +380,12 @@ public static void FormatToString(object value, string format, IFormatProvider f { if (value is IFormattable formattable) { -#if NETSTANDARD +#if !NETFRAMEWORK if (string.IsNullOrEmpty(format)) builder.AppendFormat(formatProvider, "{0}", formattable); // Support ISpanFormattable else #endif - builder.Append(formattable.ToString(format, formatProvider)); + builder.Append(formattable.ToString(format, formatProvider)); } else { diff --git a/src/NLog/NLog.csproj b/src/NLog/NLog.csproj index 27457a15d6..8fae7e29ec 100644 --- a/src/NLog/NLog.csproj +++ b/src/NLog/NLog.csproj @@ -1,7 +1,9 @@ - net35;net46;netstandard1.3;netstandard1.5;netstandard2.0 + 17.0 + net35;net46;netstandard2.0 + net35;net46;netstandard2.0;netstandard2.1 NLog for .NET Framework and .NET Standard NLog @@ -10,9 +12,8 @@ NLog supports traditional logging, structured logging and the combination of bot Supported platforms: -- .NET 5, 6, 7 and 8 -- .NET Core 1, 2 and 3 -- .NET Standard 1.3+ and 2.0+ +- .NET 5, 6, 7, 8 and 9 +- .NET Standard 2.0 and 2.1 - .NET Framework 3.5 - 4.8 - Xamarin Android + iOS (.NET Standard) - Mono 4 @@ -27,31 +28,36 @@ For ASP.NET Core, check: https://www.nuget.org/packages/NLog.Web.AspNetCore Copyright (c) 2004-$(CurrentYear) NLog Project - https://nlog-project.org/ -ChangeLog: - -- Layout.FromMethod that supports typed Layout (#5572) (@smnsht) -- Layout.FromMethod that supports typed Layout (without boxing) (#5580) (@snakefoot) -- ScopeContextPropertyEnumerator - Optimize HasUniqueCollectionKeys (#5570) (@snakefoot) -- XmlLayout - Fixed bug in handling unsafe xml property names (#5571) (@snakefoot) -- FuncThreadAgnosticLayoutRenderer - Implement IRawValue (#5573) (@snakefoot) -- Introduced OnConfigurationAssigned to signal activation of LoggingConfiguration (#5577) (@snakefoot) -- Update copyright to 2024, and removed trailing white spaces in source code (#5578) (@snakefoot) -- Fixed various issues reported by EnableNETAnalyzers (#5585) (@snakefoot) -- NetworkTarget - Added SendTimeoutSeconds to assign TCP Socket SendTimeout (#5587) (@snakefoot) -- DateLayoutRenderer - Optimize for Round Trip ISO 8601 Date Format = o (#5588) (@snakefoot) -- LayoutRenderer - Changed Render-method to use StringBuilderPool (#5589) (@snakefoot) -- JsonLayout - Precalculate Json-Document delimiters upfront (#5600) (@snakefoot) -- JsonLayout - Refactor code to simplify rendering of scope properties (#5599) (@snakefoot) - -NLog v5.2 changes how to load extensions: https://nlog-project.org/2023/05/30/nlog-5-2-trim-warnings.html - -List of major changes in NLog 5.0: https://nlog-project.org/2022/05/16/nlog-5-0-finally-ready.html - -Full changelog: https://github.com/NLog/NLog/blob/master/CHANGELOG.md +Changelog: + +- (#5930) XmlParser - Handle XML comments after root-end-tag. (@snakefoot) +- (#5929) XmlLoggingConfiguration - Improve handling of invalid XML. (@snakefoot) +- (#5933) Handle invalid message template when skipping parameters array. (@snakefoot) +- (#5915) ReplaceNewLinesLayoutRendererWrapper - Replace more line ending characters. (@oikku) +- (#5911) NLog.Targets.GZipFile - Improve support for ArchiveAboveSize. (@snakefoot) +- (#5921) FileTarget - Activate legacy ArchiveFileName when ArchiveSuffixFormat contains legacy placeholder. (@snakefoot) +- (#5924) AsyncTargetWrapper - Updated FullBatchSizeWriteLimit default value from 5 to 10. (@snakefoot) +- (#5937) Mark Assembly loading with RequiresUnreferencedCodeAttribute for AOT. (@snakefoot) +- (#5938) Logger - Align WriteToTargets with WriteToTargetsWithSpan. (@snakefoot) +- (#5909) ConfigurationItemFactory - Added extension hints for webservice and activityid. (@snakefoot) +- (#5918) Log4JXmlTarget - Removed alias NLogViewer as conflicts with other nuget-packages. (@snakefoot) +- (#5926) SplunkTarget - NetworkTarget with SplunkLayout. (@snakefoot) +- (#5927) GelfLayout - Align with SplunkLayout. (@snakefoot) +- (#5913) NLog.Targets.Network - Updated nuget-package README.md. (@snakefoot) +- (#5912) NLog.Targets.Trace - Updated nuget-package README.md. (@snakefoot) +- (#5919) XML docs for Targets and Layouts with remarks about default value. (@snakefoot) +- (#5922) XML docs for LayoutRenderers with remarks about default value. (@snakefoot) +- (#5925) XML docs for Target Wrappers with remarks about default value. (@snakefoot) +- (#5935) Improve NLog XSD Schema with better handling of typed Layout. (@snakefoot) +- (#5923) Updated unit-tests from NET6 to NET8. (@snakefoot) + +NLog v6.0 release notes: https://nlog-project.org/2025/04/29/nlog-6-0-major-changes.html + +Full changelog: https://github.com/NLog/NLog/releases For all config options and platform support, check https://nlog-project.org/config/ - NLog;logging;log;structured;tracing;logfiles;database;eventlog;console;email + NLog;logging;log;structured;tracing;logfiles;database;eventlog;console N.png https://nlog-project.org/ BSD-3-Clause @@ -59,15 +65,19 @@ For all config options and platform support, check https://nlog-project.org/conf https://github.com/NLog/NLog.git true - 5.0.0.0 + 6.0.0.0 ..\NLog.snk true + true true true - true - true - copyused + enable + 9 + 13 + true + true + true @@ -88,20 +98,14 @@ For all config options and platform support, check https://nlog-project.org/conf Full - - NLog for NetStandard 1.3 - 1.6.0 - - - - NLog for NetStandard 1.5 - 1.6.0 - - NLog for NetStandard 2.0 + + NLog for NetStandard 2.1 + + $(Title) - Mono $(DefineConstants);MONO @@ -111,70 +115,13 @@ For all config options and platform support, check https://nlog-project.org/conf - - - ILLink.Descriptors.xml - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TextTemplatingFileGenerator diff --git a/src/NLog/NLogConfigurationException.cs b/src/NLog/NLogConfigurationException.cs index 38732608a5..c3e3f8be73 100644 --- a/src/NLog/NLogConfigurationException.cs +++ b/src/NLog/NLogConfigurationException.cs @@ -70,7 +70,7 @@ public NLogConfigurationException(string message) [Obsolete("Instead use string interpolation. Marked obsolete with NLog 5.0")] [StringFormatMethod("message")] [EditorBrowsable(EditorBrowsableState.Never)] - public NLogConfigurationException(string message, params object[] messageParameters) + public NLogConfigurationException(string message, params object?[] messageParameters) : base(string.Format(message, messageParameters)) { } @@ -85,7 +85,7 @@ public NLogConfigurationException(string message, params object[] messageParamet [Obsolete("Instead use string interpolation. Marked obsolete with NLog 5.0")] [StringFormatMethod("message")] [EditorBrowsable(EditorBrowsableState.Never)] - public NLogConfigurationException(Exception innerException, string message, params object[] messageParameters) + public NLogConfigurationException(Exception? innerException, string message, params object?[] messageParameters) : base(string.Format(message, messageParameters), innerException) { } @@ -95,7 +95,7 @@ public NLogConfigurationException(Exception innerException, string message, para /// /// The message. /// The inner exception. - public NLogConfigurationException(string message, Exception innerException) + public NLogConfigurationException(string message, Exception? innerException) : base(message, innerException) { } diff --git a/src/NLog/NLogRuntimeException.cs b/src/NLog/NLogRuntimeException.cs index 176fbbf702..3656c65871 100644 --- a/src/NLog/NLogRuntimeException.cs +++ b/src/NLog/NLogRuntimeException.cs @@ -31,12 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using JetBrains.Annotations; - namespace NLog { using System; using System.ComponentModel; + using JetBrains.Annotations; /// /// Exception thrown during log event processing. @@ -71,7 +70,7 @@ public NLogRuntimeException(string message) [Obsolete("Instead use string interpolation. Marked obsolete with NLog 5.0")] [StringFormatMethod("message")] [EditorBrowsable(EditorBrowsableState.Never)] - public NLogRuntimeException(string message, params object[] messageParameters) + public NLogRuntimeException(string message, params object?[] messageParameters) : base(string.Format(message, messageParameters)) { } @@ -81,7 +80,7 @@ public NLogRuntimeException(string message, params object[] messageParameters) /// /// The message. /// The inner exception. - public NLogRuntimeException(string message, Exception innerException) + public NLogRuntimeException(string message, Exception? innerException) : base(message, innerException) { } diff --git a/src/NLog/NestedDiagnosticsContext.cs b/src/NLog/NestedDiagnosticsContext.cs index 44c4492c5e..c8d64b6d75 100644 --- a/src/NLog/NestedDiagnosticsContext.cs +++ b/src/NLog/NestedDiagnosticsContext.cs @@ -58,7 +58,7 @@ public static class NestedDiagnosticsContext /// /// The object at the top of the NDC stack if defined; otherwise null. [Obsolete("Replaced by ScopeContext.PeekNestedState. Marked obsolete on NLog 5.0")] - public static object TopObject => PeekObject(); + public static object? TopObject => PeekObject(); /// /// Pushes the specified text on current thread NDC. @@ -98,7 +98,7 @@ public static string Pop() /// The to use when converting the value to a string. /// The top message, which is removed from the stack, as a string value. [Obsolete("Replaced by dispose of return value from ScopeContext.PushNestedState or Logger.PushScopeNested. Marked obsolete on NLog 5.0")] - public static string Pop(IFormatProvider formatProvider) + public static string Pop(IFormatProvider? formatProvider) { return FormatHelper.ConvertToString(PopObject() ?? string.Empty, formatProvider); } @@ -108,7 +108,7 @@ public static string Pop(IFormatProvider formatProvider) /// /// The object from the top of the NDC stack, if defined; otherwise null. [Obsolete("Replaced by dispose of return value from ScopeContext.PushNestedState or Logger.PushScopeNested. Marked obsolete on NLog 5.0")] - public static object PopObject() + public static object? PopObject() { return ScopeContext.PopNestedContextLegacy(); } @@ -118,7 +118,7 @@ public static object PopObject() /// /// The object from the top of the NDC stack, if defined; otherwise null. [Obsolete("Replaced by ScopeContext.PeekNestedState. Marked obsolete on NLog 5.0")] - public static object PeekObject() + public static object? PeekObject() { return ScopeContext.PeekNestedState(); } @@ -148,7 +148,7 @@ public static string[] GetAllMessages() /// The to use when converting a value to a string. /// Array of strings. [Obsolete("Replaced by ScopeContext.GetAllNestedStates. Marked obsolete on NLog 5.0")] - public static string[] GetAllMessages(IFormatProvider formatProvider) + public static string[] GetAllMessages(IFormatProvider? formatProvider) { var stack = GetAllObjects(); if (stack.Length == 0) diff --git a/src/NLog/NestedDiagnosticsLogicalContext.cs b/src/NLog/NestedDiagnosticsLogicalContext.cs index 6d02ece540..eed302d16a 100644 --- a/src/NLog/NestedDiagnosticsLogicalContext.cs +++ b/src/NLog/NestedDiagnosticsLogicalContext.cs @@ -31,7 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // - namespace NLog { using System; @@ -75,7 +74,7 @@ public static IDisposable PushObject(object value) /// The top message which is no longer on the stack. /// this methods returns a object instead of string, this because of backwards-compatibility [Obsolete("Replaced by dispose of return value from ScopeContext.PushNestedState or Logger.PushScopeNested. Marked obsolete on NLog 5.0")] - public static object Pop() + public static object? Pop() { return PopObject(); } @@ -96,7 +95,7 @@ public static string Pop(IFormatProvider formatProvider) /// /// The object from the top of the NDLC stack, if defined; otherwise null. [Obsolete("Replaced by dispose of return value from ScopeContext.PushNestedState or Logger.PushScopeNested. Marked obsolete on NLog 5.0")] - public static object PopObject() + public static object? PopObject() { return ScopeContext.PopNestedContextLegacy(); } @@ -106,7 +105,7 @@ public static object PopObject() /// /// The object from the top of the NDLC stack, if defined; otherwise null. [Obsolete("Replaced by ScopeContext.PeekNestedState. Marked obsolete on NLog 5.0")] - public static object PeekObject() + public static object? PeekObject() { return ScopeContext.PeekNestedState(); } @@ -136,7 +135,7 @@ public static string[] GetAllMessages() /// The to use when converting a value to a string. /// Array of strings. [Obsolete("Replaced by ScopeContext.GetAllNestedStates. Marked obsolete on NLog 5.0")] - public static string[] GetAllMessages(IFormatProvider formatProvider) + public static string[] GetAllMessages(IFormatProvider? formatProvider) { return GetAllObjects().Select((o) => FormatHelper.ConvertToString(o, formatProvider)).ToArray(); } @@ -156,7 +155,7 @@ interface INestedContext : IDisposable { INestedContext Parent { get; } int FrameLevel { get; } - object Value { get; } + object? Value { get; } long CreatedTimeUtcTicks { get; } } @@ -172,11 +171,11 @@ sealed class NestedContext : INestedContext public int FrameLevel { get; } private int _disposed; - object INestedContext.Value + object? INestedContext.Value { get { - object value = Value; + object? value = Value; #if NET35 || NET40 || NET45 if (value is ObjectHandleSerializer objectHandle) { @@ -205,7 +204,7 @@ void IDisposable.Dispose() public override string ToString() { - object value = Value; + object? value = Value; return value?.ToString() ?? "null"; } } diff --git a/src/NLog/Properties/AssemblyInfo.cs b/src/NLog/Properties/AssemblyInfo.cs index 3fabab2e0a..722c7070dd 100644 --- a/src/NLog/Properties/AssemblyInfo.cs +++ b/src/NLog/Properties/AssemblyInfo.cs @@ -36,16 +36,12 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; -using NLog.Internal.Xamarin; -[assembly: Preserve] // Automatic --linkskip=NLog [assembly: AssemblyCulture("")] [assembly: CLSCompliant(true)] [assembly: ComVisible(false)] [assembly: InternalsVisibleTo("NLog.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ef8eab4fbdeb511eeb475e1659fe53f00ec1c1340700f1aa347bf3438455d71993b28b1efbed44c8d97a989e0cb6f01bcb5e78f0b055d311546f63de0a969e04cf04450f43834db9f909e566545a67e42822036860075a1576e90e1c43d43e023a24c22a427f85592ae56cac26f13b7ec2625cbc01f9490d60f16cfbb1bc34d9")] [assembly: AllowPartiallyTrustedCallers] -#if !NET35 && !NETSTANDARD1_3 && !NETSTANDARD1_5 +#if !NET35 [assembly: SecurityRules(SecurityRuleSet.Level1)] #endif -// NSubstitute -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/NLog/ScopeContext.cs b/src/NLog/ScopeContext.cs index 7bc7363167..ecfbc0c659 100644 --- a/src/NLog/ScopeContext.cs +++ b/src/NLog/ScopeContext.cs @@ -31,13 +31,13 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Collections.Generic; -using System.Linq; -using NLog.Internal; - namespace NLog { + using System; + using System.Collections.Generic; + using System.Linq; + using NLog.Internal; + /// /// stores state in the async thread execution context. All LogEvents created /// within a scope can include the scope state in the target output. The logical context scope supports @@ -61,9 +61,9 @@ public static class ScopeContext /// Properties being added to the scope dictionary /// A disposable object that pops the nested scope state on dispose (including properties). /// Scope dictionary keys are case-insensitive - public static IDisposable PushNestedStateProperties(object nestedState, IReadOnlyCollection> properties) + public static IDisposable PushNestedStateProperties(object? nestedState, IReadOnlyCollection>? properties) { - properties = properties ?? ArrayHelper.Empty>(); + properties = properties ?? ArrayHelper.Empty>(); if (properties.Count > 0 || nestedState is null) { #if !NET45 @@ -74,9 +74,9 @@ public static IDisposable PushNestedStateProperties(object nestedState, IReadOnl if (allProperties != null) { // Collapse all 3 property-scopes into a collapsed scope, and return bookmark that can restore original parent (Avoid huge object-graphs) - ScopeContextPropertyEnumerator.CopyScopePropertiesToDictionary(properties, allProperties); + ScopeContextPropertyEnumerator.CopyScopePropertiesToDictionary(properties, allProperties); - var collapsedState = new ScopeContextPropertiesAsyncState(parent.Parent.Parent, allProperties, nestedState); + var collapsedState = new ScopeContextPropertiesAsyncState(parent?.Parent?.Parent, allProperties, nestedState); SetAsyncLocalContext(collapsedState); return new ScopeContextPropertiesCollapsed(parent, collapsedState); } @@ -105,7 +105,7 @@ public static IDisposable PushNestedStateProperties(object nestedState, IReadOnl /// Properties being added to the scope dictionary /// A disposable object that removes the properties from logical context scope on dispose. /// Scope dictionary keys are case-insensitive - public static IDisposable PushProperties(IReadOnlyCollection> properties) + public static IDisposable PushProperties(IReadOnlyCollection> properties) { return PushProperties(properties); } @@ -116,7 +116,7 @@ public static IDisposable PushProperties(IReadOnlyCollectionProperties being added to the scope dictionary /// A disposable object that removes the properties from logical context scope on dispose. /// Scope dictionary keys are case-insensitive - public static IDisposable PushProperties(IReadOnlyCollection> properties) + public static IDisposable PushProperties(IReadOnlyCollection> properties) { #if !NET45 var parent = GetAsyncLocalContext(); @@ -127,7 +127,7 @@ public static IDisposable PushProperties(IReadOnlyCollection.CopyScopePropertiesToDictionary(properties, allProperties); - var collapsedState = new ScopeContextPropertiesAsyncState(parent.Parent.Parent, allProperties); + var collapsedState = new ScopeContextPropertiesAsyncState(parent?.Parent?.Parent, allProperties); SetAsyncLocalContext(collapsedState); return new ScopeContextPropertiesCollapsed(parent, collapsedState); } @@ -149,8 +149,9 @@ public static IDisposable PushProperties(IReadOnlyCollectionValue of property /// A disposable object that removes the properties from logical context scope on dispose. /// Scope dictionary keys are case-insensitive - public static IDisposable PushProperty(string key, TValue value) + public static IDisposable PushProperty(string key, TValue? value) { + Guard.ThrowIfNull(key); #if !NET35 && !NET40 && !NET45 var parent = GetAsyncLocalContext(); @@ -160,7 +161,7 @@ public static IDisposable PushProperty(string key, TValue value) // Collapse all 3 property-scopes into a collapsed scope, and return bookmark that can restore original parent (Avoid huge object-graphs) allProperties[key] = value; - var collapsedState = new ScopeContextPropertiesAsyncState(parent.Parent.Parent, allProperties); + var collapsedState = new ScopeContextPropertiesAsyncState(parent?.Parent?.Parent, allProperties); SetAsyncLocalContext(collapsedState); return new ScopeContextPropertiesCollapsed(parent, collapsedState); } @@ -181,7 +182,7 @@ public static IDisposable PushProperty(string key, TValue value) /// Value of property /// A disposable object that removes the properties from logical context scope on dispose. /// Scope dictionary keys are case-insensitive - public static IDisposable PushProperty(string key, object value) + public static IDisposable PushProperty(string key, object? value) { return PushProperty(key, value); } @@ -200,7 +201,13 @@ public static IDisposable PushNestedState(T nestedState) SetAsyncLocalContext(current); return current; #else - object objectValue = nestedState; + object? objectValue = nestedState; + if (objectValue is null) + { + var oldContext = GetNestedContextCallContext(); + return new ScopeContextNestedState(oldContext, objectValue); + } + var oldNestedContext = PushNestedStateCallContext(objectValue); return new ScopeContextNestedState(oldNestedContext, objectValue); #endif @@ -233,12 +240,12 @@ public static void Clear() /// Retrieves all properties stored within the logical context scopes /// /// Collection of all properties - public static IEnumerable> GetAllProperties() + public static IEnumerable> GetAllProperties() { #if !NET35 && !NET40 && !NET45 var contextState = GetAsyncLocalContext(); var propertyCollector = new ScopeContextPropertyCollector(); - return contextState?.CaptureContextProperties(ref propertyCollector) ?? ArrayHelper.Empty>(); + return contextState?.CaptureContextProperties(ref propertyCollector) ?? ArrayHelper.Empty>(); #else var mappedContext = GetMappedContextCallContext(); if (mappedContext?.Count > 0) @@ -252,13 +259,13 @@ public static IEnumerable> GetAllProperties() } return mappedContext; } - return ArrayHelper.Empty>(); + return ArrayHelper.Empty>(); #endif } - internal static ScopeContextPropertyEnumerator GetAllPropertiesEnumerator() + internal static ScopeContextPropertyEnumerator GetAllPropertiesEnumerator() { - return new ScopeContextPropertyEnumerator(GetAllProperties()); + return new ScopeContextPropertyEnumerator(GetAllProperties()); } /// @@ -266,9 +273,9 @@ internal static ScopeContextPropertyEnumerator GetAllPropertiesEnumerato /// /// Name of property /// When this method returns, contains the value associated with the specified key - /// Returns true when value is found with the specified key + /// Returns when value is found with the specified key /// Scope dictionary keys are case-insensitive - public static bool TryGetProperty(string key, out object value) + public static bool TryGetProperty(string key, out object? value) { #if !NET35 && !NET40 && !NET45 var contextState = GetAsyncLocalContext(); @@ -350,7 +357,7 @@ internal static IList GetAllNestedStateList() /// Peeks the top value from the logical context scope stack /// /// Value from the top of the stack. - public static object PeekNestedState() + public static object? PeekNestedState() { #if !NET35 && !NET40 && !NET45 var parent = GetAsyncLocalContext(); @@ -426,15 +433,15 @@ public static object PeekNestedState() } #if !NET35 && !NET40 && !NET45 - private static bool TryLookupProperty(IReadOnlyCollection> scopeProperties, string key, out object value) + private static bool TryLookupProperty(IReadOnlyCollection> scopeProperties, string key, out object? value) { - if (scopeProperties is Dictionary mappedDictionary && ReferenceEquals(mappedDictionary.Comparer, DefaultComparer)) + if (scopeProperties is Dictionary mappedDictionary && ReferenceEquals(mappedDictionary.Comparer, DefaultComparer)) { return mappedDictionary.TryGetValue(key, out value); } else { - using (var scopeEnumerator = new ScopeContextPropertyEnumerator(scopeProperties)) + using (var scopeEnumerator = new ScopeContextPropertyEnumerator(scopeProperties)) { while (scopeEnumerator.MoveNext()) { @@ -473,28 +480,28 @@ private static TimeSpan GetNestedStateDuration(long scopeTimestamp, long current /// private sealed class ScopeContextPropertiesCollapsed : IDisposable { - private readonly IScopeContextAsyncState _parent; + private readonly IScopeContextAsyncState? _parent; private readonly IScopeContextPropertiesAsyncState _collapsed; private bool _disposed; - public ScopeContextPropertiesCollapsed(IScopeContextAsyncState parent, IScopeContextPropertiesAsyncState collapsed) + public ScopeContextPropertiesCollapsed(IScopeContextAsyncState? parent, IScopeContextPropertiesAsyncState collapsed) { _parent = parent; _collapsed = collapsed; } - public static Dictionary BuildCollapsedDictionary(IScopeContextAsyncState parent, int initialCapacity) + public static Dictionary? BuildCollapsedDictionary(IScopeContextAsyncState? parent, int initialCapacity) { if (parent is IScopeContextPropertiesAsyncState parentProperties && parentProperties.Parent is IScopeContextPropertiesAsyncState grandParentProperties) { if (parentProperties.NestedState is null && grandParentProperties.NestedState is null) { - var propertyCollectorList = new List>(); // Marks the collector as active + var propertyCollectorList = new List>(); // Marks the collector as active var propertyCollector = new ScopeContextPropertyCollector(propertyCollectorList); var propertyCollection = propertyCollector.StartCaptureProperties(parent); - if (propertyCollectorList.Count > 0 && propertyCollection is Dictionary propertyDictionary) + if (propertyCollectorList.Count > 0 && propertyCollection is Dictionary propertyDictionary) return propertyDictionary; // New property collector was built from the list - propertyDictionary = new Dictionary(propertyCollection.Count + initialCapacity, ScopeContext.DefaultComparer); + propertyDictionary = new Dictionary(propertyCollection.Count + initialCapacity, ScopeContext.DefaultComparer); ScopeContextPropertyEnumerator.CopyScopePropertiesToDictionary(propertyCollection, propertyDictionary); return propertyDictionary; } @@ -518,27 +525,27 @@ public override string ToString() } } - internal static void SetAsyncLocalContext(IScopeContextAsyncState newValue) + internal static void SetAsyncLocalContext(IScopeContextAsyncState? newValue) { AsyncNestedDiagnosticsContext.Value = newValue; } - private static IScopeContextAsyncState GetAsyncLocalContext() + private static IScopeContextAsyncState? GetAsyncLocalContext() { return AsyncNestedDiagnosticsContext.Value; } - private static readonly System.Threading.AsyncLocal AsyncNestedDiagnosticsContext = new System.Threading.AsyncLocal(); + private static readonly System.Threading.AsyncLocal AsyncNestedDiagnosticsContext = new System.Threading.AsyncLocal(); #endif #if NET45 private sealed class ScopeContextNestedStateProperties : IDisposable { - private readonly LinkedList _parentNestedContext; - private readonly Dictionary _parentMappedContext; + private readonly LinkedList? _parentNestedContext; + private readonly Dictionary? _parentMappedContext; - public ScopeContextNestedStateProperties(LinkedList parentNestedContext, Dictionary parentMappedContext) + public ScopeContextNestedStateProperties(LinkedList? parentNestedContext, Dictionary? parentMappedContext) { _parentNestedContext = parentNestedContext; _parentMappedContext = parentMappedContext; @@ -555,7 +562,7 @@ public void Dispose() [Obsolete("Replaced by ScopeContext.PushProperty. Marked obsolete on NLog 5.0")] - internal static void SetMappedContextLegacy(string key, TValue value) + internal static void SetMappedContextLegacy(string key, TValue? value) { #if !NET35 && !NET40 && !NET45 PushProperty(key, value); @@ -611,7 +618,7 @@ internal static void RemoveMappedContextLegacy(string key) } [Obsolete("Replaced by disposing return value from ScopeContext.PushNestedState. Marked obsolete on NLog 5.0")] - internal static object PopNestedContextLegacy() + internal static object? PopNestedContextLegacy() { #if !NET35 && !NET40 && !NET45 var contextState = GetAsyncLocalContext(); @@ -627,7 +634,7 @@ internal static object PopNestedContextLegacy() // Replace with new legacy-scope, the legacy-scope can be discarded when previous parent scope is restored var propertyCollector = new ScopeContextPropertyCollector(); var stackTopValue = nestedStates[0]; - var allProperties = contextState.CaptureContextProperties(ref propertyCollector) ?? ArrayHelper.Empty>(); + var allProperties = contextState.CaptureContextProperties(ref propertyCollector) ?? ArrayHelper.Empty>(); var nestedContext = ArrayHelper.Empty(); if (nestedStates.Count > 1) { @@ -720,7 +727,7 @@ internal static void ClearNestedContextLegacy() #if NET35 || NET40 || NET45 #if !NET35 && !NET40 - private static Dictionary PushPropertiesCallContext(IReadOnlyCollection> properties) + private static Dictionary? PushPropertiesCallContext(IReadOnlyCollection> properties) { var oldContext = GetMappedContextCallContext(); var newContext = CloneMappedContext(oldContext, properties.Count); @@ -737,7 +744,7 @@ private static Dictionary PushPropertiesCallContext(IRea } #endif - private static Dictionary PushPropertyCallContext(string propertyName, TValue propertyValue) + private static Dictionary? PushPropertyCallContext(string propertyName, TValue propertyValue) { var oldContext = GetMappedContextCallContext(); var newContext = CloneMappedContext(oldContext, 1); @@ -751,13 +758,13 @@ private static void ClearMappedContextCallContext() SetMappedContextCallContext(null); } - private static IEnumerable> GetAllPropertiesUnwrapped(Dictionary properties) + private static IEnumerable> GetAllPropertiesUnwrapped(Dictionary properties) { foreach (var item in properties) { if (item.Value is ObjectHandleSerializer objectHandle) { - yield return new KeyValuePair(item.Key, objectHandle.Unwrap()); + yield return new KeyValuePair(item.Key, objectHandle.Unwrap()); } else { @@ -766,23 +773,23 @@ private static IEnumerable> GetAllPropertiesUnwrapp } } - private static Dictionary CloneMappedContext(Dictionary oldContext, int initialCapacity = 0) + private static Dictionary CloneMappedContext(Dictionary? oldContext, int initialCapacity = 0) { if (oldContext?.Count > 0) { - var dictionary = new Dictionary(oldContext.Count + initialCapacity, DefaultComparer); + var dictionary = new Dictionary(oldContext.Count + initialCapacity, DefaultComparer); foreach (var keyValue in oldContext) dictionary[keyValue.Key] = keyValue.Value; return dictionary; } - return new Dictionary(initialCapacity, DefaultComparer); + return new Dictionary(initialCapacity, DefaultComparer); } - private static void SetPropertyCallContext(string item, TValue value, IDictionary mappedContext) + private static void SetPropertyCallContext(string item, TValue? value, IDictionary mappedContext) { - object objectValue = value; - if (Convert.GetTypeCode(objectValue) != TypeCode.Object) + object? objectValue = value; + if (objectValue is null || Convert.GetTypeCode(objectValue) != TypeCode.Object) mappedContext[item] = objectValue; else mappedContext[item] = new ObjectHandleSerializer(objectValue); @@ -790,10 +797,10 @@ private static void SetPropertyCallContext(string item, TValue value, ID private sealed class ScopeContextProperties : IDisposable { - private readonly Dictionary _oldContext; + private readonly Dictionary? _oldContext; private bool _diposed; - public ScopeContextProperties(Dictionary oldContext) + public ScopeContextProperties(Dictionary? oldContext) { _oldContext = oldContext; } @@ -808,7 +815,7 @@ public void Dispose() } } - private static void SetMappedContextCallContext(Dictionary newValue) + private static void SetMappedContextCallContext(Dictionary? newValue) { if (newValue is null) System.Runtime.Remoting.Messaging.CallContext.FreeNamedDataSlot(MappedContextDataSlotName); @@ -816,14 +823,14 @@ private static void SetMappedContextCallContext(Dictionary newVa System.Runtime.Remoting.Messaging.CallContext.LogicalSetData(MappedContextDataSlotName, newValue); } - internal static Dictionary GetMappedContextCallContext() + internal static Dictionary? GetMappedContextCallContext() { - return System.Runtime.Remoting.Messaging.CallContext.LogicalGetData(MappedContextDataSlotName) as Dictionary; + return System.Runtime.Remoting.Messaging.CallContext.LogicalGetData(MappedContextDataSlotName) as Dictionary; } private const string MappedContextDataSlotName = "NLog.AsyncableMappedDiagnosticsContext"; - private static LinkedList PushNestedStateCallContext(object objectValue) + private static LinkedList? PushNestedStateCallContext(object objectValue) { var oldContext = GetNestedContextCallContext(); var newContext = oldContext?.Count > 0 ? new LinkedList(oldContext) : new LinkedList(); @@ -841,11 +848,11 @@ private static void ClearNestedContextCallContext() private sealed class ScopeContextNestedState : IDisposable { - private readonly LinkedList _oldContext; - private readonly object _nestedState; + private readonly LinkedList? _oldContext; + private readonly object? _nestedState; private bool _diposed; - public ScopeContextNestedState(LinkedList oldContext, object nestedState) + public ScopeContextNestedState(LinkedList? oldContext, object? nestedState) { _oldContext = oldContext; _nestedState = nestedState; @@ -867,7 +874,7 @@ public override string ToString() } [System.Security.SecuritySafeCriticalAttribute] - private static void SetNestedContextCallContext(LinkedList nestedContext) + private static void SetNestedContextCallContext(LinkedList? nestedContext) { if (nestedContext is null) System.Runtime.Remoting.Messaging.CallContext.FreeNamedDataSlot(NestedContextDataSlotName); @@ -877,7 +884,7 @@ private static void SetNestedContextCallContext(LinkedList nestedContext [System.Security.SecuritySafeCriticalAttribute] - private static LinkedList GetNestedContextCallContext() + private static LinkedList? GetNestedContextCallContext() { return System.Runtime.Remoting.Messaging.CallContext.LogicalGetData(NestedContextDataSlotName) as LinkedList; } diff --git a/src/NLog/SetupBuilderExtensions.cs b/src/NLog/SetupBuilderExtensions.cs index 01de2e570a..b941dcc8d2 100644 --- a/src/NLog/SetupBuilderExtensions.cs +++ b/src/NLog/SetupBuilderExtensions.cs @@ -35,8 +35,10 @@ namespace NLog { using System; using System.Collections.Generic; + using System.IO; using System.Linq; using System.Runtime.CompilerServices; + using NLog.Common; using NLog.Config; using NLog.Internal; @@ -51,7 +53,8 @@ public static class SetupBuilderExtensions [MethodImpl(MethodImplOptions.NoInlining)] public static Logger GetCurrentClassLogger(this ISetupBuilder setupBuilder) { - return setupBuilder.LogFactory.GetLogger(StackTraceUsageUtils.GetClassFullName()); + var className = StackTraceUsageUtils.GetClassFullName(new System.Diagnostics.StackFrame(1, false)); + return setupBuilder.LogFactory.GetLogger(className); } /// @@ -140,7 +143,7 @@ public static ISetupBuilder LoadConfiguration(this ISetupBuilder setupBuilder, L /// Fluent interface parameter. /// Explicit configuration file to be read (Default NLog.config from candidates paths) /// Whether to allow application to run when NLog config is not available - public static ISetupBuilder LoadConfigurationFromFile(this ISetupBuilder setupBuilder, string configFile = null, bool optional = true) + public static ISetupBuilder LoadConfigurationFromFile(this ISetupBuilder setupBuilder, string? configFile = null, bool optional = true) { setupBuilder.LogFactory.LoadConfiguration(configFile, optional); return setupBuilder; @@ -205,24 +208,24 @@ public static ISetupBuilder LoadConfigurationFromAssemblyResource(this ISetupBui var nlogConfigStream = applicationAssembly.GetManifestResourceStream(resourcePaths[0]); if (nlogConfigStream?.Length > 0) { - NLog.Common.InternalLogger.Info("Loading NLog XML config from assembly embedded resource '{0}'", resourceName); - using (var xmlReader = System.Xml.XmlReader.Create(nlogConfigStream)) + InternalLogger.Info("Loading NLog XML config from assembly embedded resource '{0}'", resourceName); + using (var streamReader = new StreamReader(nlogConfigStream)) { - setupBuilder.LoadConfiguration(new XmlLoggingConfiguration(xmlReader, null, setupBuilder.LogFactory)); + setupBuilder.LoadConfiguration(new XmlLoggingConfiguration(streamReader, null, setupBuilder.LogFactory)); } } else { - NLog.Common.InternalLogger.Debug("No NLog config loaded. Empty Embedded resource '{0}' found in assembly: {1}", resourceName, applicationAssembly.FullName); + InternalLogger.Debug("No NLog config loaded. Empty Embedded resource '{0}' found in assembly: {1}", resourceName, applicationAssembly.FullName); } } else if (resourcePaths.Count == 0) { - NLog.Common.InternalLogger.Debug("No NLog config loaded. No matching embedded resource '{0}' found in assembly: {1}", resourceName, applicationAssembly.FullName); + InternalLogger.Debug("No NLog config loaded. No matching embedded resource '{0}' found in assembly: {1}", resourceName, applicationAssembly.FullName); } else { - NLog.Common.InternalLogger.Error("No NLog config loaded. Multiple matching embedded resource '{0}' found in assembly: {1}", resourceName, applicationAssembly.FullName); + InternalLogger.Error("No NLog config loaded. Multiple matching embedded resource '{0}' found in assembly: {1}", resourceName, applicationAssembly.FullName); } return setupBuilder; } @@ -233,12 +236,36 @@ public static ISetupBuilder LoadConfigurationFromAssemblyResource(this ISetupBui /// Logevents produced during the configuration-reload can become lost, as targets are unavailable while closing and initializing. public static ISetupBuilder ReloadConfiguration(this ISetupBuilder setupBuilder) { - var newConfig = setupBuilder.LogFactory._config?.Reload(); - if (newConfig is null || (newConfig as IInitializeSucceeded)?.InitializeSucceeded == false) + try + { + InternalLogger.Debug("Reloading NLog LoggingConfiguration"); + var newConfig = setupBuilder.LogFactory._config?.Reload(); + if (newConfig is null) + return setupBuilder; + + setupBuilder.LogFactory.Configuration = newConfig; return setupBuilder; + } + catch (NLogConfigurationException ex) + { + InternalLogger.Error(ex, "Failed to reload NLog LoggingConfiguration"); + if (ex.MustBeRethrown() || (setupBuilder.LogFactory.ThrowConfigExceptions ?? setupBuilder.LogFactory.ThrowExceptions)) + { + throw; + } - setupBuilder.LogFactory.Configuration = newConfig; - return setupBuilder; + return setupBuilder; + } + catch (Exception ex) + { + InternalLogger.Error(ex, "Failed to reload NLog LoggingConfiguration"); + if (ex.MustBeRethrownImmediately()) + { + throw; + } + + return setupBuilder; + } } } } diff --git a/src/NLog/SetupExtensionsBuilderExtensions.cs b/src/NLog/SetupExtensionsBuilderExtensions.cs index 7c8bda9e84..b2d32d6b61 100644 --- a/src/NLog/SetupExtensionsBuilderExtensions.cs +++ b/src/NLog/SetupExtensionsBuilderExtensions.cs @@ -80,9 +80,7 @@ public static ISetupExtensionsBuilder AutoLoadExtensions(this ISetupExtensionsBu /// public static ISetupExtensionsBuilder RegisterAssembly(this ISetupExtensionsBuilder setupBuilder, Assembly assembly) { -#pragma warning disable CS0618 // Type or member is obsolete - ConfigurationItemFactory.Default.RegisterItemsFromAssembly(assembly); -#pragma warning restore CS0618 // Type or member is obsolete + ConfigurationItemFactory.Default.AssemblyLoader.LoadAssembly(ConfigurationItemFactory.Default, assembly, string.Empty); return setupBuilder; } @@ -113,7 +111,7 @@ public static ISetupExtensionsBuilder RegisterAssembly(this ISetupExtensionsBuil /// Type of the Target. /// Fluent interface parameter. /// The target type-alias for use in NLog configuration. Will extract from class-attribute when unassigned. - public static ISetupExtensionsBuilder RegisterTarget<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, string name = null) + public static ISetupExtensionsBuilder RegisterTarget<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, string? name = null) where T : Target, new() { return RegisterTarget(setupBuilder, () => new T(), name); @@ -126,11 +124,11 @@ public static ISetupExtensionsBuilder RegisterAssembly(this ISetupExtensionsBuil /// Fluent interface parameter. /// The factory method for creating instance of NLog Target /// The target type-alias for use in NLog configuration. Will extract from class-attribute when unassigned. - public static ISetupExtensionsBuilder RegisterTarget<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, Func factory, string typeAlias = null) + public static ISetupExtensionsBuilder RegisterTarget<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, Func factory, string? typeAlias = null) where T : Target { typeAlias = string.IsNullOrEmpty(typeAlias) ? typeof(T).GetFirstCustomAttribute()?.Name : typeAlias; - if (string.IsNullOrEmpty(typeAlias)) + if (typeAlias is null || string.IsNullOrEmpty(typeAlias)) { typeAlias = ResolveTypeAlias("TargetWrapper", "Target"); if (typeof(NLog.Targets.Wrappers.WrapperTargetBase).IsAssignableFrom(typeof(T)) && !typeAlias.EndsWith("Wrapper", StringComparison.OrdinalIgnoreCase)) @@ -165,7 +163,7 @@ public static ISetupExtensionsBuilder RegisterTarget(this ISetupExtensionsBuilde /// Type of the layout renderer. /// Fluent interface parameter. /// The layout type-alias for use in NLog configuration. Will extract from class-attribute when unassigned. - public static ISetupExtensionsBuilder RegisterLayout<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, string typeAlias = null) + public static ISetupExtensionsBuilder RegisterLayout<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, string? typeAlias = null) where T : Layout, new() { return RegisterLayout(setupBuilder, () => new T(), typeAlias); @@ -178,10 +176,10 @@ public static ISetupExtensionsBuilder RegisterTarget(this ISetupExtensionsBuilde /// Fluent interface parameter. /// The factory method for creating instance of NLog Layout /// The layout type-alias for use in NLog configuration. Will extract from class-attribute when unassigned. - public static ISetupExtensionsBuilder RegisterLayout<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, Func factory, string typeAlias = null) + public static ISetupExtensionsBuilder RegisterLayout<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, Func factory, string? typeAlias = null) where T : Layout { - typeAlias = string.IsNullOrEmpty(typeAlias) ? ResolveTypeAlias(ArrayHelper.Empty()) : typeAlias; + typeAlias = (typeAlias is null || string.IsNullOrEmpty(typeAlias)) ? ResolveTypeAlias(ArrayHelper.Empty()) : typeAlias; ConfigurationItemFactory.Default.GetLayoutFactory().RegisterType(typeAlias, factory); return setupBuilder; } @@ -209,7 +207,7 @@ public static ISetupExtensionsBuilder RegisterLayout(this ISetupExtensionsBuilde /// Type of the layout renderer. /// Fluent interface parameter. /// The layout-renderer type-alias for use in NLog configuration - without '${ }'. Will extract from class-attribute when unassigned. - public static ISetupExtensionsBuilder RegisterLayoutRenderer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, string name = null) + public static ISetupExtensionsBuilder RegisterLayoutRenderer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, string? name = null) where T : LayoutRenderer, new() { return RegisterLayoutRenderer(setupBuilder, () => new T(), name); @@ -222,10 +220,10 @@ public static ISetupExtensionsBuilder RegisterLayout(this ISetupExtensionsBuilde /// Fluent interface parameter. /// The factory method for creating instance of NLog LayoutRenderer /// The layout-renderer type-alias for use in NLog configuration - without '${ }'. Will extract from class-attribute when unassigned. - public static ISetupExtensionsBuilder RegisterLayoutRenderer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, Func factory, string typeAlias = null) + public static ISetupExtensionsBuilder RegisterLayoutRenderer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupExtensionsBuilder setupBuilder, Func factory, string? typeAlias = null) where T : LayoutRenderer { - typeAlias = string.IsNullOrEmpty(typeAlias) ? ResolveTypeAlias("LayoutRendererWrapper", "LayoutRenderer") : typeAlias; + typeAlias = (typeAlias is null || string.IsNullOrEmpty(typeAlias)) ? ResolveTypeAlias("LayoutRendererWrapper", "LayoutRenderer") : typeAlias; ConfigurationItemFactory.Default.GetLayoutRendererFactory().RegisterType(typeAlias, factory); return setupBuilder; } @@ -233,10 +231,10 @@ public static ISetupExtensionsBuilder RegisterLayout(this ISetupExtensionsBuilde private static string ResolveTypeAlias(params string[] trimEndings) where TNameAttribute : NameBaseAttribute { var typeAlias = typeof(T).GetFirstCustomAttribute()?.Name; - if (!string.IsNullOrEmpty(typeAlias)) + if (typeAlias is null || string.IsNullOrEmpty(typeAlias)) + return ResolveTypeAlias(trimEndings); + else return typeAlias; - - return ResolveTypeAlias(trimEndings); } private static string ResolveTypeAlias(params string[] trimEndings) @@ -248,7 +246,7 @@ private static string ResolveTypeAlias(params string[] trimEndings) int endingPosition = typeAlias.IndexOf(ending, StringComparison.OrdinalIgnoreCase); if (endingPosition > 0) { - return typeAlias.Substring(endingPosition); + return typeAlias.Substring(0, endingPosition); } } @@ -289,7 +287,7 @@ public static ISetupExtensionsBuilder RegisterLayoutRenderer(this ISetupExtensio /// Fluent interface parameter. /// The layout-renderer type-alias for use in NLog configuration - without '${ }' /// Callback that returns the value for the layout renderer. - public static ISetupExtensionsBuilder RegisterLayoutRenderer(this ISetupExtensionsBuilder setupBuilder, string name, Func layoutMethod) + public static ISetupExtensionsBuilder RegisterLayoutRenderer(this ISetupExtensionsBuilder setupBuilder, string name, Func layoutMethod) { return RegisterLayoutRenderer(setupBuilder, name, layoutMethod, LayoutRenderOptions.None); } @@ -313,7 +311,7 @@ public static ISetupExtensionsBuilder RegisterLayoutRenderer(this ISetupExtensio /// The layout-renderer type-alias for use in NLog configuration - without '${ }' /// Callback that returns the value for the layout renderer. /// Whether method is ThreadAgnostic and doesn't depend on context of the logging application thread. - public static ISetupExtensionsBuilder RegisterLayoutRenderer(this ISetupExtensionsBuilder setupBuilder, string name, Func layoutMethod, LayoutRenderOptions options) + public static ISetupExtensionsBuilder RegisterLayoutRenderer(this ISetupExtensionsBuilder setupBuilder, string name, Func layoutMethod, LayoutRenderOptions options) { FuncLayoutRenderer layoutRenderer = Layout.CreateFuncLayoutRenderer(layoutMethod, options, name); return setupBuilder.RegisterLayoutRenderer(layoutRenderer); @@ -344,7 +342,7 @@ public static ISetupExtensionsBuilder RegisterConditionMethod(this ISetupExtensi if (!conditionMethod.IsStatic) throw new ArgumentException($"{conditionMethod.Name} must be static", nameof(conditionMethod)); - ConfigurationItemFactory.Default.ConditionMethodFactory.RegisterDefinition(name, conditionMethod); + ConfigurationItemFactory.Default.GetConditionMethodFactory().RegisterDefinition(name, conditionMethod); return setupBuilder; } @@ -357,7 +355,7 @@ public static ISetupExtensionsBuilder RegisterConditionMethod(this ISetupExtensi public static ISetupExtensionsBuilder RegisterConditionMethod(this ISetupExtensionsBuilder setupBuilder, string name, Func conditionMethod) { Guard.ThrowIfNull(conditionMethod); - ConfigurationItemFactory.Default.ConditionMethodFactory.RegisterNoParameters(name, (logEvent) => conditionMethod(logEvent)); + ConfigurationItemFactory.Default.GetConditionMethodFactory().RegisterNoParameters(name, (logEvent) => conditionMethod(logEvent)); return setupBuilder; } @@ -370,7 +368,7 @@ public static ISetupExtensionsBuilder RegisterConditionMethod(this ISetupExtensi public static ISetupExtensionsBuilder RegisterConditionMethod(this ISetupExtensionsBuilder setupBuilder, string name, Func conditionMethod) { Guard.ThrowIfNull(conditionMethod); - ConfigurationItemFactory.Default.ConditionMethodFactory.RegisterNoParameters(name, (logEvent) => conditionMethod()); + ConfigurationItemFactory.Default.GetConditionMethodFactory().RegisterNoParameters(name, (logEvent) => conditionMethod()); return setupBuilder; } diff --git a/src/NLog/SetupInternalLoggerBuilderExtensions.cs b/src/NLog/SetupInternalLoggerBuilderExtensions.cs index 64db149807..2491d312f0 100644 --- a/src/NLog/SetupInternalLoggerBuilderExtensions.cs +++ b/src/NLog/SetupInternalLoggerBuilderExtensions.cs @@ -34,10 +34,11 @@ namespace NLog { using System; - using System.ComponentModel; + using System.Globalization; using System.IO; using NLog.Common; using NLog.Config; + using NLog.Internal; /// /// Extension methods to setup NLog options @@ -80,19 +81,6 @@ public static ISetupInternalLoggerBuilder LogToConsole(this ISetupInternalLogger return setupBuilder; } -#if !NETSTANDARD1_3 - /// - /// Configures - /// - public static ISetupInternalLoggerBuilder LogToTrace(this ISetupInternalLoggerBuilder setupBuilder, bool enabled) - { -#pragma warning disable CS0618 // Type or member is obsolete - InternalLogger.LogToTrace = enabled; -#pragma warning restore CS0618 // Type or member is obsolete - return setupBuilder; - } -#endif - /// /// Configures /// @@ -103,42 +91,29 @@ public static ISetupInternalLoggerBuilder LogToWriter(this ISetupInternalLoggerB } /// - /// Configures - /// - [Obsolete("Instead use AddEventSubscription. Marked obsolete with NLog v5.3")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static ISetupInternalLoggerBuilder AddLogSubscription(this ISetupInternalLoggerBuilder setupBuilder, EventHandler eventSubscriber) - { - InternalLogger.LogMessageReceived += eventSubscriber; - return setupBuilder; - } - - /// - /// Configures + /// Configures /// - [Obsolete("Instead use RemoveEventSubscription. Marked obsolete with NLog v5.3")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static ISetupInternalLoggerBuilder RemoveLogSubscription(this ISetupInternalLoggerBuilder setupBuilder, EventHandler eventSubscriber) + public static ISetupInternalLoggerBuilder AddEventSubscription(this ISetupInternalLoggerBuilder setupBuilder, InternalEventOccurredHandler eventSubscriber) { - InternalLogger.LogMessageReceived -= eventSubscriber; + InternalLogger.InternalEventOccurred += eventSubscriber; return setupBuilder; } /// /// Configures /// - public static ISetupInternalLoggerBuilder AddEventSubscription(this ISetupInternalLoggerBuilder setupBuilder, InternalEventOccurredHandler eventSubscriber) + public static ISetupInternalLoggerBuilder RemoveEventSubscription(this ISetupInternalLoggerBuilder setupBuilder, InternalEventOccurredHandler eventSubscriber) { - InternalLogger.InternalEventOccurred += eventSubscriber; + InternalLogger.InternalEventOccurred -= eventSubscriber; return setupBuilder; } /// - /// Configures + /// Resets the InternalLogger configuration without resolving default values from Environment-variables or App.config /// - public static ISetupInternalLoggerBuilder RemoveEventSubscription(this ISetupInternalLoggerBuilder setupBuilder, InternalEventOccurredHandler eventSubscriber) + public static ISetupInternalLoggerBuilder ResetConfig(this ISetupInternalLoggerBuilder setupBuilder) { - InternalLogger.InternalEventOccurred -= eventSubscriber; + InternalLogger.Reset(); return setupBuilder; } @@ -152,7 +127,6 @@ public static ISetupInternalLoggerBuilder RemoveEventSubscription(this ISetupInt /// - NLOG_INTERNAL_LOG_FILE /// - NLOG_INTERNAL_LOG_TO_CONSOLE /// - NLOG_INTERNAL_LOG_TO_CONSOLE_ERROR - /// - NLOG_INTERNAL_LOG_TO_TRACE /// - NLOG_INTERNAL_INCLUDE_TIMESTAMP /// /// Legacy .NetFramework platform will also recognizes the following app.config settings: @@ -161,13 +135,113 @@ public static ISetupInternalLoggerBuilder RemoveEventSubscription(this ISetupInt /// - nlog.internalLogFile /// - nlog.internalLogToConsole /// - nlog.internalLogToConsoleError - /// - nlog.internalLogToTrace /// - nlog.internalLogIncludeTimestamp /// public static ISetupInternalLoggerBuilder SetupFromEnvironmentVariables(this ISetupInternalLoggerBuilder setupBuilder) { - InternalLogger.Reset(); + InternalLogger.LogLevel = GetSetting("nlog.internalLogLevel", "NLOG_INTERNAL_LOG_LEVEL", LogLevel.Off); + InternalLogger.IncludeTimestamp = GetSetting("nlog.internalLogIncludeTimestamp", "NLOG_INTERNAL_INCLUDE_TIMESTAMP", true); + InternalLogger.LogToConsole = GetSetting("nlog.internalLogToConsole", "NLOG_INTERNAL_LOG_TO_CONSOLE", false); + InternalLogger.LogToConsoleError = GetSetting("nlog.internalLogToConsoleError", "NLOG_INTERNAL_LOG_TO_CONSOLE_ERROR", false); + InternalLogger.LogFile = GetSetting("nlog.internalLogFile", "NLOG_INTERNAL_LOG_FILE", string.Empty); return setupBuilder; } + + private static string? GetAppSettings(string configName) + { +#if NETFRAMEWORK + try + { + return System.Configuration.ConfigurationManager.AppSettings[configName]; + } + catch (Exception ex) + { + if (ex.MustBeRethrownImmediately()) + { + throw; + } + } +#endif + return null; + } + + private static string? GetSettingString(string configName, string envName) + { + try + { + var settingValue = GetAppSettings(configName); + if (settingValue != null) + return settingValue; + } + catch (Exception ex) + { + if (ex.MustBeRethrownImmediately()) + { + throw; + } + } + + try + { + string settingValue = EnvironmentHelper.GetSafeEnvironmentVariable(envName); + if (!string.IsNullOrEmpty(settingValue)) + return settingValue; + } + catch (Exception ex) + { + if (ex.MustBeRethrownImmediately()) + { + throw; + } + } + + return null; + } + + private static LogLevel GetSetting(string configName, string envName, LogLevel defaultValue) + { + var value = GetSettingString(configName, envName); + if (value is null) + { + return defaultValue; + } + + try + { + return LogLevel.FromString(value); + } + catch (Exception exception) + { + if (exception.MustBeRethrownImmediately()) + { + throw; + } + + return defaultValue; + } + } + + private static T GetSetting(string configName, string envName, T defaultValue) + { + var value = GetSettingString(configName, envName); + if (value is null) + { + return defaultValue; + } + + try + { + return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + if (exception.MustBeRethrownImmediately()) + { + throw; + } + + return defaultValue; + } + } } } diff --git a/src/NLog/SetupLoadConfigurationExtensions.cs b/src/NLog/SetupLoadConfigurationExtensions.cs index 179b689467..0c590b045e 100644 --- a/src/NLog/SetupLoadConfigurationExtensions.cs +++ b/src/NLog/SetupLoadConfigurationExtensions.cs @@ -75,7 +75,7 @@ public static ISetupLoadConfigurationBuilder SetGlobalContextProperty(this ISetu /// Fluent interface parameter. /// Logger name pattern to check which names matches this rule /// Rule identifier to allow rule lookup - public static ISetupConfigurationLoggingRuleBuilder ForLogger(this ISetupLoadConfigurationBuilder configBuilder, string loggerNamePattern = "*", string ruleName = null) + public static ISetupConfigurationLoggingRuleBuilder ForLogger(this ISetupLoadConfigurationBuilder configBuilder, string loggerNamePattern = "*", string? ruleName = null) { var ruleBuilder = new SetupConfigurationLoggingRuleBuilder(configBuilder.LogFactory, configBuilder.Configuration, loggerNamePattern, ruleName); ruleBuilder.LoggingRule.EnableLoggingForLevels(LogLevel.MinLevel, LogLevel.MaxLevel); @@ -86,13 +86,13 @@ public static ISetupConfigurationLoggingRuleBuilder ForLogger(this ISetupLoadCon /// Defines for redirecting output from matching to wanted targets. /// /// Fluent interface parameter. - /// Restrict minimum LogLevel for names that matches this rule + /// Restrict minimum LogLevel for names that matches this rule /// Logger name pattern to check which names matches this rule /// Rule identifier to allow rule lookup - public static ISetupConfigurationLoggingRuleBuilder ForLogger(this ISetupLoadConfigurationBuilder configBuilder, LogLevel finalMinLevel, string loggerNamePattern = "*", string ruleName = null) + public static ISetupConfigurationLoggingRuleBuilder ForLogger(this ISetupLoadConfigurationBuilder configBuilder, LogLevel minLevel, string loggerNamePattern = "*", string? ruleName = null) { var ruleBuilder = new SetupConfigurationLoggingRuleBuilder(configBuilder.LogFactory, configBuilder.Configuration, loggerNamePattern, ruleName); - ruleBuilder.LoggingRule.EnableLoggingForLevels(finalMinLevel ?? LogLevel.MinLevel, LogLevel.MaxLevel); + ruleBuilder.LoggingRule.EnableLoggingForLevels(minLevel ?? LogLevel.MinLevel, LogLevel.MaxLevel); return ruleBuilder; } @@ -101,7 +101,7 @@ public static ISetupConfigurationLoggingRuleBuilder ForLogger(this ISetupLoadCon /// /// Fluent interface parameter. /// Override the name for the target created - public static ISetupConfigurationTargetBuilder ForTarget(this ISetupLoadConfigurationBuilder configBuilder, string targetName = null) + public static ISetupConfigurationTargetBuilder ForTarget(this ISetupLoadConfigurationBuilder configBuilder, string? targetName = null) { var ruleBuilder = new SetupConfigurationTargetBuilder(configBuilder.LogFactory, configBuilder.Configuration, targetName); return ruleBuilder; @@ -318,7 +318,7 @@ public static ISetupConfigurationTargetBuilder WriteTo(this ISetupConfigurationT /// /// Fluent interface parameter. /// Only discard output from matching Logger when below minimum LogLevel - public static void WriteToNil(this ISetupConfigurationLoggingRuleBuilder configBuilder, LogLevel finalMinLevel = null) + public static void WriteToNil(this ISetupConfigurationLoggingRuleBuilder configBuilder, LogLevel? finalMinLevel = null) { var loggingRule = configBuilder.LoggingRule; if (finalMinLevel != null) @@ -398,9 +398,10 @@ public static T FirstTarget(this ISetupConfigurationTargetBuilder configBuild throw new InvalidCastException($"Unable to cast object of type '{target.GetType()}' to type '{typeof(T)}'"); } - internal static IEnumerable YieldAllTargets(Target target) + internal static IEnumerable YieldAllTargets(Target? target) { - yield return target; + if (target is not null) + yield return target; if (target is WrapperTargetBase wrapperTarget) { @@ -423,7 +424,7 @@ internal static IEnumerable YieldAllTargets(Target target) /// Fluent interface parameter. /// Method to call on logevent /// Layouts to render object[]-args before calling - public static ISetupConfigurationTargetBuilder WriteToMethodCall(this ISetupConfigurationTargetBuilder configBuilder, Action logEventAction, Layout[] layouts = null) + public static ISetupConfigurationTargetBuilder WriteToMethodCall(this ISetupConfigurationTargetBuilder configBuilder, Action logEventAction, Layout[]? layouts = null) { Guard.ThrowIfNull(logEventAction); @@ -437,7 +438,6 @@ public static ISetupConfigurationTargetBuilder WriteToMethodCall(this ISetupConf return configBuilder.WriteTo(methodTarget); } -#if !NETSTANDARD1_3 /// /// Write to /// @@ -446,17 +446,18 @@ public static ISetupConfigurationTargetBuilder WriteToMethodCall(this ISetupConf /// Override the default Encoding for output (Ex. UTF8) /// Write to stderr instead of standard output (stdout) /// Skip overhead from writing to console, when not available (Ex. running as Windows Service) - /// Enable batch writing of logevents, instead of Console.WriteLine for each logevent (Requires ) - public static ISetupConfigurationTargetBuilder WriteToConsole(this ISetupConfigurationTargetBuilder configBuilder, Layout layout = null, System.Text.Encoding encoding = null, bool stderr = false, bool detectConsoleAvailable = false, bool writeBuffered = false) + /// Force Console.WriteLine (slower) instead of Console.WriteBuffer (faster) + public static ISetupConfigurationTargetBuilder WriteToConsole(this ISetupConfigurationTargetBuilder configBuilder, Layout? layout = null, System.Text.Encoding? encoding = null, bool stderr = false, bool detectConsoleAvailable = false, bool forceWriteLine = false) { var consoleTarget = new ConsoleTarget(); if (layout != null) consoleTarget.Layout = layout; if (encoding != null) consoleTarget.Encoding = encoding; - consoleTarget.StdErr = stderr; + if (stderr) + consoleTarget.StdErr = stderr; consoleTarget.DetectConsoleAvailable = detectConsoleAvailable; - consoleTarget.WriteBuffer = writeBuffered; + consoleTarget.ForceWriteLine = forceWriteLine; return configBuilder.WriteTo(consoleTarget); } @@ -470,7 +471,7 @@ public static ISetupConfigurationTargetBuilder WriteToConsole(this ISetupConfigu /// Write to stderr instead of standard output (stdout) /// Skip overhead from writing to console, when not available (Ex. running as Windows Service) /// Enables output using ANSI Color Codes (Windows console does not support this by default) - public static ISetupConfigurationTargetBuilder WriteToColoredConsole(this ISetupConfigurationTargetBuilder configBuilder, Layout layout = null, bool highlightWordLevel = false, System.Text.Encoding encoding = null, bool stderr = false, bool detectConsoleAvailable = false, bool enableAnsiOutput = false) + public static ISetupConfigurationTargetBuilder WriteToColoredConsole(this ISetupConfigurationTargetBuilder configBuilder, Layout? layout = null, bool highlightWordLevel = false, System.Text.Encoding? encoding = null, bool stderr = false, bool detectConsoleAvailable = false, bool enableAnsiOutput = false) { var consoleTarget = new ColoredConsoleTarget(); if (layout != null) @@ -496,9 +497,8 @@ public static ISetupConfigurationTargetBuilder WriteToColoredConsole(this ISetup } else { - consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule(conditionLogLevelFatal, ConsoleOutputColor.DarkRed, ConsoleOutputColor.NoChange)); - consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule(conditionLogLevelError, ConsoleOutputColor.DarkRed, ConsoleOutputColor.NoChange)); - consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule(conditionLogLevelWarn, ConsoleOutputColor.DarkYellow, ConsoleOutputColor.NoChange)); + foreach (var defaultRowHighlight in new ColoredConsoleAnsiPrinter().DefaultConsoleRowHighlightingRules) + consoleTarget.RowHighlightingRules.Add(defaultRowHighlight); } } else @@ -511,40 +511,20 @@ public static ISetupConfigurationTargetBuilder WriteToColoredConsole(this ISetup } else { - consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule(conditionLogLevelFatal, ConsoleOutputColor.Red, ConsoleOutputColor.NoChange)); - consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule(conditionLogLevelError, ConsoleOutputColor.Red, ConsoleOutputColor.NoChange)); - consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule(conditionLogLevelWarn, ConsoleOutputColor.Yellow, ConsoleOutputColor.NoChange)); - consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule(ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Info", (evt) => evt.Level == LogLevel.Info), ConsoleOutputColor.White, ConsoleOutputColor.NoChange)); - consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule(ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Debug", (evt) => evt.Level == LogLevel.Debug), ConsoleOutputColor.Gray, ConsoleOutputColor.NoChange)); - consoleTarget.RowHighlightingRules.Add(new ConsoleRowHighlightingRule(ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Trace", (evt) => evt.Level == LogLevel.Trace), ConsoleOutputColor.Gray, ConsoleOutputColor.NoChange)); + foreach (var defaultRowHighlight in new ColoredConsoleSystemPrinter().DefaultConsoleRowHighlightingRules) + consoleTarget.RowHighlightingRules.Add(defaultRowHighlight); } } return configBuilder.WriteTo(consoleTarget); } - /// - /// Write to - /// - /// - /// Override the default Layout for output - /// Force use independent of - public static ISetupConfigurationTargetBuilder WriteToTrace(this ISetupConfigurationTargetBuilder configBuilder, Layout layout = null, bool rawWrite = true) - { - var traceTarget = new TraceTarget(); - traceTarget.RawWrite = rawWrite; - if (layout != null) - traceTarget.Layout = layout; - return configBuilder.WriteTo(traceTarget); - } -#endif - /// /// Write to /// /// /// Override the default Layout for output - public static ISetupConfigurationTargetBuilder WriteToDebug(this ISetupConfigurationTargetBuilder configBuilder, Layout layout = null) + public static ISetupConfigurationTargetBuilder WriteToDebug(this ISetupConfigurationTargetBuilder configBuilder, Layout? layout = null) { var debugTarget = new DebugSystemTarget(); if (layout != null) @@ -558,7 +538,7 @@ public static ISetupConfigurationTargetBuilder WriteToDebug(this ISetupConfigura /// /// Override the default Layout for output [System.Diagnostics.Conditional("DEBUG")] - public static void WriteToDebugConditional(this ISetupConfigurationTargetBuilder configBuilder, Layout layout = null) + public static void WriteToDebugConditional(this ISetupConfigurationTargetBuilder configBuilder, Layout? layout = null) { configBuilder.WriteToDebug(layout); } @@ -572,27 +552,28 @@ public static void WriteToDebugConditional(this ISetupConfigurationTargetBuilder /// Override the default Encoding for output (Default = UTF8) /// Override the default line ending characters (Ex. without CR) /// Keep log file open instead of opening and closing it on each logging event - /// Activate multi-process synchronization using global mutex on the operating system /// Size in bytes where log files will be automatically archived. /// Maximum number of archive files that should be kept. /// Maximum days of archive files that should be kept. - public static ISetupConfigurationTargetBuilder WriteToFile(this ISetupConfigurationTargetBuilder configBuilder, Layout fileName, Layout layout = null, System.Text.Encoding encoding = null, LineEndingMode lineEnding = null, bool keepFileOpen = true, bool concurrentWrites = false, long archiveAboveSize = 0, int maxArchiveFiles = 0, int maxArchiveDays = 0) + public static ISetupConfigurationTargetBuilder WriteToFile(this ISetupConfigurationTargetBuilder configBuilder, Layout fileName, Layout? layout = null, System.Text.Encoding? encoding = null, LineEndingMode? lineEnding = null, bool keepFileOpen = true, long archiveAboveSize = -1, int maxArchiveFiles = -1, int maxArchiveDays = -1) { Guard.ThrowIfNull(fileName); var fileTarget = new FileTarget(); fileTarget.FileName = fileName; + fileTarget.KeepFileOpen = keepFileOpen; if (layout != null) fileTarget.Layout = layout; if (encoding != null) fileTarget.Encoding = encoding; - if (lineEnding != null) + if (lineEnding is not null) fileTarget.LineEnding = lineEnding; - fileTarget.KeepFileOpen = keepFileOpen; - fileTarget.ConcurrentWrites = concurrentWrites; - fileTarget.ArchiveAboveSize = archiveAboveSize; - fileTarget.MaxArchiveFiles = maxArchiveFiles; - fileTarget.MaxArchiveDays = maxArchiveDays; + if (archiveAboveSize > 0) + fileTarget.ArchiveAboveSize = archiveAboveSize; + if (maxArchiveFiles >= 0) + fileTarget.MaxArchiveFiles = maxArchiveFiles; + if (maxArchiveDays > 0) + fileTarget.MaxArchiveDays = maxArchiveDays; return configBuilder.WriteTo(fileTarget); } @@ -601,7 +582,7 @@ public static ISetupConfigurationTargetBuilder WriteToFile(this ISetupConfigurat /// /// Fluent interface parameter. /// Factory method for creating target-wrapper - public static ISetupConfigurationTargetBuilder WithWrapper(this ISetupConfigurationTargetBuilder configBuilder, Func wrapperFactory) + public static ISetupConfigurationTargetBuilder WithWrapper(this ISetupConfigurationTargetBuilder configBuilder, Func wrapperFactory) { Guard.ThrowIfNull(wrapperFactory); diff --git a/src/NLog/SetupSerializationBuilderExtensions.cs b/src/NLog/SetupSerializationBuilderExtensions.cs index 1855f44566..6e55d37bce 100644 --- a/src/NLog/SetupSerializationBuilderExtensions.cs +++ b/src/NLog/SetupSerializationBuilderExtensions.cs @@ -34,7 +34,8 @@ namespace NLog { using System; - using System.Reflection; + using System.Diagnostics.CodeAnalysis; + using NLog.Common; using NLog.Config; using NLog.Internal; @@ -45,13 +46,13 @@ public static class SetupSerializationBuilderExtensions { /// /// Enable/disables the NLog Message Template Parsing: - /// - True = Always use NLog mesage-template-parser and formatting. - /// - False = Never use NLog-parser and only use string.Format (Disable support for message-template-syntax). - /// - Null = Auto detection of message-template-syntax, with fallback to string.Format (Default Behavior). + /// - = Always use NLog mesage-template-parser and formatting. + /// - = Never use NLog-parser and only use string.Format (Disable support for message-template-syntax). + /// - = Auto detection of message-template-syntax, with fallback to string.Format (Default Behavior). /// public static ISetupSerializationBuilder ParseMessageTemplates(this ISetupSerializationBuilder setupBuilder, bool? enable) { - setupBuilder.LogFactory.ServiceRepository.ParseMessageTemplates(enable); + setupBuilder.LogFactory.ServiceRepository.ParseMessageTemplates(setupBuilder.LogFactory, enable); return setupBuilder; } @@ -69,7 +70,26 @@ public static ISetupSerializationBuilder RegisterJsonConverter(this ISetupSerial /// public static ISetupSerializationBuilder RegisterValueFormatter(this ISetupSerializationBuilder setupBuilder, IValueFormatter valueFormatter) { - setupBuilder.LogFactory.ServiceRepository.RegisterValueFormatter(valueFormatter ?? new MessageTemplates.ValueFormatter(setupBuilder.LogFactory.ServiceRepository)); + setupBuilder.LogFactory.ServiceRepository.RegisterValueFormatter(valueFormatter ?? new MessageTemplates.ValueFormatter(setupBuilder.LogFactory.ServiceRepository, legacyStringQuotes: false)); + return setupBuilder; + } + + /// + /// Overrides the active to use legacy-mode string-quotes (Before NLog v6) + /// + public static ISetupSerializationBuilder RegisterValueFormatterWithStringQuotes(this ISetupSerializationBuilder setupBuilder) + { + setupBuilder.LogFactory.ServiceRepository.RegisterValueFormatter(new MessageTemplates.ValueFormatter(setupBuilder.LogFactory.ServiceRepository, legacyStringQuotes: true)); + return setupBuilder; + } + + /// + /// Registers object Type transformation so build trimming will keep public properties. + /// + public static ISetupSerializationBuilder RegisterObjectTransformation<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(this ISetupSerializationBuilder setupBuilder) + { + Guard.ThrowIfNull(setupBuilder); + InternalLogger.Debug("RegisterObjectTransformation for {0} to keep public properties", typeof(T)); return setupBuilder; } @@ -79,6 +99,7 @@ public static ISetupSerializationBuilder RegisterValueFormatter(this ISetupSeria public static ISetupSerializationBuilder RegisterObjectTransformation(this ISetupSerializationBuilder setupBuilder, Func transformer) { Guard.ThrowIfNull(transformer); + InternalLogger.Debug("RegisterObjectTransformation for {0} to convert safely", typeof(T)); var original = setupBuilder.LogFactory.ServiceRepository.GetService(); setupBuilder.LogFactory.ServiceRepository.RegisterObjectTypeTransformer(new ObjectTypeTransformation(transformer, original)); @@ -109,7 +130,7 @@ public ObjectTypeTransformation(Func transformer, IObjectTypeTransfor _transformer = transformer; } - public object TryTransformObject(object obj) + public object? TryTransformObject(object obj) { if (obj is T rawObject) { @@ -134,7 +155,7 @@ public ObjectTypeTransformation(Type objecType, Func transformer _objectType = objecType; } - public object TryTransformObject(object obj) + public object? TryTransformObject(object obj) { if (_objectType.IsAssignableFrom(obj.GetType())) { diff --git a/src/NLog/Targets/AsyncTaskTarget.cs b/src/NLog/Targets/AsyncTaskTarget.cs index d70387a3e9..cfd84049ca 100644 --- a/src/NLog/Targets/AsyncTaskTarget.cs +++ b/src/NLog/Targets/AsyncTaskTarget.cs @@ -58,7 +58,6 @@ namespace NLog.Targets /// this.Host = "localhost"; /// } /// - /// [RequiredParameter] /// public Layout Host { get; set; } /// /// protected override Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken token) @@ -82,41 +81,48 @@ public abstract class AsyncTaskTarget : TargetWithContext AsyncRequestQueueBase _requestQueue; private readonly Action _taskCancelledTokenReInit; private readonly Action _taskCompletion; - private Task _previousTask; + private Task? _previousTask; private readonly Timer _lazyWriterTimer; + private readonly LogEventInfo _flushEvent = LogEventInfo.Create(LogLevel.Off, null, "NLog Async Task Flush Event"); private readonly ReusableAsyncLogEventList _reusableAsyncLogEventList = new ReusableAsyncLogEventList(200); - private Tuple, List> _reusableLogEvents; - private AsyncHelpersTask? _flushEventsInQueueDelegate; + private Tuple, List>? _reusableLogEvents; + private WaitCallback? _flushEventsInQueueDelegate; private bool _missingServiceTypes; /// /// How many milliseconds to delay the actual write operation to optimize for batching /// + /// Default: /// public int TaskDelayMilliseconds { get; set; } = 1; /// /// How many seconds a Task is allowed to run before it is cancelled. /// + /// Default: /// public int TaskTimeoutSeconds { get; set; } = 150; /// /// How many attempts to retry the same Task, before it is aborted /// + /// Default: /// public int RetryCount { get; set; } /// /// How many milliseconds to wait before next retry (will double with each retry) /// + /// Default: ms /// - public int RetryDelayMilliseconds { get; set; } = 500; + public int RetryDelayMilliseconds { get => _retryDelayMilliseconds ?? ((RetryCount > 0 || OverflowAction != AsyncTargetWrapperOverflowAction.Discard) ? 500 : 50); set => _retryDelayMilliseconds = value; } + private int? _retryDelayMilliseconds; /// /// Gets or sets whether to use the locking queue, instead of a lock-free concurrent queue /// The locking queue is less concurrent when many logger threads, but reduces memory allocation /// + /// Default: /// public bool ForceLockingQueue { get => _forceLockingQueue ?? false; set => _forceLockingQueue = value; } private bool? _forceLockingQueue; @@ -125,6 +131,7 @@ public abstract class AsyncTaskTarget : TargetWithContext /// Gets or sets the action to be taken when the lazy writer thread request queue count /// exceeds the set limit. /// + /// Default: /// public AsyncTargetWrapperOverflowAction OverflowAction { @@ -135,23 +142,26 @@ public AsyncTargetWrapperOverflowAction OverflowAction /// /// Gets or sets the limit on the number of requests in the lazy writer thread request queue. /// + /// Default: /// public int QueueLimit { - get => _requestQueue.RequestLimit; - set => _requestQueue.RequestLimit = value; + get => _requestQueue.QueueLimit; + set => _requestQueue.QueueLimit = value; } /// /// Gets or sets the number of log events that should be processed in a batch /// by the lazy writer thread. /// + /// Default: /// public int BatchSize { get; set; } = 1; /// /// Task Scheduler used for processing async Tasks /// + /// Default: protected virtual TaskScheduler TaskScheduler => TaskScheduler.Default; /// @@ -160,22 +170,22 @@ public int QueueLimit protected AsyncTaskTarget() { _taskCompletion = TaskCompletion; - _taskCancelledTokenReInit = TaskCancelledTokenReInit; + _taskCancelledTokenReInit = () => TaskCancelledTokenReInit(out _); _taskTimeoutTimer = new Timer(TaskTimeout, null, Timeout.Infinite, Timeout.Infinite); -#if NETSTANDARD2_0 +#if NETFRAMEWORK + _requestQueue = new AsyncRequestQueue(10000, AsyncTargetWrapperOverflowAction.Discard); +#else // NetStandard20 includes many optimizations for ConcurrentQueue: // - See: https://blogs.msdn.microsoft.com/dotnet/2017/06/07/performance-improvements-in-net-core/ // Net40 ConcurrencyQueue can seem to leak, because it doesn't clear properly on dequeue // - See: https://blogs.msdn.microsoft.com/pfxteam/2012/05/08/concurrentqueuet-holding-on-to-a-few-dequeued-elements/ _requestQueue = new ConcurrentRequestQueue(10000, AsyncTargetWrapperOverflowAction.Discard); -#else - _requestQueue = new AsyncRequestQueue(10000, AsyncTargetWrapperOverflowAction.Discard); #endif _lazyWriterTimer = new Timer((s) => TaskStartNext(null, false), null, Timeout.Infinite, Timeout.Infinite); - TaskCancelledTokenReInit(); + TaskCancelledTokenReInit(out _cancelTokenSource); } /// @@ -183,7 +193,7 @@ protected override void InitializeTarget() { _missingServiceTypes = false; - TaskCancelledTokenReInit(); + TaskCancelledTokenReInit(out var _); base.InitializeTarget(); @@ -246,7 +256,7 @@ protected virtual Task WriteAsyncTask(IList logEvents, Cancellatio else { // Should never come here. Only here if someone by mistake configured BatchSize > 1 for target that only handles single LogEventInfo - Task taskChain = null; + Task? taskChain = null; for (int i = 0; i < logEvents.Count; ++i) { LogEventInfo logEvent = logEvents[i]; @@ -255,7 +265,12 @@ protected virtual Task WriteAsyncTask(IList logEvents, Cancellatio else taskChain = taskChain.ContinueWith(t => WriteAsyncTask(logEvent, cancellationToken), cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler).Unwrap(); } - return taskChain; + return taskChain ?? +#if NET45 + System.Threading.Tasks.Task.FromResult(null); +#else + System.Threading.Tasks.Task.CompletedTask; +#endif } } @@ -388,7 +403,7 @@ protected override void FlushAsync(AsyncContinuation asyncContinuation) InternalLogger.Debug("{0}: Flushing {1}", this, _requestQueue.IsEmpty ? "empty queue" : "pending queue items"); if (_requestQueue.OnOverflow != AsyncTargetWrapperOverflowAction.Block) { - _requestQueue.Enqueue(new AsyncLogEventInfo(null, asyncContinuation)); + _requestQueue.Enqueue(new AsyncLogEventInfo(_flushEvent, asyncContinuation)); _lazyWriterTimer.Change(0, Timeout.Infinite); // Schedule timer to empty queue, and execute asyncContinuation } else @@ -396,14 +411,14 @@ protected override void FlushAsync(AsyncContinuation asyncContinuation) // We are holding target-SyncRoot, and might get blocked in queue, so need to schedule flush using async-task if (_flushEventsInQueueDelegate is null) { - _flushEventsInQueueDelegate = new AsyncHelpersTask(cont => + _flushEventsInQueueDelegate = (cont) => { - _requestQueue.Enqueue(new AsyncLogEventInfo(null, (AsyncContinuation)cont)); + _requestQueue.Enqueue(new AsyncLogEventInfo(_flushEvent, (AsyncContinuation)cont)); lock (SyncRoot) _lazyWriterTimer.Change(0, Timeout.Infinite); // Schedule timer to empty queue, and execute asyncContinuation - }); + }; } - AsyncHelpers.StartAsyncTask(_flushEventsInQueueDelegate.Value, asyncContinuation); + AsyncHelpers.StartAsyncTask(_flushEventsInQueueDelegate, asyncContinuation); _lazyWriterTimer.Change(0, Timeout.Infinite); // Schedule timer to empty queue, so room for flush-request } } @@ -446,7 +461,7 @@ protected override void Dispose(bool disposing) /// /// Used for race-condition validation between task-completion and timeout /// Signals whether previousTask completed an almost full BatchSize - private void TaskStartNext(object previousTask, bool fullBatchCompleted) + private void TaskStartNext(object? previousTask, bool fullBatchCompleted) { do { @@ -497,18 +512,18 @@ private void TaskStartNext(object previousTask, bool fullBatchCompleted) } while (!_requestQueue.IsEmpty || previousTask != null); } - private bool CheckOtherTask(object previousTask) + private bool CheckOtherTask(object? previousTask) { - if (previousTask != null) + if (previousTask is null) { - // Task Completed - if (_previousTask != null && !ReferenceEquals(previousTask, _previousTask)) + // Task Queue Timer + if (_previousTask?.IsCompleted == false) return true; } else { - // Task Queue Timer - if (_previousTask?.IsCompleted == false) + // Task Completed + if (_previousTask != null && !ReferenceEquals(previousTask, _previousTask)) return true; } @@ -522,9 +537,9 @@ internal Task WriteAsyncTaskWithRetry(Task firstTask, IList logEve { var tcs = #if !NET45 - new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); #else - new TaskCompletionSource(); + new TaskCompletionSource(); #endif return firstTask.ContinueWith(t => { @@ -533,7 +548,7 @@ internal Task WriteAsyncTaskWithRetry(Task firstTask, IList logEve if (t.Exception != null) tcs.TrySetException(t.Exception); - Exception actualException = ExtractActualException(t.Exception); + var actualException = ExtractActualException(t.Exception) ?? new TaskCanceledException("Task failed without exception"); if (RetryFailedAsyncTask(actualException, cancellationToken, retryCount - 1, out var retryDelay)) { @@ -571,7 +586,7 @@ internal Task WriteAsyncTaskWithRetry(Task firstTask, IList logEve /// New Task created [true / false] private bool TaskCreation(IList logEvents) { - System.Tuple, List> reusableLogEvents = null; + System.Tuple, List>? reusableLogEvents = null; try { @@ -586,7 +601,7 @@ private bool TaskCreation(IList logEvents) for (int i = 0; i < logEvents.Count; ++i) { - if (logEvents[i].LogEvent is null) + if (ReferenceEquals(logEvents[i].LogEvent, _flushEvent)) { // Flush Request reusableLogEvents.Item2.Add(logEvents[i].Continuation); @@ -631,7 +646,7 @@ private bool TaskCreation(IList logEvents) { _previousTask = null; InternalLogger.Error(ex, "{0}: WriteAsyncTask failed on creation", this); - NotifyTaskCompletion(reusableLogEvents?.Item2, ex); + NotifyTaskCompletion(reusableLogEvents?.Item2 ?? (IList)ArrayHelper.Empty(), ex); } return false; } @@ -642,7 +657,15 @@ private Task StartWriteAsyncTask(IList logEvents, CancellationToke { InternalLogger.Trace("{0}: Writing {1} events", this, logEvents.Count); var newTask = WriteAsyncTask(logEvents, cancellationToken); - if (newTask?.Status == TaskStatus.Created) + if (newTask is null) + { +#if NET45 + return System.Threading.Tasks.Task.FromResult(null); +#else + return System.Threading.Tasks.Task.CompletedTask; +#endif + } + if (newTask.Status == TaskStatus.Created) newTask.Start(TaskScheduler); return newTask; } @@ -663,11 +686,11 @@ private Task StartWriteAsyncTask(IList logEvents, CancellationToke } } - private static void NotifyTaskCompletion(IList reusableContinuations, Exception ex) + private static void NotifyTaskCompletion(IList reusableContinuations, Exception? ex) { try { - for (int i = 0; i < reusableContinuations?.Count; ++i) + for (int i = 0; i < reusableContinuations.Count; ++i) reusableContinuations[i](ex); } catch @@ -686,6 +709,8 @@ private void TaskCompletion(Task completedTask, object continuation) bool success = true; bool fullBatchCompleted = true; + TimeSpan retryOnFailureDelay = TimeSpan.Zero; + try { if (ReferenceEquals(completedTask, _previousTask)) @@ -719,10 +744,9 @@ private void TaskCompletion(Task completedTask, object continuation) success = false; if (RetryCount <= 0) { - if (RetryFailedAsyncTask(actualException, CancellationToken.None, 0, out var retryDelay)) + if (RetryFailedAsyncTask(actualException, CancellationToken.None, 0, out retryOnFailureDelay)) { - InternalLogger.Warn(actualException, "{0}: WriteAsyncTask failed on completion. Sleep {1} ms", this, retryDelay.TotalMilliseconds); - AsyncHelpers.WaitForDelay(retryDelay); + InternalLogger.Warn(actualException, "{0}: WriteAsyncTask failed on completion. Delay {1} ms", this, retryOnFailureDelay.TotalMilliseconds); } } else @@ -737,7 +761,7 @@ private void TaskCompletion(Task completedTask, object continuation) else success = false; - if (success) + if (success && reusableLogEvents != null) { // The expected Task completed with success, allow buffer reuse fullBatchCompleted = reusableLogEvents.Item2.Count * 2 > BatchSize; @@ -748,7 +772,10 @@ private void TaskCompletion(Task completedTask, object continuation) } finally { - TaskStartNext(completedTask, fullBatchCompleted); + if (retryOnFailureDelay > TimeSpan.Zero) + _lazyWriterTimer.Change((int)retryOnFailureDelay.TotalMilliseconds, Timeout.Infinite); + else + TaskStartNext(completedTask, fullBatchCompleted); } } @@ -790,7 +817,7 @@ private void TaskTimeout(object state) InternalLogger.Debug("{0}: WriteAsyncTask had timeout. Task did not cancel properly: {1}.", this, previousTask.Status); } - Exception actualException = ExtractActualException(previousTask.Exception); + var actualException = ExtractActualException(previousTask.Exception); RetryFailedAsyncTask(actualException ?? new TimeoutException("WriteAsyncTask had timeout"), CancellationToken.None, 0, out var retryDelay); } } @@ -818,16 +845,18 @@ private static bool WaitTaskIsCompleted(Task task, TimeSpan timeout) return task.IsCompleted; } - private static Exception ExtractActualException(AggregateException taskException) + private static Exception? ExtractActualException(AggregateException? taskException) { + if (taskException?.InnerExceptions?.Count == 1 && !(taskException.InnerExceptions[0] is AggregateException)) + return taskException.InnerExceptions[0]; // Skip Flatten() + var flattenExceptions = taskException?.Flatten()?.InnerExceptions; - Exception actualException = flattenExceptions?.Count == 1 ? flattenExceptions[0] : taskException; - return actualException; + return flattenExceptions?.Count == 1 ? flattenExceptions[0] : taskException; } - private void TaskCancelledTokenReInit() + private void TaskCancelledTokenReInit(out CancellationTokenSource cancelTokenSource) { - _cancelTokenSource = new CancellationTokenSource(); + _cancelTokenSource = cancelTokenSource = new CancellationTokenSource(); _cancelTokenSource.Token.Register(_taskCancelledTokenReInit); } } diff --git a/src/NLog/Targets/ColoredConsoleAnsiPrinter.cs b/src/NLog/Targets/ColoredConsoleAnsiPrinter.cs index 8b66bc3fab..fb5607e4ad 100644 --- a/src/NLog/Targets/ColoredConsoleAnsiPrinter.cs +++ b/src/NLog/Targets/ColoredConsoleAnsiPrinter.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.Targets { using System; @@ -213,14 +211,9 @@ private static string TerminalDefaultColorEscapeCode /// public IList DefaultConsoleRowHighlightingRules { get; } = new List() { - new ConsoleRowHighlightingRule("level == LogLevel.Fatal", ConsoleOutputColor.DarkRed, ConsoleOutputColor.NoChange), - new ConsoleRowHighlightingRule("level == LogLevel.Error", ConsoleOutputColor.DarkYellow, ConsoleOutputColor.NoChange), - new ConsoleRowHighlightingRule("level == LogLevel.Warn", ConsoleOutputColor.DarkMagenta, ConsoleOutputColor.NoChange), - new ConsoleRowHighlightingRule("level == LogLevel.Info", ConsoleOutputColor.NoChange, ConsoleOutputColor.NoChange), - new ConsoleRowHighlightingRule("level == LogLevel.Debug", ConsoleOutputColor.NoChange, ConsoleOutputColor.NoChange), - new ConsoleRowHighlightingRule("level == LogLevel.Trace", ConsoleOutputColor.NoChange, ConsoleOutputColor.NoChange), + new ConsoleRowHighlightingRule(Conditions.ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Fatal", (evt) => evt.Level == LogLevel.Fatal), ConsoleOutputColor.DarkRed, ConsoleOutputColor.NoChange), + new ConsoleRowHighlightingRule(Conditions.ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Error", (evt) => evt.Level == LogLevel.Error), ConsoleOutputColor.DarkRed, ConsoleOutputColor.NoChange), + new ConsoleRowHighlightingRule(Conditions.ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Warn", (evt) => evt.Level == LogLevel.Warn), ConsoleOutputColor.DarkYellow, ConsoleOutputColor.NoChange), }; } } - -#endif diff --git a/src/NLog/Targets/ColoredConsoleSystemPrinter.cs b/src/NLog/Targets/ColoredConsoleSystemPrinter.cs index b42abbf87e..68cf175589 100644 --- a/src/NLog/Targets/ColoredConsoleSystemPrinter.cs +++ b/src/NLog/Targets/ColoredConsoleSystemPrinter.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.Targets { using System; @@ -103,14 +101,12 @@ public void WriteLine(TextWriter consoleWriter, string text) public IList DefaultConsoleRowHighlightingRules { get; } = new List() { - new ConsoleRowHighlightingRule("level == LogLevel.Fatal", ConsoleOutputColor.Red, ConsoleOutputColor.NoChange), - new ConsoleRowHighlightingRule("level == LogLevel.Error", ConsoleOutputColor.Yellow, ConsoleOutputColor.NoChange), - new ConsoleRowHighlightingRule("level == LogLevel.Warn", ConsoleOutputColor.Magenta, ConsoleOutputColor.NoChange), - new ConsoleRowHighlightingRule("level == LogLevel.Info", ConsoleOutputColor.White, ConsoleOutputColor.NoChange), - new ConsoleRowHighlightingRule("level == LogLevel.Debug", ConsoleOutputColor.Gray, ConsoleOutputColor.NoChange), - new ConsoleRowHighlightingRule("level == LogLevel.Trace", ConsoleOutputColor.DarkGray, ConsoleOutputColor.NoChange), + new ConsoleRowHighlightingRule(Conditions.ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Fatal", (evt) => evt.Level == LogLevel.Fatal), ConsoleOutputColor.Red, ConsoleOutputColor.NoChange), + new ConsoleRowHighlightingRule(Conditions.ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Error", (evt) => evt.Level == LogLevel.Error), ConsoleOutputColor.Red, ConsoleOutputColor.NoChange), + new ConsoleRowHighlightingRule(Conditions.ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Warn", (evt) => evt.Level == LogLevel.Warn), ConsoleOutputColor.Yellow, ConsoleOutputColor.NoChange), + new ConsoleRowHighlightingRule(Conditions.ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Info", (evt) => evt.Level == LogLevel.Info), ConsoleOutputColor.White, ConsoleOutputColor.NoChange), + new ConsoleRowHighlightingRule(Conditions.ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Debug", (evt) => evt.Level == LogLevel.Debug), ConsoleOutputColor.Gray, ConsoleOutputColor.NoChange), + new ConsoleRowHighlightingRule(Conditions.ConditionMethodExpression.CreateMethodNoParameters("level == LogLevel.Trace", (evt) => evt.Level == LogLevel.Trace), ConsoleOutputColor.Gray, ConsoleOutputColor.NoChange), }; } } - -#endif diff --git a/src/NLog/Targets/ColoredConsoleTarget.cs b/src/NLog/Targets/ColoredConsoleTarget.cs index 5e686008cb..463070791b 100644 --- a/src/NLog/Targets/ColoredConsoleTarget.cs +++ b/src/NLog/Targets/ColoredConsoleTarget.cs @@ -31,17 +31,17 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.Targets { using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; + using System.Linq; using System.Text; using NLog.Common; using NLog.Config; + using NLog.Layouts; /// /// Writes log messages to the console with customizable coloring. @@ -115,19 +115,19 @@ public ColoredConsoleTarget(string name) : this() /// [Obsolete("Replaced by StdErr to align with ConsoleTarget. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] - public bool ErrorStream { get => StdErr; set => StdErr = value; } + public bool ErrorStream { get => StdErr?.IsFixed == true && StdErr.FixedValue; set => StdErr = value; } /// /// Gets or sets a value indicating whether to send the log messages to the standard error instead of the standard output. /// /// - public bool StdErr { get; set; } + public Layout? StdErr { get; set; } /// /// Gets or sets a value indicating whether to use default row highlighting rules. /// /// - /// The default rules are: + /// Default: which enables the following rules: /// /// /// @@ -141,12 +141,12 @@ public ColoredConsoleTarget(string name) : this() /// /// /// - /// + /// /// /// /// /// - /// + /// /// /// /// @@ -161,7 +161,7 @@ public ColoredConsoleTarget(string name) : this() /// /// /// - /// + /// /// /// ///
Condition
level == LogLevel.ErrorYellowRedNoChange
level == LogLevel.WarnMagentaYellowNoChange
level == LogLevel.TraceDarkGrayGrayNoChange
@@ -183,20 +183,22 @@ public Encoding Encoding _encoding = value; } } - private Encoding _encoding; + private Encoding? _encoding; /// /// Gets or sets a value indicating whether to auto-check if the console is available. - /// - Disables console writing if Environment.UserInteractive = False (Windows Service) + /// - Disables console writing if Environment.UserInteractive = (Windows Service) /// - Disables console writing if Console Standard Input is not available (Non-Console-App) /// + /// Default: /// public bool DetectConsoleAvailable { get; set; } /// /// Gets or sets a value indicating whether to auto-check if the console has been redirected to file - /// - Disables coloring logic when System.Console.IsOutputRedirected = true + /// - Disables coloring logic when System.Console.IsOutputRedirected = /// + /// Default: /// public bool DetectOutputRedirected { get; set; } @@ -204,6 +206,7 @@ public Encoding Encoding /// Gets or sets a value indicating whether to auto-flush after ///
/// + /// Default: . /// Normally not required as standard Console.Out will have = true, but not when pipe to file /// /// @@ -212,9 +215,17 @@ public Encoding Encoding /// /// Enables output using ANSI Color Codes /// + /// Default: /// public bool EnableAnsiOutput { get; set; } + /// + /// Support NO_COLOR=1 environment variable. See also + /// + /// Default: NO_COLOR=1 + /// + public Layout NoColor { get; set; } = Layout.FromMethod((evt) => new string[] { "1", "TRUE" }.Contains(NLog.Internal.EnvironmentHelper.GetSafeEnvironmentVariable("NO_COLOR")?.Trim().ToUpper())); + /// /// Gets the row highlighting rules. /// @@ -241,7 +252,7 @@ protected override void InitializeTarget() _pauseLogging = !ConsoleTargetHelper.IsConsoleAvailable(out reason); if (_pauseLogging) { - InternalLogger.Info("{0}: Console detected as turned off. Disable DetectConsoleAvailable to skip detection. Reason: {1}", this, reason); + InternalLogger.Info("{0}: Console detected as turned off. Set DetectConsoleAvailable=false to skip detection. Reason: {1}", this, reason); } } @@ -253,11 +264,12 @@ protected override void InitializeTarget() { try { - _disableColors = StdErr ? Console.IsErrorRedirected : Console.IsOutputRedirected; + var stdErr = RenderLogEvent(StdErr, LogEventInfo.CreateNullEvent()); + _disableColors = stdErr ? Console.IsErrorRedirected : Console.IsOutputRedirected; if (_disableColors) { - InternalLogger.Info("{0}: Console output is redirected so no colors. Disable DetectOutputRedirected to skip detection.", this); - if (!AutoFlush && GetOutput() is StreamWriter streamWriter && !streamWriter.AutoFlush) + InternalLogger.Info("{0}: Console output is redirected so no colors. Set DetectOutputRedirected=false to skip detection.", this); + if (!AutoFlush && GetOutput(stdErr) is StreamWriter streamWriter && !streamWriter.AutoFlush) { AutoFlush = true; } @@ -270,6 +282,12 @@ protected override void InitializeTarget() } #endif + if (!_disableColors && NoColor?.RenderValue(LogEventInfo.CreateNullEvent()) == true) + { + _disableColors = true; + InternalLogger.Info("{0}: Environment with NO_COLOR, so colors are disabled. Set NoColor=false to skip detection.", this); + } + base.InitializeTarget(); if (Header != null) @@ -319,8 +337,19 @@ private void ExplicitConsoleFlush() { if (!_pauseLogging && !AutoFlush) { - var output = GetOutput(); - output.Flush(); + if ((StdErr?.IsFixed ?? true)) + { + var stdErr = StdErr?.FixedValue ?? false; + var output = GetOutput(stdErr); + output.Flush(); + } + else + { + var output = GetOutput(false); + output.Flush(); + output = GetOutput(true); + output.Flush(); + } } } @@ -369,7 +398,8 @@ private void WriteToOutputWithColor(LogEventInfo logEvent, string message) newBackgroundColor = matchingRule.BackgroundColor != ConsoleOutputColor.NoChange ? (ConsoleColor)matchingRule.BackgroundColor : default(ConsoleColor?); } - var consoleStream = GetOutput(); + var stdErr = RenderLogEvent(StdErr, logEvent); + var consoleStream = GetOutput(stdErr); if (ReferenceEquals(colorMessage, message) && !newForegroundColor.HasValue && !newBackgroundColor.HasValue) { ConsoleTargetHelper.WriteLineThreadSafe(consoleStream, message, AutoFlush); @@ -440,7 +470,7 @@ private ConsoleRowHighlightingRule GetMatchingRowHighlightingRule(LogEventInfo l return matchingRule ?? ConsoleRowHighlightingRule.Default; } - private static ConsoleRowHighlightingRule GetMatchingRowHighlightingRule(IList rules, LogEventInfo logEvent) + private static ConsoleRowHighlightingRule? GetMatchingRowHighlightingRule(IList rules, LogEventInfo logEvent) { for (int i = 0; i < rules.Count; ++i) { @@ -465,27 +495,33 @@ private string GenerateColorEscapeSequences(LogEventInfo logEvent, string messag for (int i = 0; i < WordHighlightingRules.Count; ++i) { var hl = WordHighlightingRules[i]; - var matches = hl.Matches(logEvent, message); - if (matches is null || matches.Count == 0) + if (!hl.CheckCondition(logEvent)) + continue; + + var matches = hl.GetWordsForHighlighting(message); + if (matches is null) continue; if (sb != null) sb.Length = 0; int previousIndex = 0; - foreach (System.Text.RegularExpressions.Match match in matches) + foreach (var match in matches) { sb = sb ?? new StringBuilder(message.Length + 5); - sb.Append(message, previousIndex, match.Index - previousIndex); + sb.Append(message, previousIndex, match.Key - previousIndex); sb.Append('\a'); sb.Append((char)((int)hl.ForegroundColor + 'A')); sb.Append((char)((int)hl.BackgroundColor + 'A')); - sb.Append(match.Value); + for (int j = 0; j < match.Value; ++j) + { + sb.Append(message[j + match.Key]); + } sb.Append('\a'); sb.Append('X'); - previousIndex = match.Index + match.Length; + previousIndex = match.Key + match.Value; } if (sb?.Length > 0) @@ -627,11 +663,9 @@ private static void ColorizeEscapeSequences( } } - private TextWriter GetOutput() + private static TextWriter GetOutput(bool stdErr) { - return StdErr ? Console.Error : Console.Out; + return stdErr ? Console.Error : Console.Out; } } } - -#endif diff --git a/src/NLog/Targets/ConsoleRowHighlightingRule.cs b/src/NLog/Targets/ConsoleRowHighlightingRule.cs index 45c30e4fd2..fc5a25f126 100644 --- a/src/NLog/Targets/ConsoleRowHighlightingRule.cs +++ b/src/NLog/Targets/ConsoleRowHighlightingRule.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.Targets { using NLog.Conditions; @@ -48,7 +46,6 @@ public class ConsoleRowHighlightingRule /// Initializes a new instance of the class. /// public ConsoleRowHighlightingRule() - : this(null, ConsoleOutputColor.NoChange, ConsoleOutputColor.NoChange) { } @@ -58,7 +55,7 @@ public ConsoleRowHighlightingRule() /// The condition. /// Color of the foreground. /// Color of the background. - public ConsoleRowHighlightingRule(ConditionExpression condition, ConsoleOutputColor foregroundColor, ConsoleOutputColor backgroundColor) + public ConsoleRowHighlightingRule(ConditionExpression? condition, ConsoleOutputColor foregroundColor, ConsoleOutputColor backgroundColor) { Condition = condition; ForegroundColor = foregroundColor; @@ -73,37 +70,30 @@ public ConsoleRowHighlightingRule(ConditionExpression condition, ConsoleOutputCo /// /// Gets or sets the condition that must be met in order to set the specified foreground and background color. /// + /// Default: /// - [RequiredParameter] - public ConditionExpression Condition { get; set; } + public ConditionExpression? Condition { get; set; } /// /// Gets or sets the foreground color. /// + /// Default: /// - public ConsoleOutputColor ForegroundColor { get; set; } + public ConsoleOutputColor ForegroundColor { get; set; } = ConsoleOutputColor.NoChange; /// /// Gets or sets the background color. /// + /// Default: /// - public ConsoleOutputColor BackgroundColor { get; set; } + public ConsoleOutputColor BackgroundColor { get; set; } = ConsoleOutputColor.NoChange; /// /// Checks whether the specified log event matches the condition (if any). /// - /// - /// Log event. - /// - /// - /// A value of if the condition is not defined or - /// if it matches, otherwise. - /// public bool CheckCondition(LogEventInfo logEvent) { return Condition is null || true.Equals(Condition.Evaluate(logEvent)); } } } - -#endif diff --git a/src/NLog/Targets/ConsoleTarget.cs b/src/NLog/Targets/ConsoleTarget.cs index 8775ad0baf..1c1436c4fb 100644 --- a/src/NLog/Targets/ConsoleTarget.cs +++ b/src/NLog/Targets/ConsoleTarget.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.Targets { using System; @@ -91,18 +89,17 @@ public sealed class ConsoleTarget : TargetWithLayoutHeaderAndFooter /// [Obsolete("Replaced by StdErr to align with ColoredConsoleTarget. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] - public bool Error { get => StdErr; set => StdErr = value; } + public bool Error { get => StdErr?.IsFixed == true && StdErr.FixedValue; set => StdErr = value; } /// /// Gets or sets a value indicating whether to send the log messages to the standard error instead of the standard output. /// /// - public bool StdErr { get; set; } + public Layout? StdErr { get; set; } /// /// The encoding for writing messages to the . /// - /// Has side effect /// public Encoding Encoding { @@ -113,30 +110,40 @@ public Encoding Encoding _encoding = value; } } - private Encoding _encoding; + private Encoding? _encoding; /// /// Gets or sets a value indicating whether to auto-check if the console is available - /// - Disables console writing if Environment.UserInteractive = False (Windows Service) + /// - Disables console writing if Environment.UserInteractive = (Windows Service) /// - Disables console writing if Console Standard Input is not available (Non-Console-App) /// + /// Default: /// public bool DetectConsoleAvailable { get; set; } /// /// Gets or sets a value indicating whether to auto-flush after /// - /// + /// Default: . /// Normally not required as standard Console.Out will have = true, but not when pipe to file /// /// public bool AutoFlush { get; set; } /// - /// Gets or sets whether to activate internal buffering to allow batch writing, instead of using + /// Gets or sets whether to force (slower) instead of the faster internal buffering. /// + /// Default: /// - public bool WriteBuffer { get; set; } + public bool ForceWriteLine { get; set; } + + /// + /// Gets or sets whether to activate internal buffering to allow batch writing, instead of using + /// + /// Default: + /// + [Obsolete("Replaced by ForceWriteLine (but inverted value). Marked obsolete with NLog v6.0")] + public bool WriteBuffer { get => !ForceWriteLine; set => ForceWriteLine = !value; } /// /// Initializes a new instance of the class. @@ -144,7 +151,7 @@ public Encoding Encoding /// /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} /// - public ConsoleTarget() : base() + public ConsoleTarget() { } @@ -171,7 +178,7 @@ protected override void InitializeTarget() _pauseLogging = !ConsoleTargetHelper.IsConsoleAvailable(out reason); if (_pauseLogging) { - InternalLogger.Info("{0}: Console has been detected as turned off. Disable DetectConsoleAvailable to skip detection. Reason: {1}", this, reason); + InternalLogger.Info("{0}: Console detected as turned off. Set DetectConsoleAvailable=false to skip detection. Reason: {1}", this, reason); } } @@ -214,8 +221,19 @@ private void ExplicitConsoleFlush() { if (!_pauseLogging && !AutoFlush) { - var output = GetOutput(); - output.Flush(); + if ((StdErr?.IsFixed ?? true)) + { + var stdErr = StdErr?.FixedValue ?? false; + var output = GetOutput(stdErr); + output.Flush(); + } + else + { + var output = GetOutput(false); + output.Flush(); + output = GetOutput(true); + output.Flush(); + } } } @@ -239,13 +257,13 @@ protected override void Write(IList logEvents) return; } - if (WriteBuffer) + if (ForceWriteLine) { - WriteBufferToOutput(logEvents); + base.Write(logEvents); // Console.WriteLine } else { - base.Write(logEvents); // Console.WriteLine + WriteBufferToOutput(logEvents); } } @@ -256,14 +274,15 @@ private void RenderToOutput(Layout layout, LogEventInfo logEvent) return; } - var output = GetOutput(); - if (WriteBuffer) + var stdErr = RenderLogEvent(StdErr, logEvent); + var output = GetOutput(stdErr); + if (ForceWriteLine) { - WriteBufferToOutput(output, layout, logEvent); + WriteLineToOutput(output, RenderLogEvent(layout, logEvent)); } else { - WriteLineToOutput(output, RenderLogEvent(layout, logEvent)); + WriteBufferToOutput(output, layout, logEvent); } } @@ -283,17 +302,28 @@ private void WriteBufferToOutput(TextWriter output, Layout layout, LogEventInfo private void WriteBufferToOutput(IList logEvents) { - var output = GetOutput(); using (var targetBuffer = _reusableEncodingBuffer.Allocate()) using (var targetBuilder = ReusableLayoutBuilder.Allocate()) { int targetBufferPosition = 0; + bool stdErr = false; + var output = GetOutput(stdErr); + try { + for (int i = 0; i < logEvents.Count; ++i) { + var logEvent = logEvents[i].LogEvent; + if (stdErr != RenderLogEvent(StdErr, logEvent)) + { + if (targetBufferPosition > 0) + WriteBufferToOutput(output, targetBuffer.Result, targetBufferPosition); + stdErr = !stdErr; + output = GetOutput(stdErr); + } targetBuilder.Result.ClearBuilder(); - RenderLogEventToWriteBuffer(output, Layout, logEvents[i].LogEvent, targetBuilder.Result, targetBuffer.Result, ref targetBufferPosition); + RenderLogEventToWriteBuffer(output, Layout, logEvent, targetBuilder.Result, targetBuffer.Result, ref targetBufferPosition); logEvents[i].Continuation(null); } } @@ -360,11 +390,9 @@ private void WriteBufferToOutput(TextWriter output, char[] buffer, int length) } } - private TextWriter GetOutput() + private static TextWriter GetOutput(bool stdErr) { - return StdErr ? Console.Error : Console.Out; + return stdErr ? Console.Error : Console.Out; } } } - -#endif diff --git a/src/NLog/Targets/ConsoleTargetHelper.cs b/src/NLog/Targets/ConsoleTargetHelper.cs index d565fdd7c7..e3e6a6c5bb 100644 --- a/src/NLog/Targets/ConsoleTargetHelper.cs +++ b/src/NLog/Targets/ConsoleTargetHelper.cs @@ -45,12 +45,12 @@ internal static class ConsoleTargetHelper public static bool IsConsoleAvailable(out string reason) { reason = string.Empty; -#if !MONO && !NETSTANDARD1_3 && !NETSTANDARD1_5 +#if !MONO try { if (!Environment.UserInteractive) { -#if !NETSTANDARD +#if NETFRAMEWORK if (Internal.PlatformDetector.IsMono && Console.In is StreamReader) return true; // Extra bonus check for Mono, that doesn't support Environment.UserInteractive #endif @@ -74,26 +74,18 @@ public static bool IsConsoleAvailable(out string reason) return true; } - public static Encoding GetConsoleOutputEncoding(Encoding currentEncoding, bool isInitialized, bool pauseLogging) + public static Encoding GetConsoleOutputEncoding(Encoding? currentEncoding, bool isInitialized, bool pauseLogging) { -#if !NETSTANDARD1_3 if (currentEncoding != null) return currentEncoding; else if ((isInitialized && !pauseLogging) || IsConsoleAvailable(out _)) return Console.OutputEncoding; -#if !NETSTANDARD1_5 + return Encoding.Default; -#else - return currentEncoding; -#endif -#else - return currentEncoding; -#endif } public static bool SetConsoleOutputEncoding(Encoding newEncoding, bool isInitialized, bool pauseLogging) { -#if !NETSTANDARD1_3 if (!isInitialized) { return true; // Waiting for console target to be initialized @@ -110,7 +102,7 @@ public static bool SetConsoleOutputEncoding(Encoding newEncoding, bool isInitial InternalLogger.Warn(ex, "Failed changing Console.OutputEncoding to {0}", newEncoding); } } -#endif + return false; // No console available } diff --git a/src/NLog/Targets/ConsoleWordHighlightingRule.cs b/src/NLog/Targets/ConsoleWordHighlightingRule.cs index 91c5f8293a..82f1792fb0 100644 --- a/src/NLog/Targets/ConsoleWordHighlightingRule.cs +++ b/src/NLog/Targets/ConsoleWordHighlightingRule.cs @@ -31,11 +31,9 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.Targets { - using System.Text.RegularExpressions; + using System.Collections.Generic; using NLog.Conditions; using NLog.Config; using NLog.Internal; @@ -46,15 +44,11 @@ namespace NLog.Targets [NLogConfigurationItem] public class ConsoleWordHighlightingRule { - private readonly RegexHelper _regexHelper = new RegexHelper(); - /// /// Initializes a new instance of the class. /// public ConsoleWordHighlightingRule() { - BackgroundColor = ConsoleOutputColor.NoChange; - ForegroundColor = ConsoleOutputColor.NoChange; } /// @@ -65,94 +59,109 @@ public ConsoleWordHighlightingRule() /// Color of the background. public ConsoleWordHighlightingRule(string text, ConsoleOutputColor foregroundColor, ConsoleOutputColor backgroundColor) { - _regexHelper.SearchText = text; + Text = text; ForegroundColor = foregroundColor; BackgroundColor = backgroundColor; } - /// - /// Gets or sets the regular expression to be matched. You must specify either text or regex. - /// - /// - public string Regex - { - get => _regexHelper.RegexPattern; - set => _regexHelper.RegexPattern = value; - } - /// /// Gets or sets the condition that must be met before scanning the row for highlight of words /// + /// Default: /// - public ConditionExpression Condition { get; set; } - - /// - /// Compile the ? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used. - /// - /// - public bool CompileRegex - { - get => _regexHelper.CompileRegex; - set => _regexHelper.CompileRegex = value; - } + public ConditionExpression? Condition { get; set; } /// /// Gets or sets the text to be matched. You must specify either text or regex. /// + /// Default: /// - public string Text - { - get => _regexHelper.SearchText; - set => _regexHelper.SearchText = value; - } + public string Text { get => _text; set => _text = string.IsNullOrEmpty(value) ? string.Empty : value; } + private string _text = string.Empty; /// /// Gets or sets a value indicating whether to match whole words only. /// + /// Default: /// - public bool WholeWords - { - get => _regexHelper.WholeWords; - set => _regexHelper.WholeWords = value; - } + public bool WholeWords { get; set; } /// /// Gets or sets a value indicating whether to ignore case when comparing texts. /// + /// Default: /// - public bool IgnoreCase - { - get => _regexHelper.IgnoreCase; - set => _regexHelper.IgnoreCase = value; - } + public bool IgnoreCase { get; set; } /// /// Gets or sets the foreground color. /// + /// Default: /// - public ConsoleOutputColor ForegroundColor { get; set; } + public ConsoleOutputColor ForegroundColor { get; set; } = ConsoleOutputColor.NoChange; /// /// Gets or sets the background color. /// + /// Default: /// - public ConsoleOutputColor BackgroundColor { get; set; } + public ConsoleOutputColor BackgroundColor { get; set; } = ConsoleOutputColor.NoChange; /// - /// Gets the compiled regular expression that matches either Text or Regex property. Only used when is true. + /// Scans the for words that should be highlighted. /// - public Regex CompiledRegex => _regexHelper.Regex; + /// List of words with start-position (Key) and word-length (Value) + internal protected virtual IEnumerable>? GetWordsForHighlighting(string haystack) + { + if (ReferenceEquals(_text, string.Empty)) + return null; + + int firstIndex = FindNextWordForHighlighting(haystack, null); + if (firstIndex < 0) + return null; + + int nextIndex = FindNextWordForHighlighting(haystack, firstIndex); + if (nextIndex < 0) + return new[] { new KeyValuePair(firstIndex, Text.Length) }; + + return YieldWordsForHighlighting(haystack, firstIndex, nextIndex); + } - internal MatchCollection Matches(LogEventInfo logEvent, string message) + private IEnumerable> YieldWordsForHighlighting(string haystack, int firstIndex, int nextIndex) { - if (Condition != null && false.Equals(Condition.Evaluate(logEvent))) + yield return new KeyValuePair(firstIndex, _text.Length); + + yield return new KeyValuePair(nextIndex, _text.Length); + + int index = nextIndex; + while (index >= 0) { - return null; + index = FindNextWordForHighlighting(haystack, index); + if (index >= 0) + yield return new KeyValuePair(index, _text.Length); + } + } + + private int FindNextWordForHighlighting(string haystack, int? prevIndex) + { + int index = prevIndex.HasValue ? prevIndex.Value + _text.Length : 0; + while (index >= 0) + { + index = IgnoreCase ? haystack.IndexOf(_text, index, System.StringComparison.CurrentCultureIgnoreCase) : haystack.IndexOf(_text, index); + if (index < 0 || (!WholeWords || StringHelpers.IsWholeWord(haystack, _text, index))) + return index; + + index += _text.Length; } + return index; + } - return _regexHelper.Matches(message); + /// + /// Checks whether the specified log event matches the condition (if any). + /// + internal bool CheckCondition(LogEventInfo logEvent) + { + return Condition is null || true.Equals(Condition.Evaluate(logEvent)); } } } - -#endif diff --git a/src/NLog/Targets/DebugSystemTarget.cs b/src/NLog/Targets/DebugSystemTarget.cs index 5ccd611620..f3c025a164 100644 --- a/src/NLog/Targets/DebugSystemTarget.cs +++ b/src/NLog/Targets/DebugSystemTarget.cs @@ -51,7 +51,7 @@ public sealed class DebugSystemTarget : TargetWithLayoutHeaderAndFooter /// /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} /// - public DebugSystemTarget() : base() + public DebugSystemTarget() { } diff --git a/src/NLog/Targets/DebuggerTarget.cs b/src/NLog/Targets/DebuggerTarget.cs index f452a20b49..f269322eab 100644 --- a/src/NLog/Targets/DebuggerTarget.cs +++ b/src/NLog/Targets/DebuggerTarget.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 - namespace NLog.Targets { using System.Diagnostics; @@ -65,7 +63,7 @@ public sealed class DebuggerTarget : TargetWithLayoutHeaderAndFooter /// /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} /// - public DebuggerTarget() : base() + public DebuggerTarget() { } @@ -125,5 +123,3 @@ protected override void Write(LogEventInfo logEvent) } } } - -#endif diff --git a/src/NLog/Targets/DefaultJsonSerializer.cs b/src/NLog/Targets/DefaultJsonSerializer.cs index efaec1e78e..a7b3221acb 100644 --- a/src/NLog/Targets/DefaultJsonSerializer.cs +++ b/src/NLog/Targets/DefaultJsonSerializer.cs @@ -31,16 +31,16 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Text; -using NLog.Internal; - namespace NLog.Targets { + using System; + using System.Collections; + using System.Collections.Generic; + using System.ComponentModel; + using System.Globalization; + using System.Text; + using NLog.Internal; + /// /// Default class for serialization of values to JSON format. /// @@ -61,7 +61,8 @@ public class DefaultJsonSerializer : IJsonConverter /// [Obsolete("Instead use ResolveService() in Layout / Target. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] - public static DefaultJsonSerializer Instance { get; } = new DefaultJsonSerializer(null); + public static DefaultJsonSerializer Instance => _instance ?? (_instance = new DefaultJsonSerializer(LogManager.LogFactory.ServiceRepository)); + private static DefaultJsonSerializer? _instance; /// /// Private. Use @@ -95,9 +96,10 @@ public string SerializeObject(object value, JsonSerializeOptions options) } else if (value is string str) { + var escapeUnicode = options.EscapeUnicode; foreach (var chr in str) { - if (RequiresJsonEscape(chr, options.EscapeUnicode, options.EscapeForwardSlash)) + if (RequiresJsonEscape(chr, escapeUnicode)) { StringBuilder sb = new StringBuilder(str.Length + 4); sb.Append('"'); @@ -108,39 +110,38 @@ public string SerializeObject(object value, JsonSerializeOptions options) } return QuoteValue(str); } - else + else { - IConvertible convertibleValue = value as IConvertible; - TypeCode objTypeCode = convertibleValue?.GetTypeCode() ?? TypeCode.Object; - if (objTypeCode != TypeCode.Object && objTypeCode != TypeCode.Char) + if (value is IConvertible convertibleValue) { - Enum enumValue; - if (!options.EnumAsInteger && IsNumericTypeCode(objTypeCode, false) && (enumValue = value as Enum) != null) - { - return QuoteValue(EnumAsString(enumValue)); - } - else + TypeCode objTypeCode = convertibleValue.GetTypeCode(); + if (objTypeCode != TypeCode.Object) { - string xmlStr = XmlHelper.XmlConvertToString(convertibleValue, objTypeCode); - if (SkipQuotes(convertibleValue, objTypeCode)) + if (!options.EnumAsInteger && IsNumericTypeCode(objTypeCode, false) && convertibleValue is Enum enumValue) { - return xmlStr; + return QuoteValue(EnumAsString(enumValue)); } else { - return QuoteValue(xmlStr); + string xmlStr = XmlHelper.XmlConvertToString(convertibleValue, objTypeCode); + if (SkipQuotes(convertibleValue, objTypeCode)) + { + return xmlStr; + } + else + { + return QuoteValue(xmlStr); + } } } } - else + + StringBuilder sb = new StringBuilder(); + if (!SerializeObject(value, sb, options)) { - StringBuilder sb = new StringBuilder(); - if (!SerializeObject(value, sb, options)) - { - return null; - } - return sb.ToString(); + return string.Empty; } + return sb.ToString(); } } @@ -150,7 +151,7 @@ public string SerializeObject(object value, JsonSerializeOptions options) /// The object to serialize to JSON. /// Write the resulting JSON to this destination. /// Object serialized successfully (true/false). - public bool SerializeObject(object value, StringBuilder destination) + public bool SerializeObject(object? value, StringBuilder destination) { return SerializeObject(value, destination, DefaultSerializerOptions); } @@ -162,7 +163,7 @@ public bool SerializeObject(object value, StringBuilder destination) /// Write the resulting JSON to this destination. /// serialization options /// Object serialized successfully (true/false). - public bool SerializeObject(object value, StringBuilder destination, JsonSerializeOptions options) + public bool SerializeObject(object? value, StringBuilder destination, JsonSerializeOptions options) { return SerializeObject(value, destination, options, default(SingleItemOptimizedHashSet), 0); } @@ -176,7 +177,7 @@ public bool SerializeObject(object value, StringBuilder destination, JsonSeriali /// The objects in path (Avoid cyclic reference loop). /// The current depth (level) of recursion. /// Object serialized successfully (true/false). - private bool SerializeObject(object value, StringBuilder destination, JsonSerializeOptions options, SingleItemOptimizedHashSet objectsInPath, int depth) + private bool SerializeObject(object? value, StringBuilder destination, JsonSerializeOptions options, SingleItemOptimizedHashSet objectsInPath, int depth) { int originalLength = destination.Length; @@ -196,7 +197,7 @@ private bool SerializeObject(object value, StringBuilder destination, JsonSerial } } - private bool SerializeObjectWithReflection(object value, StringBuilder destination, JsonSerializeOptions options, ref SingleItemOptimizedHashSet objectsInPath, int depth) + private bool SerializeObjectWithReflection(object? value, StringBuilder destination, JsonSerializeOptions options, ref SingleItemOptimizedHashSet objectsInPath, int depth) { int originalLength = destination.Length; if (originalLength > MaxJsonLength) @@ -204,7 +205,7 @@ private bool SerializeObjectWithReflection(object value, StringBuilder destinati return false; } - if (objectsInPath.Contains(value)) + if (value is null || objectsInPath.Contains(value)) { return false; } @@ -218,7 +219,7 @@ private bool SerializeObjectWithReflection(object value, StringBuilder destinati } } - if (value is IEnumerable enumerable) + if (value is IEnumerable collection) { if (_objectReflectionCache.TryLookupExpandoObject(value, out var objectPropertyList)) { @@ -228,7 +229,7 @@ private bool SerializeObjectWithReflection(object value, StringBuilder destinati { using (StartCollectionScope(ref objectsInPath, value)) { - SerializeCollectionObject(enumerable, destination, options, objectsInPath, depth); + SerializeCollectionObject(collection, destination, options, objectsInPath, depth); return true; } } @@ -240,14 +241,16 @@ private bool SerializeObjectWithReflection(object value, StringBuilder destinati } } - private bool SerializeSimpleObjectValue(object value, StringBuilder destination, JsonSerializeOptions options, bool forceToString = false) + private bool SerializeSimpleObjectValue(object? value, StringBuilder destination, JsonSerializeOptions options, bool forceToString = false) { - var convertibleValue = value as IConvertible; - var objTypeCode = convertibleValue?.GetTypeCode() ?? (value is null ? TypeCode.Empty : TypeCode.Object); - if (objTypeCode != TypeCode.Object) + if (value is IConvertible convertibleValue) { - SerializeSimpleTypeCodeValue(convertibleValue, objTypeCode, destination, options, forceToString); - return true; + var objTypeCode = convertibleValue.GetTypeCode(); + if (objTypeCode != TypeCode.Object) + { + SerializeSimpleTypeCodeValue(convertibleValue, objTypeCode, destination, options, forceToString); + return true; + } } if (value is IFormattable formattable) @@ -258,20 +261,25 @@ private bool SerializeSimpleObjectValue(object value, StringBuilder destination, return true; } - destination.Append('"'); -#if NETSTANDARD - int startPos = destination.Length; - destination.AppendFormat(CultureInfo.InvariantCulture, "{0}", formattable); // Support ISpanFormattable - PerformJsonEscapeWhenNeeded(destination, startPos, options.EscapeUnicode, options.EscapeForwardSlash); -#else +#if NETFRAMEWORK var str = formattable.ToString(null, CultureInfo.InvariantCulture); AppendStringEscape(destination, str, options); +#else + int startPos = destination.Length; + destination.AppendFormat(CultureInfo.InvariantCulture, "{0}", formattable); // Support ISpanFormattable + PerformJsonEscapeWhenNeeded(destination, startPos, options.EscapeUnicode); #endif destination.Append('"'); return true; } + if (value is null) + { + SerializeSimpleTypeCodeValue(null, TypeCode.Empty, destination, options, forceToString); + return true; + } + return false; // Not simple } @@ -424,7 +432,7 @@ private bool SerializeObjectPropertyList(object value, ref ObjectReflectionCache return SerializeObjectAsString(value, destination, options); } - private void SerializeSimpleTypeCodeValue(IConvertible value, TypeCode objTypeCode, StringBuilder destination, JsonSerializeOptions options, bool forceToString = false) + private void SerializeSimpleTypeCodeValue(IConvertible? value, TypeCode objTypeCode, StringBuilder destination, JsonSerializeOptions options, bool forceToString = false) { if (objTypeCode == TypeCode.Empty || value is null) { @@ -502,13 +510,12 @@ private static void QuoteValue(StringBuilder destination, string value) private string EnumAsString(Enum value) { - string textValue; - if (!_enumCache.TryGetValue(value, out textValue)) + if (!_enumCache.TryGetValue(value, out var textValue)) { textValue = Convert.ToString(value, CultureInfo.InvariantCulture); _enumCache.TryAddValue(value, textValue); } - return textValue; + return textValue ?? string.Empty; } /// @@ -575,7 +582,7 @@ private static bool IsNumericTypeCode(TypeCode objTypeCode, bool includeDecimals /// JSON escaped string private static void AppendStringEscape(StringBuilder destination, string text, JsonSerializeOptions options) { - AppendStringEscape(destination, text, options.EscapeUnicode, options.EscapeForwardSlash); + AppendStringEscape(destination, text, options.EscapeUnicode); } /// @@ -584,9 +591,9 @@ private static void AppendStringEscape(StringBuilder destination, string text, J /// Destination Builder /// Input string /// Should non-ASCII characters be encoded - /// + /// /// JSON escaped string - internal static void AppendStringEscape(StringBuilder destination, string text, bool escapeUnicode, bool escapeForwardSlash) + internal static void AppendStringEscape(StringBuilder destination, string text, bool escapeUnicode) { if (string.IsNullOrEmpty(text)) return; @@ -594,9 +601,9 @@ internal static void AppendStringEscape(StringBuilder destination, string text, int i = 0; foreach (var chr in text) { - if (RequiresJsonEscape(chr, escapeUnicode, escapeForwardSlash)) + if (RequiresJsonEscape(chr, escapeUnicode)) { - AppendStringEscape(destination, text, escapeUnicode, escapeForwardSlash, i); + AppendStringEscape(destination, text, escapeUnicode, i); return; } @@ -606,14 +613,14 @@ internal static void AppendStringEscape(StringBuilder destination, string text, destination.Append(text); } - private static void AppendStringEscape(StringBuilder destination, string text, bool escapeUnicode, bool escapeForwardSlash, int startIndex) + private static void AppendStringEscape(StringBuilder destination, string text, bool escapeUnicode, int startIndex) { destination.Append(text, 0, startIndex); for (int i = startIndex; i < text.Length; ++i) { char ch = text[i]; - if (!RequiresJsonEscape(ch, escapeUnicode, escapeForwardSlash)) + if (!RequiresJsonEscape(ch, escapeUnicode)) { destination.Append(ch); continue; @@ -633,17 +640,6 @@ private static void AppendStringEscape(StringBuilder destination, string text, b destination.Append("\\b"); break; - case '/': - if (escapeForwardSlash) - { - destination.Append("\\/"); - } - else - { - destination.Append(ch); - } - break; - case '\r': destination.Append("\\r"); break; @@ -667,29 +663,27 @@ private static void AppendStringEscape(StringBuilder destination, string text, b } } - internal static void PerformJsonEscapeWhenNeeded(StringBuilder builder, int startPos, bool escapeUnicode, bool escapeForwardSlash) + internal static void PerformJsonEscapeWhenNeeded(StringBuilder builder, int startPos, bool escapeUnicode) { var builderLength = builder.Length; for (int i = startPos; i < builderLength; ++i) { - if (RequiresJsonEscape(builder[i], escapeUnicode, escapeForwardSlash)) + if (RequiresJsonEscape(builder[i], escapeUnicode)) { var str = builder.ToString(startPos, builder.Length - startPos); builder.Length = startPos; - Targets.DefaultJsonSerializer.AppendStringEscape(builder, str, escapeUnicode, escapeForwardSlash); + Targets.DefaultJsonSerializer.AppendStringEscape(builder, str, escapeUnicode); break; } } } - internal static bool RequiresJsonEscape(char ch, bool escapeUnicode, bool escapeForwardSlash) + internal static bool RequiresJsonEscape(char ch, bool escapeUnicode) { if (ch < 32) return true; if (ch > 127) return escapeUnicode; - if (ch == '/') - return escapeForwardSlash; return ch == '"' || ch == '\\'; } @@ -698,7 +692,8 @@ private bool SerializeObjectProperties(ObjectReflectionCache.ObjectPropertyList { destination.Append('{'); - bool first = true; + string? jsonPropertyDelimeter = null; + foreach (var propertyValue in objectPropertyList) { var originalLength = destination.Length; @@ -708,10 +703,7 @@ private bool SerializeObjectProperties(ObjectReflectionCache.ObjectPropertyList if (!propertyValue.HasNameAndValue) continue; - if (!first) - { - destination.Append(", "); - } + destination.Append(jsonPropertyDelimeter); QuoteValue(destination, propertyValue.Name); destination.Append(':'); @@ -719,20 +711,19 @@ private bool SerializeObjectProperties(ObjectReflectionCache.ObjectPropertyList var objTypeCode = propertyValue.TypeCode; if (objTypeCode != TypeCode.Object) { - SerializeSimpleTypeCodeValue((IConvertible)propertyValue.Value, objTypeCode, destination, options); - first = false; + SerializeSimpleTypeCodeValue((IConvertible?)propertyValue.Value, objTypeCode, destination, options); } else { if (!SerializeObject(propertyValue.Value, destination, options, objectsInPath, depth + 1)) { destination.Length = originalLength; - } - else - { - first = false; + continue; } } + + if (jsonPropertyDelimeter is null) + jsonPropertyDelimeter = options.SuppressSpaces ? "," : ", "; } catch { diff --git a/src/NLog/Targets/EventLogTarget.cs b/src/NLog/Targets/EventLogTarget.cs index d9e3f3ae19..6a2ae5bbdd 100644 --- a/src/NLog/Targets/EventLogTarget.cs +++ b/src/NLog/Targets/EventLogTarget.cs @@ -74,8 +74,9 @@ public class EventLogTarget : TargetWithLayout, IInstallable /// Initializes a new instance of the class. /// public EventLogTarget() - : this(null, null) { + Source = AppDomain.CurrentDomain.FriendlyName; + _eventLogWrapper = new EventLogWrapper(); } /// @@ -83,7 +84,7 @@ public EventLogTarget() /// /// Name of the target. public EventLogTarget(string name) - : this(null, null) + : this() { Name = name; } @@ -93,67 +94,60 @@ public EventLogTarget(string name) /// internal EventLogTarget(IEventLogWrapper eventLogWrapper, string sourceName) { - _eventLogWrapper = eventLogWrapper ?? new EventLogWrapper(); - Source = sourceName ?? AppDomain.CurrentDomain.FriendlyName; + _eventLogWrapper = eventLogWrapper; + Source = string.IsNullOrEmpty(sourceName) ? AppDomain.CurrentDomain.FriendlyName : sourceName; } /// /// Gets or sets the name of the machine on which Event Log service is running. /// + /// Default: /// - public string MachineName { get; set; } = "."; + public Layout MachineName { get; set; } = Layout.Empty; /// /// Gets or sets the layout that renders event ID. /// + /// Default: ${event-properties:item=EventId} /// public Layout EventId { get; set; } = "${event-properties:item=EventId}"; /// /// Gets or sets the layout that renders event Category. /// + /// Default: /// - public Layout Category { get; set; } + public Layout? Category { get; set; } /// /// Optional entry type. When not set, or when not convertible to then determined by /// + /// Default: /// - public Layout EntryType { get; set; } + public Layout? EntryType { get; set; } /// /// Gets or sets the value to be used as the event Source. /// /// - /// By default this is the friendly name of the current AppDomain. + /// [Required] Default: /// /// - [RequiredParameter] - public Layout Source { get; set; } + public Layout Source { get; set; } = Layout.Empty; /// /// Gets or sets the name of the Event Log to write to. This can be System, Application or any user-defined name. /// + /// [Required] Default: Application /// - public string Log { get; set; } = "Application"; + public Layout Log { get; set; } = "Application"; /// /// Gets or sets the message length limit to write to the Event Log. /// - /// MaxMessageLength cannot be zero or negative + /// Default: /// - public int MaxMessageLength - { - get => _maxMessageLength; - set - { - if (value <= 0) - throw new ArgumentException("MaxMessageLength cannot be zero or negative."); - - _maxMessageLength = value; - } - } - private int _maxMessageLength = EventLogMaxMessageLength; + public Layout MaxMessageLength { get; set; } = EventLogMaxMessageLength; /// /// Gets or sets the maximum Event log size in kilobytes. @@ -163,22 +157,12 @@ public int MaxMessageLength /// If null, the value will not be specified while creating the Event log. /// /// - public long? MaxKilobytes - { - get => _maxKilobytes; - set - { - if (value != null && (value < 64 || value > 4194240 || (value % 64 != 0))) // Event log API restrictions - throw new ArgumentException("MaxKilobytes must be a multiple of 64, and between 64 and 4194240"); - - _maxKilobytes = value; - } - } - private long? _maxKilobytes; + public Layout? MaxKilobytes { get; set; } /// /// Gets or sets the action to take if the message is larger than the option. /// + /// Default: /// public EventLogTargetOverflowAction OnOverflow { get; set; } = EventLogTargetOverflowAction.Truncate; @@ -199,14 +183,13 @@ public void Install(InstallationContext installationContext) public void Uninstall(InstallationContext installationContext) { var fixedSource = GetFixedSource(); - if (string.IsNullOrEmpty(fixedSource)) { InternalLogger.Debug("{0}: Skipping removing of event source because it contains layout renderers", this); } else { - _eventLogWrapper.DeleteEventSource(fixedSource, MachineName); + _eventLogWrapper.DeleteEventSource(fixedSource, "."); } } @@ -220,13 +203,15 @@ public void Uninstall(InstallationContext installationContext) public bool? IsInstalled(InstallationContext installationContext) { var fixedSource = GetFixedSource(); - - if (!string.IsNullOrEmpty(fixedSource)) + if (string.IsNullOrEmpty(fixedSource)) + { + InternalLogger.Debug("{0}: Unclear if event source exists because it contains layout renderers", this); + } + else { - return _eventLogWrapper.SourceExists(fixedSource, MachineName); + return _eventLogWrapper.SourceExists(fixedSource, "."); } - InternalLogger.Debug("{0}: Unclear if event source exists because it contains layout renderers", this); - return null; //unclear! + return null; } /// @@ -234,6 +219,13 @@ protected override void InitializeTarget() { base.InitializeTarget(); + if (Source is null || ReferenceEquals(Source, Layout.Empty)) + throw new NLogConfigurationException("EventLogTarget Source-property must be assigned. Source is needed for EventLog writing."); + + var maxKilobytes = MaxKilobytes?.IsFixed == true ? MaxKilobytes.FixedValue : 0; + if (maxKilobytes > 0 && (maxKilobytes < 64 || maxKilobytes > 4194240 || (maxKilobytes % 64 != 0))) // Event log API restrictions + throw new NLogConfigurationException("EventLogTarget MaxKilobytes must be a multiple of 64, and between 64 and 4194240"); + CreateEventSourceIfNeeded(GetFixedSource(), false); } @@ -254,20 +246,32 @@ protected override void Write(LogEventInfo logEvent) return; } + var eventLogName = RenderLogEvent(Log, logEvent); + if (string.IsNullOrEmpty(eventLogName)) + { + InternalLogger.Warn("{0}: WriteEntry discarded because Log rendered as empty string", this); + return; + } + + var eventLogMachine = RenderLogEvent(MachineName, logEvent); + if (string.IsNullOrEmpty(eventLogMachine)) + eventLogMachine = "."; + // limitation of EventLog API - if (message.Length > MaxMessageLength) + var maxMessageLength = RenderLogEvent(MaxMessageLength, logEvent, EventLogMaxMessageLength); + if (maxMessageLength > 0 && message.Length > maxMessageLength) { if (OnOverflow == EventLogTargetOverflowAction.Truncate) { - message = message.Substring(0, MaxMessageLength); - WriteEntry(eventLogSource, message, entryType, eventId, category); + message = message.Substring(0, maxMessageLength); + WriteEntry(eventLogSource, eventLogName, eventLogMachine, message, entryType, eventId, category); } else if (OnOverflow == EventLogTargetOverflowAction.Split) { - for (int offset = 0; offset < message.Length; offset += MaxMessageLength) + for (int offset = 0; offset < message.Length; offset += maxMessageLength) { - string chunk = message.Substring(offset, Math.Min(MaxMessageLength, (message.Length - offset))); - WriteEntry(eventLogSource, chunk, entryType, eventId, category); + string chunk = message.Substring(offset, Math.Min(maxMessageLength, (message.Length - offset))); + WriteEntry(eventLogSource, eventLogName, eventLogMachine, chunk, entryType, eventId, category); } } else if (OnOverflow == EventLogTargetOverflowAction.Discard) @@ -278,34 +282,34 @@ protected override void Write(LogEventInfo logEvent) } else { - WriteEntry(eventLogSource, message, entryType, eventId, category); + WriteEntry(eventLogSource, eventLogName, eventLogMachine, message, entryType, eventId, category); } } - private void WriteEntry(string eventLogSource, string message, EventLogEntryType entryType, int eventId, short category) + private void WriteEntry(string eventLogSource, string eventLogName, string eventLogMachine, string message, EventLogEntryType entryType, int eventId, short category) { var isCacheUpToDate = _eventLogWrapper.IsEventLogAssociated && - _eventLogWrapper.Log == Log && - _eventLogWrapper.MachineName == MachineName && - _eventLogWrapper.Source == eventLogSource; + _eventLogWrapper.Source == eventLogSource && + string.Equals(_eventLogWrapper.Log, eventLogName, StringComparison.OrdinalIgnoreCase) && + string.Equals(_eventLogWrapper.MachineName, eventLogMachine, StringComparison.OrdinalIgnoreCase); if (!isCacheUpToDate) { - InternalLogger.Debug("{0}: Refresh EventLog Source {1} and Log {2}", this, eventLogSource, Log); + InternalLogger.Debug("{0}: Refresh EventLog Source {1} and Log {2}", this, eventLogSource, eventLogName); - _eventLogWrapper.AssociateNewEventLog(Log, MachineName, eventLogSource); + _eventLogWrapper.AssociateNewEventLog(eventLogName, eventLogMachine, eventLogSource); try { - if (!_eventLogWrapper.SourceExists(eventLogSource, MachineName)) + if (!_eventLogWrapper.SourceExists(eventLogSource, eventLogMachine)) { InternalLogger.Warn("{0}: Source {1} does not exist", this, eventLogSource); } else { - var currentLogName = _eventLogWrapper.LogNameFromSourceName(eventLogSource, MachineName); - if (!currentLogName.Equals(Log, StringComparison.OrdinalIgnoreCase)) + var currentLogName = _eventLogWrapper.LogNameFromSourceName(eventLogSource, eventLogMachine); + if (!currentLogName.Equals(eventLogName, StringComparison.OrdinalIgnoreCase)) { - InternalLogger.Debug("{0}: Source {1} should be mapped to Log {2}, but EventLog.LogNameFromSourceName returns {3}", this, eventLogSource, Log, currentLogName); + InternalLogger.Debug("{0}: Source {1} should be mapped to Log {2}, but EventLog.LogNameFromSourceName returns {3}", this, eventLogSource, eventLogName, currentLogName); } } } @@ -314,7 +318,7 @@ private void WriteEntry(string eventLogSource, string message, EventLogEntryType if (LogManager.ThrowExceptions) throw; - InternalLogger.Warn(ex, "{0}: Exception thrown when checking if Source {1} and LogName {2} are valid", this, eventLogSource, Log); + InternalLogger.Warn(ex, "{0}: Exception thrown when checking if Source {1} and Log {2} are valid", this, eventLogSource, eventLogName); } } @@ -353,11 +357,14 @@ private EventLogEntryType GetEntryType(LogEventInfo logEvent) /// Internal for unit tests internal string GetFixedSource() { - if (Source is SimpleLayout simpleLayout && simpleLayout.IsFixedText) + if (Source is SimpleLayout simpleLayout && simpleLayout.IsFixedText && Log is SimpleLayout logNameLayout && logNameLayout.IsFixedText) { - return simpleLayout.FixedText; + if (MachineName is null || (MachineName is SimpleLayout machineLayout && machineLayout.IsFixedText && (".".Equals(machineLayout.FixedText) || string.IsNullOrEmpty(machineLayout.FixedText)))) + { + return simpleLayout.FixedText ?? string.Empty; + } } - return null; + return string.Empty; } /// @@ -374,46 +381,52 @@ private void CreateEventSourceIfNeeded(string fixedSource, bool alwaysThrowError return; } + var eventLogName = RenderLogEvent(Log, LogEventInfo.CreateNullEvent()); + var eventLogMachine = RenderLogEvent(MachineName, LogEventInfo.CreateNullEvent()); + if (string.IsNullOrEmpty(eventLogMachine)) + eventLogMachine = "."; + var maxKilobytes = RenderLogEvent(MaxKilobytes, LogEventInfo.CreateNullEvent(), 0); + // if we throw anywhere, we remain non-operational try { - if (_eventLogWrapper.SourceExists(fixedSource, MachineName)) + if (_eventLogWrapper.SourceExists(fixedSource, eventLogMachine)) { - string currentLogName = _eventLogWrapper.LogNameFromSourceName(fixedSource, MachineName); - if (!currentLogName.Equals(Log, StringComparison.OrdinalIgnoreCase)) + string currentLogName = _eventLogWrapper.LogNameFromSourceName(fixedSource, eventLogMachine); + if (!currentLogName.Equals(eventLogName, StringComparison.OrdinalIgnoreCase)) { - InternalLogger.Debug("{0}: Updating source {1} to use log {2}, instead of {3} (Computer restart is needed)", this, fixedSource, Log, currentLogName); + InternalLogger.Debug("{0}: Updating source {1} to use log {2}, instead of {3} (Computer restart is needed)", this, fixedSource, eventLogName, currentLogName); // re-create the association between Log and Source - _eventLogWrapper.DeleteEventSource(fixedSource, MachineName); + _eventLogWrapper.DeleteEventSource(fixedSource, eventLogMachine); - var eventSourceCreationData = new EventSourceCreationData(fixedSource, Log) + var eventSourceCreationData = new EventSourceCreationData(fixedSource, eventLogName) { - MachineName = MachineName + MachineName = eventLogMachine }; _eventLogWrapper.CreateEventSource(eventSourceCreationData); } } else { - InternalLogger.Debug("{0}: Creating source {1} to use log {2}", this, fixedSource, Log); - var eventSourceCreationData = new EventSourceCreationData(fixedSource, Log) + InternalLogger.Debug("{0}: Creating source {1} to use log {2}", this, fixedSource, eventLogName); + var eventSourceCreationData = new EventSourceCreationData(fixedSource, eventLogName) { - MachineName = MachineName + MachineName = eventLogMachine }; _eventLogWrapper.CreateEventSource(eventSourceCreationData); } - _eventLogWrapper.AssociateNewEventLog(Log, MachineName, fixedSource); + _eventLogWrapper.AssociateNewEventLog(eventLogName, eventLogMachine, fixedSource); - if (MaxKilobytes.HasValue && _eventLogWrapper.MaximumKilobytes < MaxKilobytes) + if (maxKilobytes > 0 && _eventLogWrapper.MaximumKilobytes < maxKilobytes) { - _eventLogWrapper.MaximumKilobytes = MaxKilobytes.Value; + _eventLogWrapper.MaximumKilobytes = maxKilobytes; } } catch (Exception exception) { - InternalLogger.Error(exception, "{0}: Error when connecting to EventLog. Source={1} in Log={2}", this, fixedSource, Log); + InternalLogger.Error(exception, "{0}: Error when connecting to EventLog. Source={1} in Log={2}", this, fixedSource, eventLogName); if (alwaysThrowError || LogManager.ThrowExceptions) { throw; @@ -495,23 +508,27 @@ internal interface IEventLogWrapper /// private sealed class EventLogWrapper : IEventLogWrapper, IDisposable { - private EventLog _windowsEventLog; + private EventLog? _windowsEventLog; - public string Source { get; private set; } + public string Source { get; private set; } = string.Empty; - public string Log { get; private set; } + public string Log { get; private set; } = string.Empty; - public string MachineName { get; private set; } + public string MachineName { get; private set; } = string.Empty; public long MaximumKilobytes { - get => _windowsEventLog.MaximumKilobytes; - set => _windowsEventLog.MaximumKilobytes = value; + get => _windowsEventLog?.MaximumKilobytes ?? 0; + set + { + if (_windowsEventLog != null) + _windowsEventLog.MaximumKilobytes = value; + } } public bool IsEventLogAssociated => _windowsEventLog != null; public void WriteEntry(string message, EventLogEntryType entryType, int eventId, short category) => - _windowsEventLog.WriteEntry(message, entryType, eventId, category); + _windowsEventLog?.WriteEntry(message, entryType, eventId, category); /// /// Creates a new association with an instance of Windows . diff --git a/src/NLog/Targets/EventLogTargetOverflowAction.cs b/src/NLog/Targets/EventLogTargetOverflowAction.cs index 3830e62e72..f863a73855 100644 --- a/src/NLog/Targets/EventLogTargetOverflowAction.cs +++ b/src/NLog/Targets/EventLogTargetOverflowAction.cs @@ -32,7 +32,7 @@ // -#if !NETSTANDARD || WindowsEventLogPackage +#if NETFRAMEWORK || WindowsEventLogPackage namespace NLog.Targets { diff --git a/tests/NLog.UnitTests/Mocks/AppDomainMock.cs b/src/NLog/Targets/FileAppenders/DiscardAllFileAppender.cs similarity index 64% rename from tests/NLog.UnitTests/Mocks/AppDomainMock.cs rename to src/NLog/Targets/FileAppenders/DiscardAllFileAppender.cs index 7ce51aa937..868d6b1df0 100644 --- a/tests/NLog.UnitTests/Mocks/AppDomainMock.cs +++ b/src/NLog/Targets/FileAppenders/DiscardAllFileAppender.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen // // All rights reserved. @@ -31,33 +31,45 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Collections.Generic; -using System.Reflection; -using NLog.Internal.Fakeables; - -namespace NLog.UnitTests.Mocks +namespace NLog.Targets.FileAppenders { - [Obsolete("For unit testing only. Marked obsolete on NLog 5.0")] - public class AppDomainMock : IAppDomain + using System; + + internal sealed class DiscardAllFileAppender : IFileAppender { - public AppDomainMock(string baseDirectory) + public string FilePath { get; } + + public DateTime OpenStreamTime { get; } + + public DateTime FileLastModified => OpenStreamTime; + + public DateTime NextArchiveTime => DateTime.MaxValue; + + public long FileSize => 0; + + public DiscardAllFileAppender(string filePath) { - BaseDirectory = baseDirectory; + FilePath = filePath; + OpenStreamTime = Time.TimeSource.Current.Time; } + public void Write(byte[] buffer, int offset, int count) + { + // SONAR: Nothing to write + } - public string BaseDirectory { get; set; } - public string ConfigurationFile { get; set; } - public IEnumerable PrivateBinPath { get; set; } - public string FriendlyName { get; set; } - public int Id { get; set; } - public IEnumerable GetAssemblies() + public void Flush() { - throw new NotImplementedException(); + // SONAR: Nothing to flush } - event EventHandler IAppDomain.ProcessExit { add { /* Nothing */ } remove { /* Nothing */ } } - event EventHandler IAppDomain.DomainUnload { add { /* Nothing */ } remove { /* Nothing */ } } + public void Dispose() + { + // SONAR: Nothing to close + } + + public bool VerifyFileExists() => true; + + public override string ToString() => FilePath; } } diff --git a/src/NLog/Targets/FileAppenders/ExclusiveFileLockingAppender.cs b/src/NLog/Targets/FileAppenders/ExclusiveFileLockingAppender.cs new file mode 100644 index 0000000000..b44a08ea9a --- /dev/null +++ b/src/NLog/Targets/FileAppenders/ExclusiveFileLockingAppender.cs @@ -0,0 +1,212 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.FileAppenders +{ + using System; + using System.IO; + using NLog.Common; + using NLog.Internal; + +#if NETFRAMEWORK + [System.Security.SecuritySafeCritical] +#endif + internal sealed class ExclusiveFileLockingAppender : IFileAppender + { + private readonly FileTarget _fileTarget; + private readonly string _filePath; + private Stream _fileStream; + private int _lastFileDeletedCheck; + private long? _countedFileSize; + + public string FilePath => _filePath; + + public DateTime OpenStreamTime { get; } + + public DateTime FileLastModified { get; private set; } + + private DateTime FileBirthTime + { + get => _fileBirthTime ?? OpenStreamTime; + set => _fileBirthTime = value; + } + private DateTime? _fileBirthTime; + + public DateTime NextArchiveTime + { + get + { + var fileBirthTime = FileBirthTime; + if (_lastFileBirthTime != fileBirthTime) + { + _nextArchiveTime = FileTarget.CalculateNextArchiveEventTime(_fileTarget.ArchiveEvery, fileBirthTime); + _lastFileBirthTime = fileBirthTime; + } + return _nextArchiveTime; + } + } + private DateTime _nextArchiveTime; + private DateTime _lastFileBirthTime; + + public long FileSize => _countedFileSize ?? _fileStream.Length; + + public ExclusiveFileLockingAppender(FileTarget fileTarget, string filePath) + { + _fileTarget = fileTarget; + _filePath = filePath; + OpenStreamTime = Time.TimeSource.Current.Time; + _lastFileDeletedCheck = Environment.TickCount; + + var fileInfoSize = RefreshFileBirthTimeUtc(true); + + _fileStream = _fileTarget.CreateFileStreamWithRetry(this, fileTarget.BufferSize, initialFileOpen: true); + _countedFileSize = RefreshCountedFileSize(_fileStream, fileInfoSize); + } + + private long RefreshFileBirthTimeUtc(bool forceRefresh) + { + FileLastModified = NLog.Time.TimeSource.Current.Time; + + if (_fileTarget.ArchiveFileName is null && _fileTarget.ArchiveEvery == FileArchivePeriod.None && _fileTarget.ArchiveAboveSize <= 0) + return 0; + + try + { + FileInfo fileInfo = new FileInfo(_filePath); + long fileInfoSize = fileInfo.Exists ? fileInfo.Length : 0; + if (fileInfoSize > 0) + { + var fileBirthTimeUtc = FileInfoHelper.LookupValidFileCreationTimeUtc(fileInfo) ?? DateTime.MinValue; + var fileBirthTime = fileBirthTimeUtc != DateTime.MinValue ? NLog.Time.TimeSource.Current.FromSystemTime(fileBirthTimeUtc) : OpenStreamTime; + if (!forceRefresh && fileBirthTime.Date < FileBirthTime.Date) + fileBirthTime = FileBirthTime; + FileBirthTime = fileBirthTime; + FileLastModified = NLog.Time.TimeSource.Current.FromSystemTime(fileInfo.LastWriteTimeUtc); + } + + return fileInfoSize; + } + catch (Exception ex) + { + InternalLogger.Debug(ex, "{0}: Failed to refresh BirthTime for file: '{1}'", _fileTarget, _filePath); + return 0; + } + } + + public void Write(byte[] buffer, int offset, int count) + { + var lastFileSizeCheck = Environment.TickCount - _lastFileDeletedCheck; + if (lastFileSizeCheck > 1000 || lastFileSizeCheck < -1000) + { + MonitorFileHasBeenDeleted(); + _lastFileDeletedCheck = Environment.TickCount; + FileLastModified = NLog.Time.TimeSource.Current.Time; + } + + _fileStream.Write(buffer, offset, count); + if (_countedFileSize.HasValue) + _countedFileSize += count; + } + + public void Flush() + { + _fileStream.Flush(); + } + + public void Dispose() + { + SafeCloseFile(_filePath, _fileStream); + } + + public bool VerifyFileExists() + { + return SafeFileExists(_filePath); + } + + private void MonitorFileHasBeenDeleted() + { + if (!SafeFileExists(_filePath)) + { + InternalLogger.Debug("{0}: Recreating FileStream because no longer File.Exists: '{1}'", _fileTarget, _filePath); + SafeCloseFile(_filePath, _fileStream); + _fileStream = _fileTarget.CreateFileStreamWithRetry(this, _fileTarget.BufferSize, initialFileOpen: false); + var fileInfoSize = RefreshFileBirthTimeUtc(false); + _countedFileSize = RefreshCountedFileSize(_fileStream, fileInfoSize); + } + } + + private long? RefreshCountedFileSize(Stream fileStream, long fileInfoSize) + { + if (_fileTarget.ArchiveAboveSize > 0) + { + if (_fileTarget.GetType().Equals(typeof(FileTarget))) + return fileStream.Length; + + if (fileStream.GetType().Equals(typeof(FileStream))) + return null; // NLog.Targets.AtomicFile should not use counted filesize + + return fileInfoSize; // NLog.Targets.GZipFile should use counted uncompressed filesize + } + + return null; + } + + private void SafeCloseFile(string filepath, Stream? fileStream) + { + try + { + var stream = fileStream; + stream?.Dispose(); + } + catch (Exception ex) + { + InternalLogger.Error(ex, "{0}: Failed to close file: '{1}'", _fileTarget, filepath); + } + } + + private bool SafeFileExists(string filepath) + { + try + { + return File.Exists(filepath); + } + catch (Exception ex) + { + InternalLogger.Error(ex, "{0}: Failed to check if File.Exists: '{1}'", _fileTarget, filepath); + return false; + } + } + + public override string ToString() => _filePath; + } +} diff --git a/src/NLog/Targets/FileAppenders/IFileAppender.cs b/src/NLog/Targets/FileAppenders/IFileAppender.cs new file mode 100644 index 0000000000..26d42fbeaa --- /dev/null +++ b/src/NLog/Targets/FileAppenders/IFileAppender.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.FileAppenders +{ + using System; + + /// + /// Handles the actual file-operations on disk + /// + internal interface IFileAppender : IDisposable + { + string FilePath { get; } + long FileSize { get; } + + DateTime OpenStreamTime { get; } + DateTime FileLastModified { get; } + DateTime NextArchiveTime { get; } + + /// + /// Writes the specified bytes to a file. + /// + /// The bytes array. + /// The bytes array offset. + /// The number of bytes. + void Write(byte[] buffer, int offset, int count); + + void Flush(); + + bool VerifyFileExists(); + } +} diff --git a/src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs b/src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs new file mode 100644 index 0000000000..a1e336e9d2 --- /dev/null +++ b/src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs @@ -0,0 +1,144 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.FileAppenders +{ + using System; + using System.IO; + using NLog.Internal; + +#if NETFRAMEWORK + [System.Security.SecuritySafeCritical] +#endif + internal sealed class MinimalFileLockingAppender : IFileAppender + { + private readonly FileTarget _fileTarget; + private readonly string _filePath; + private bool _initialFileOpen; + + public string FilePath => _filePath; + + public DateTime OpenStreamTime { get; } + + public DateTime FileLastModified + { + get + { + var fileInfo = new FileInfo(_filePath); + if (fileInfo.Exists && fileInfo.Length != 0) + { + return Time.TimeSource.Current.FromSystemTime(fileInfo.LastWriteTimeUtc); + } + return OpenStreamTime; + } + } + + public DateTime NextArchiveTime + { + get + { + if (_nextArchiveTime < NLog.Time.TimeSource.Current.Time.AddMinutes(1) || _lastFileBirthTimeUtc == DateTime.MinValue) + { + var fileInfo = new FileInfo(_filePath); + var fileBirthTimeUtc = (fileInfo.Exists && fileInfo.Length != 0) ? (FileInfoHelper.LookupValidFileCreationTimeUtc(fileInfo) ?? DateTime.MinValue) : DateTime.MinValue; + if (fileBirthTimeUtc == DateTime.MinValue || _lastFileBirthTimeUtc < fileBirthTimeUtc) + { + var fileBirthTime = fileBirthTimeUtc != DateTime.MinValue ? NLog.Time.TimeSource.Current.FromSystemTime(fileBirthTimeUtc) : OpenStreamTime; + _nextArchiveTime = FileTarget.CalculateNextArchiveEventTime(_fileTarget.ArchiveEvery, fileBirthTime); + _lastFileBirthTimeUtc = fileBirthTimeUtc; + } + } + return _nextArchiveTime; + } + } + private DateTime _nextArchiveTime; + private DateTime _lastFileBirthTimeUtc; + + public long FileSize + { + get + { + var fileInfo = new FileInfo(_filePath); + var fileSize = fileInfo.Exists ? fileInfo.Length : 0; + return fileSize; + } + } + + public MinimalFileLockingAppender(FileTarget fileTarget, string filePath) + { + _fileTarget = fileTarget; + _filePath = filePath; + _initialFileOpen = true; + OpenStreamTime = Time.TimeSource.Current.Time; + } + + public void Write(byte[] buffer, int offset, int count) + { + int overrideBufferSize = Math.Min((count / 4096 + 1) * 4096, _fileTarget.BufferSize); + + var initialFileOpen = _initialFileOpen; + _initialFileOpen = false; + + using (var fileStream = _fileTarget.CreateFileStreamWithRetry(this, overrideBufferSize, initialFileOpen)) + { + fileStream.Write(buffer, offset, count); + + if (_fileTarget.ReplaceFileContentsOnEachWrite) + { + var footerBytes = _fileTarget.GetFooterLayoutBytes(); + if (footerBytes?.Length > 0) + { + fileStream.Write(footerBytes, 0, footerBytes.Length); + } + } + } + } + + public void Dispose() + { + // Nothing to dispose + } + + public void Flush() + { + // Nothing to flush + } + + public bool VerifyFileExists() + { + return FileSize != 0; + } + + public override string ToString() => _filePath; + } +} diff --git a/src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs b/src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs new file mode 100644 index 0000000000..f2708e45d6 --- /dev/null +++ b/src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs @@ -0,0 +1,388 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.FileArchiveHandlers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading; + using NLog.Common; + using NLog.Internal; + + internal class BaseFileArchiveHandler + { + protected readonly FileTarget _fileTarget; + + public BaseFileArchiveHandler(FileTarget fileTarget) + { + _fileTarget = fileTarget; + } + + protected bool DeleteOldFilesBeforeArchive(string filePath, bool initialFileOpen, string? excludeFileName = null) + { + // Get all files matching the filename, order by timestamp, and when same timestamp then order by filename + // - First start with removing the oldest files + string fileDirectory = Path.GetDirectoryName(filePath); + // Replace all non-letter with '*' replace all '**' with single '*' + string fileWildcard = GetDeleteOldFileNameWildcard(filePath); + return DeleteOldFilesBeforeArchive(fileDirectory, fileWildcard, initialFileOpen, excludeFileName); + } + + protected bool DeleteOldFilesBeforeArchive(string fileDirectory, string fileWildcard, bool initialFileOpen, string? excludeFileName = null, bool wildCardContainsSeqNo = false) + { + try + { + if (string.IsNullOrEmpty(fileWildcard)) + return false; + + if (string.IsNullOrEmpty(fileDirectory)) + return false; + + var directoryInfo = new DirectoryInfo(fileDirectory); + if (!directoryInfo.Exists) + return false; + + var fileInfos = directoryInfo.GetFiles(fileWildcard); + InternalLogger.Debug("{0}: Archive Cleanup found {1} files matching wildcard {2} in directory: {3}", _fileTarget, fileInfos.Length, fileWildcard, fileDirectory); + if (fileInfos.Length != 0) + { + int fileWildcardStartIndex = fileWildcard.IndexOf('*'); + int fileWildcardEndIndex = fileWildcard.Length - fileWildcardStartIndex; + var maxArchiveFiles = _fileTarget.MaxArchiveFiles; + if (initialFileOpen && (!_fileTarget.ArchiveOldFileOnStartup || _fileTarget.DeleteOldFileOnStartup)) + maxArchiveFiles = _fileTarget.DeleteOldFileOnStartup ? 0 : maxArchiveFiles; + else if (maxArchiveFiles > 0) + maxArchiveFiles -= 1; + + bool oldFilesDeleted = false; + foreach (var cleanupFileInfo in FileInfoDateTime.CleanupFiles(fileInfos, maxArchiveFiles, _fileTarget.MaxArchiveDays, fileWildcardStartIndex, fileWildcardEndIndex, excludeFileName, wildCardContainsSeqNo)) + { + oldFilesDeleted = true; + DeleteOldArchiveFile(cleanupFileInfo.FullName); + } + + return oldFilesDeleted; + } + } + catch (Exception exception) + { + InternalLogger.Warn(exception, "{0}: Failed to cleanup archive folder: {1} {2}", _fileTarget, fileDirectory, fileWildcard ?? ""); + if (exception.MustBeRethrown(_fileTarget)) + throw; + } + + return false; + } + + protected static int? GetMaxArchiveSequenceNo(FileInfo[] fileInfos, int fileWildcardStartIndex, int fileWildcardEndIndex) + { + return FileInfoDateTime.ScanFileNamesForMaxSequenceNo(fileInfos, fileWildcardStartIndex, fileWildcardEndIndex); + } + + private +#if !NETFRAMEWORK + readonly +#endif + struct FileInfoDateTime : IComparer + { + public FileInfo FileInfo { get; } + public DateTime FileCreatedTimeUtc { get; } + public int? ArchiveSequenceNumber { get; } + + public FileInfoDateTime(FileInfo fileInfo, DateTime fileCreatedTimeUtc, int? archiveSequenceNumber = null) + { + FileInfo = fileInfo; + ArchiveSequenceNumber = archiveSequenceNumber; + FileCreatedTimeUtc = fileCreatedTimeUtc; + } + + public int Compare(FileInfoDateTime x, FileInfoDateTime y) + { + if (x.ArchiveSequenceNumber.HasValue && y.ArchiveSequenceNumber.HasValue) + { + return x.ArchiveSequenceNumber.Value.CompareTo(y.ArchiveSequenceNumber.Value); + } + else if (x.FileCreatedTimeUtc == y.FileCreatedTimeUtc) + { + return StringComparer.OrdinalIgnoreCase.Compare(x.FileInfo.Name, y.FileInfo.Name); + } + else + { + return x.FileCreatedTimeUtc.CompareTo(y.FileCreatedTimeUtc); + } + } + + public override string ToString() + { + return FileInfo.Name; + } + + public static int? ScanFileNamesForMaxSequenceNo(FileInfo[] fileInfos, int fileWildcardStartIndex, int fileWildcardEndIndex) + { + int? maxArchiveSequenceNo = null; + + foreach (var fileInfo in fileInfos) + { + var fileName = fileInfo.Name; + + if (ExcludeFileName(fileName, fileWildcardStartIndex, fileWildcardEndIndex, null)) + continue; + + if (TryParseStartSequenceNumber(fileName, fileWildcardStartIndex, out var archiveSequenceNo)) + { + if (!maxArchiveSequenceNo.HasValue || archiveSequenceNo > maxArchiveSequenceNo.Value) + maxArchiveSequenceNo = archiveSequenceNo; + } + } + + return maxArchiveSequenceNo; + } + + /// + /// - Only strict scan for sequence-number (GetTodaysArchiveFiles) when having input "fileLastWriteTime" + /// - Expect optional DateTime-part to be "sortable" (when missing birthtime) + /// - Trim away sequencer-number, so not part of sorting + /// - Use DateTime part from FileSystem for ordering by Date-only, and sort by FileName + /// + public static IEnumerable CleanupFiles(FileInfo[] fileInfos, int maxArchiveFiles, int maxArchiveDays, int fileWildcardStartIndex, int fileWildcardEndIndex, string? excludeFileName = null, bool wildCardContainsSeqNo = false) + { + if (fileInfos.Length <= 1) + { + if (maxArchiveFiles == 0 && fileInfos.Length == 1 && !ExcludeFileName(fileInfos[0].Name, fileWildcardStartIndex, fileWildcardEndIndex, excludeFileName)) + yield return fileInfos[0]; + yield break; + } + + if (maxArchiveFiles >= fileInfos.Length && maxArchiveDays <= 0) + yield break; + + var fileInfoDates = new List(fileInfos.Length); + foreach (var fileInfo in fileInfos) + { + var fileName = fileInfo.Name; + if (ExcludeFileName(fileName, fileWildcardStartIndex, fileWildcardEndIndex, excludeFileName)) + continue; + + var fileCreatedTimeUtc = (FileInfoHelper.LookupValidFileCreationTimeUtc(fileInfo) ?? NLog.Time.TimeSource.Current.Time).Date; + if (wildCardContainsSeqNo && TryParseStartSequenceNumber(fileName, fileWildcardStartIndex, out var archiveSequenceNo)) + fileInfoDates.Add(new FileInfoDateTime(fileInfo, fileCreatedTimeUtc, archiveSequenceNo)); + else + fileInfoDates.Add(new FileInfoDateTime(fileInfo, fileCreatedTimeUtc)); + } + fileInfoDates.Sort((x, y) => x.Compare(x, y)); + + for (int i = 0; i < fileInfoDates.Count; i++) + { + if (ShouldDeleteFile(fileInfoDates[i], fileInfoDates.Count - i, maxArchiveFiles, maxArchiveDays)) + { + yield return fileInfoDates[i].FileInfo; + } + else + { + yield break; + } + } + } + + private static bool ExcludeFileName(string fileName, int fileWildcardStartIndex, int fileWildcardEndIndex, string? excludeFileName) + { + if (fileWildcardStartIndex >= 0 && fileWildcardEndIndex >= 0 && fileName.Length > fileWildcardEndIndex) + { + for (int i = fileWildcardStartIndex; i < fileName.Length - fileWildcardEndIndex; ++i) + { + if (char.IsLetter(fileName[i])) + return true; + } + } + + if (excludeFileName is null) + return false; + else + return string.Equals(fileName, excludeFileName, StringComparison.OrdinalIgnoreCase); + } + + private static bool ShouldDeleteFile(FileInfoDateTime existingArchiveFile, int remainingFileCount, int maxArchiveFiles, int maxArchiveDays) + { + if (maxArchiveFiles >= 0 && remainingFileCount > maxArchiveFiles) + return true; + + if (maxArchiveDays > 0) + { + var currentDateUtc = NLog.Time.TimeSource.Current.Time.Date; + var fileAgeDays = (currentDateUtc - NLog.Time.TimeSource.Current.FromSystemTime(existingArchiveFile.FileCreatedTimeUtc).Date).TotalDays; + if (fileAgeDays > maxArchiveDays) + { + InternalLogger.Debug("FileTarget: Detected old file in archive. FileName={0}, FileDateUtc={1:u}, CurrentDateUtc={2:u}, Age={3} days", existingArchiveFile.FileInfo.FullName, existingArchiveFile.FileCreatedTimeUtc, currentDateUtc, Math.Round(fileAgeDays, 1)); + return true; + } + } + + return false; + } + + private static bool TryParseStartSequenceNumber(string archiveFileName, int seqStartIndex, out int archiveSequenceNo) + { + int? parsedSequenceNo = null; + + int startIndex = seqStartIndex; + for (int i = startIndex; i < archiveFileName.Length; ++i) + { + char chr = archiveFileName[i]; + if (!char.IsNumber(chr)) + break; + + parsedSequenceNo = parsedSequenceNo > 0 ? parsedSequenceNo * 10 : 0; + parsedSequenceNo += (chr - '0'); + } + archiveSequenceNo = parsedSequenceNo ?? 0; + return parsedSequenceNo.HasValue; + } + } + + private static string GetDeleteOldFileNameWildcard(string filepath) + { + var filename = Path.GetFileNameWithoutExtension(filepath) ?? string.Empty; + var fileext = Path.GetExtension(filepath) ?? string.Empty; + if (string.IsNullOrEmpty(filename) && string.IsNullOrEmpty(fileext)) + return string.Empty; + + int lastStart = 0; + int lastLength = 0; + int currentLength = 0; + int currentStart = 0; + for (int i = 0; i < filename.Length; ++i) + { + if (!char.IsLetter(filename[i])) + { + if (currentLength == 0) + currentStart = i; + ++currentLength; + } + else + { + if (currentLength != 0) + { + if (lastLength <= currentLength) + { + lastStart = currentStart; + lastLength = currentLength; + } + currentLength = 0; + } + } + } + + if (lastLength < currentLength) + { + lastStart = currentStart; + lastLength = currentLength; + } + + if (lastLength > 0) + { + if (lastStart > 0 && lastLength > 1 && !char.IsDigit(filename[lastStart + 1])) + { + lastStart += 1; + lastLength -= 1; + } + + var prefix = filename.Substring(0, lastStart); + var suffix = filename.Substring(lastStart + lastLength, filename.Length - lastStart - lastLength); + return string.IsNullOrEmpty(suffix) ? string.Concat(prefix, "*", fileext) : string.Concat(prefix, "*", suffix, "*", fileext); + } + + return string.Concat(filename, "*", fileext); + } + + protected bool DeleteOldArchiveFile(string filepath) + { + for (int i = 1; i <= 3; ++i) + { + try + { + InternalLogger.Info("{0}: Deleting old archive file: '{1}'.", _fileTarget, filepath); + _fileTarget.CloseOpenFileBeforeArchiveCleanup(filepath); + File.Delete(filepath); + return true; + } + catch (DirectoryNotFoundException ex) + { + InternalLogger.Debug(ex, "{0}: Failed to delete old file as directory not found: '{1}'", _fileTarget, filepath); + return true; + } + catch (FileNotFoundException ex) + { + InternalLogger.Debug(ex, "{0}: Failed to delete old file as file not found: '{1}'", _fileTarget, filepath); + return true; + } + catch (IOException ex) + { + InternalLogger.Debug(ex, "{0}: Failed to delete old file, maybe file is locked: '{1}'", _fileTarget, filepath); + if (!File.Exists(filepath)) + return true; + if (i >= 3 && ex.MustBeRethrown(_fileTarget)) + throw; + } + catch (Exception ex) + { + InternalLogger.Warn(ex, "{0}: Failed to delete old archive file: '{1}'.", _fileTarget, filepath); + if (ex.MustBeRethrown(_fileTarget)) + throw; + + return false; + } + + Thread.Sleep(i * 10); + } + return false; + } + + protected void FixWindowsFileSystemTunneling(string newFilePath) + { + try + { + if (PlatformDetector.IsWin32 && !File.Exists(newFilePath)) + { + // Set the file's creation time to avoid being thwarted by Windows FileSystem Tunneling capabilities (https://support.microsoft.com/en-us/kb/172190). + File.Create(newFilePath).Dispose(); + File.SetCreationTimeUtc(newFilePath, DateTime.UtcNow); + } + } + catch (Exception ex) + { + InternalLogger.Debug(ex, "{0}: Failed to refresh CreationTimeUtc for FileName: {1}", _fileTarget, newFilePath); + } + } + } +} diff --git a/src/NLog/Targets/FileArchiveHandlers/DisabledFileArchiveHandler.cs b/src/NLog/Targets/FileArchiveHandlers/DisabledFileArchiveHandler.cs new file mode 100644 index 0000000000..f4a07d4fd0 --- /dev/null +++ b/src/NLog/Targets/FileArchiveHandlers/DisabledFileArchiveHandler.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +using System; + +namespace NLog.Targets.FileArchiveHandlers +{ + internal sealed class DisabledFileArchiveHandler : IFileArchiveHandler + { + public static readonly IFileArchiveHandler Default = new DisabledFileArchiveHandler(); + + public int ArchiveBeforeOpenFile(string newFileName, LogEventInfo firstLogEvent, DateTime? previousFileLastModified, int newSequenceNumber) + { + return 0; // Archive logic disabled, no rolling of active file + } + } +} diff --git a/src/NLog/Targets/FileArchiveHandlers/IFileArchiveHandler.cs b/src/NLog/Targets/FileArchiveHandlers/IFileArchiveHandler.cs new file mode 100644 index 0000000000..4d901b0a30 --- /dev/null +++ b/src/NLog/Targets/FileArchiveHandlers/IFileArchiveHandler.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.FileArchiveHandlers +{ + using System; + + /// + /// Handles the actual file-operations on disk + /// + internal interface IFileArchiveHandler + { + /// + /// Called just before opening a new log-file + /// + /// File-name of the new log-file + /// The first LogEvent for the new log-file + /// Previous file-write-time + /// File-path-suffix for the new log-file + /// Updated for the new file. + int ArchiveBeforeOpenFile(string newFileName, LogEventInfo firstLogEvent, DateTime? previousFileLastModified, int newSequenceNumber); + } +} diff --git a/src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs b/src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs new file mode 100644 index 0000000000..2cd1254a6e --- /dev/null +++ b/src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs @@ -0,0 +1,287 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.FileArchiveHandlers +{ + using System; + using System.IO; + using NLog.Common; + using NLog.Internal; + using NLog.Layouts; + + /// + /// Legacy archive logic with file-move of active-file to file-path specified by + /// + /// + /// Kept mostly for legacy reasons, because archive operation can fail because of file-locks by other applications (or by multi-process logging). + /// + /// Avoid using when possible, and instead rely on only using and . + /// + internal sealed class LegacyArchiveFileNameHandler : RollingArchiveFileHandler, IFileArchiveHandler + { + public LegacyArchiveFileNameHandler(FileTarget fileTarget) + : base(fileTarget) + { + } + + public override int ArchiveBeforeOpenFile(string newFileName, LogEventInfo firstLogEvent, DateTime? previousFileLastModified, int newSequenceNumber) + { + var archiveFileName = _fileTarget.ArchiveFileName?.Render(firstLogEvent); + if (archiveFileName is null || StringHelpers.IsNullOrWhiteSpace(archiveFileName)) + { + return base.ArchiveBeforeOpenFile(newFileName, firstLogEvent, previousFileLastModified, newSequenceNumber); + } + + var newFilePath = FileTarget.CleanFullFilePath(newFileName); + + bool initialFileOpen = newSequenceNumber == 0; + if (ArchiveBeforeOpenFile(archiveFileName, newFilePath, firstLogEvent, previousFileLastModified, initialFileOpen)) + { + FixWindowsFileSystemTunneling(newFilePath); + } + + return 0; + } + + private bool ArchiveBeforeOpenFile(string archiveFileName, string newFilePath, LogEventInfo firstLogEvent, DateTime? previousFileLastModified, bool initialFileOpen) + { + bool oldFilesDeleted = false; + if (_fileTarget.MaxArchiveFiles >= 0 || _fileTarget.MaxArchiveDays > 0 || (initialFileOpen && _fileTarget.DeleteOldFileOnStartup)) + { + bool wildCardStrictSeqNo = _fileTarget.ArchiveSuffixFormat.IndexOf("{0", StringComparison.Ordinal) >= 0 && _fileTarget.ArchiveSuffixFormat.IndexOf("{1", StringComparison.Ordinal) < 0 && + !(_fileTarget.ArchiveFileName is SimpleLayout simpleLayout && (simpleLayout.OriginalText.IndexOf("${date", StringComparison.OrdinalIgnoreCase) >= 0 || simpleLayout.OriginalText.IndexOf("${shortdate", StringComparison.OrdinalIgnoreCase) >= 0)); + + var excludeFileName = Path.GetFileName(newFilePath); + if (wildCardStrictSeqNo) + { + string archiveFilePath = BuildArchiveFilePath(archiveFileName, int.MaxValue, DateTime.MinValue); + string archiveFileWildcard = archiveFilePath.Replace(int.MaxValue.ToString(), "*"); + string archiveDirectory = Path.GetDirectoryName(archiveFilePath); + oldFilesDeleted = DeleteOldFilesBeforeArchive(archiveDirectory, Path.GetFileName(archiveFileWildcard), initialFileOpen, excludeFileName, true); + } + else + { + var archiveFilePath = FileTarget.CleanFullFilePath(archiveFileName); + oldFilesDeleted = DeleteOldFilesBeforeArchive(archiveFilePath, initialFileOpen, excludeFileName); + } + } + + if (initialFileOpen && !_fileTarget.ArchiveOldFileOnStartup) + return oldFilesDeleted; + + if (!ArchiveOldFileWithRetry(archiveFileName, newFilePath, firstLogEvent, previousFileLastModified)) + return oldFilesDeleted; + + return true; + } + + private bool ArchiveOldFileWithRetry(string archiveFileName, string newFilePath, LogEventInfo firstLogEvent, DateTime? previousFileLastModified) + { + DateTime? lastWriteTimeUtc = default(DateTime?); + long? lastFileLength = default(long?); + + bool oldFilesDeleted = false; + + for (int i = 1; i <= 3; ++i) + { + try + { + var newFileInfo = new FileInfo(newFilePath); + if (!newFileInfo.Exists) + return oldFilesDeleted; + + oldFilesDeleted = true; + if (HasFileInfoChanged(newFileInfo, lastWriteTimeUtc, lastFileLength)) + return false; // File archive probably completed by someone else, and new file already created + + lastWriteTimeUtc = lastWriteTimeUtc ?? newFileInfo.LastWriteTimeUtc; + lastFileLength = lastFileLength ?? newFileInfo.Length; + if (ArchiveOldFile(archiveFileName, newFileInfo, firstLogEvent, previousFileLastModified)) + return true; + } + catch (IOException ex) + { + InternalLogger.Debug(ex, "{0}: Failed to archive file, maybe file is locked: '{1}'", _fileTarget, newFilePath); + if (!File.Exists(newFilePath)) + return oldFilesDeleted; + if (i >= 3 && LogManager.ThrowExceptions) + throw; + } + catch (Exception ex) + { + InternalLogger.Debug(ex, "{0}: Failed to archive file: '{1}'", _fileTarget, newFilePath); + if (LogManager.ThrowExceptions) + throw; + System.Threading.Thread.Sleep(i * 10); + } + } + + return oldFilesDeleted; + } + + private static bool HasFileInfoChanged(FileInfo newFileInfo, DateTime? lastWriteTimeUtc, long? lastFileLength) + { + if (lastWriteTimeUtc.HasValue && lastWriteTimeUtc.Value != newFileInfo.LastWriteTimeUtc) + return true; + + if (lastFileLength.HasValue && lastFileLength.Value != newFileInfo.Length) + return true; + + return false; + } + + private bool ArchiveOldFile(string archiveFileName, FileInfo newFileInfo, LogEventInfo firstLogEvent, DateTime? previousFileLastModified) + { + DateTime fileLastWriteTime = Time.TimeSource.Current.FromSystemTime(newFileInfo.LastWriteTimeUtc); + if (previousFileLastModified.HasValue && (previousFileLastModified > fileLastWriteTime || fileLastWriteTime >= firstLogEvent.TimeStamp)) + fileLastWriteTime = previousFileLastModified.Value; + + var archiveNextSequenceNo = ResolveNextArchiveSequenceNo(archiveFileName, newFileInfo, fileLastWriteTime); + string archiveFullPath = BuildArchiveFilePath(archiveFileName, archiveNextSequenceNo, fileLastWriteTime); + + if (!File.Exists(archiveFullPath)) + { + // Move active file to archive + InternalLogger.Info("{0}: Move file from '{1}' to '{2}'", _fileTarget, newFileInfo.FullName, archiveFullPath); + File.Move(newFileInfo.FullName, archiveFullPath); + return true; + } + + if (!newFileInfo.Exists) + { + return true; // No active file to archive, we are done + } + + if (archiveNextSequenceNo == 0) + { + // Append to existing file, and delete old file + ArchiveFileAppendExisting(newFileInfo.FullName, archiveFullPath); + return true; + } + + // Retry (guess new sequence number) + return false; + } + + private string BuildArchiveFilePath(string archiveFileName, int archiveNextSequenceNo, DateTime fileLastWriteTime) + { + return _fileTarget.BuildFullFilePath(archiveFileName, archiveNextSequenceNo, fileLastWriteTime); + } + + private void ArchiveFileAppendExisting(string newFilePath, string archiveFilePath) + { + // TODO Handle double footer + InternalLogger.Info("{0}: Already exists, append to {1}", _fileTarget, archiveFilePath); + + var fileShare = FileShare.Read | FileShare.Delete; + using (FileStream newFileStream = File.Open(newFilePath, FileMode.Open, FileAccess.ReadWrite, fileShare)) + using (FileStream archiveFileStream = File.Open(archiveFilePath, FileMode.Append)) + { + if (_fileTarget.WriteBom) + { + var preamble = _fileTarget.Encoding.GetPreamble(); + if (preamble.Length > 0) + newFileStream.Seek(preamble.Length, SeekOrigin.Begin); + } + + byte[] buffer = new byte[4096]; + int read; + while ((read = newFileStream.Read(buffer, 0, buffer.Length)) > 0) + { + archiveFileStream.Write(buffer, 0, read); + } + + // Reset/Truncate the file + newFileStream.SetLength(0); + + // Attempt to delete file to reset File-Creation-Time (Delete under file-lock) + if (!DeleteOldArchiveFile(newFilePath)) + { + fileShare &= ~FileShare.Delete; // Retry after having released file-lock + } + + newFileStream.Close(); // This flushes the content + } + + if ((fileShare & FileShare.Delete) == 0) + { + DeleteOldArchiveFile(newFilePath); // Attempt to delete file to reset File-Creation-Time + } + } + + private int ResolveNextArchiveSequenceNo(string archiveFileName, FileInfo newFileInfo, DateTime fileLastWriteTime) + { + // Archive operation triggered, how to resolve the next archive-sequence-number ? + // - Old version was able to "parse" the file-names of the archive-folder and "guess" the next sequence number + var archiveFilePath = BuildArchiveFilePath(archiveFileName, int.MaxValue, fileLastWriteTime); + var archiveDirectory = Path.GetDirectoryName(archiveFilePath); + if (string.IsNullOrEmpty(archiveDirectory)) + return 0; + + var directoryInfo = new DirectoryInfo(archiveDirectory); + if (!directoryInfo.Exists) + { + if (_fileTarget.CreateDirs) + { + InternalLogger.Debug("{0}: Creating archive directory: {1}", _fileTarget, archiveDirectory); + directoryInfo.Create(); + } + } + + var archiveWildCardFileName = Path.GetFileName(archiveFilePath).Replace(int.MaxValue.ToString(), "*"); + int fileWildcardStartIndex = archiveWildCardFileName.IndexOf('*'); + if (fileWildcardStartIndex < 0) + return 0; + + int fileWildcardEndIndex = archiveWildCardFileName.Length - fileWildcardStartIndex; + if (fileWildcardStartIndex > 0 && !char.IsLetter(archiveWildCardFileName[fileWildcardStartIndex - 1]) && !char.IsDigit(archiveWildCardFileName[fileWildcardStartIndex - 1])) + { + archiveWildCardFileName = archiveWildCardFileName.Substring(0, fileWildcardStartIndex - 1) + archiveWildCardFileName.Substring(fileWildcardStartIndex); + } + + var archiveFiles = directoryInfo.GetFiles(archiveWildCardFileName); + InternalLogger.Debug("{0}: Archive Sequence Rolling found {1} files matching wildcard {2} in directory: {3}", _fileTarget, archiveFiles.Length, archiveWildCardFileName, archiveDirectory); + if (archiveFiles.Length == 0) + return 0; + + var sequenceNo = GetMaxArchiveSequenceNo(archiveFiles, fileWildcardStartIndex, fileWildcardEndIndex); + if (sequenceNo.HasValue) + return sequenceNo.Value + 1; + + if (string.Equals(directoryInfo.FullName, newFileInfo.DirectoryName, StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetFileName(archiveFileName), newFileInfo.Name, StringComparison.OrdinalIgnoreCase)) + return 1; // Same folder archive + + return 0; + } + } +} diff --git a/src/NLog/Targets/FileArchiveHandlers/RollingArchiveFileHandler.cs b/src/NLog/Targets/FileArchiveHandlers/RollingArchiveFileHandler.cs new file mode 100644 index 0000000000..ee03fa1d05 --- /dev/null +++ b/src/NLog/Targets/FileArchiveHandlers/RollingArchiveFileHandler.cs @@ -0,0 +1,160 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.FileArchiveHandlers +{ + using System; + using System.IO; + using NLog.Common; + using NLog.Internal; + + /// + /// Rolls the active-file to the next sequence-number + /// + internal class RollingArchiveFileHandler : BaseFileArchiveHandler, IFileArchiveHandler + { + public RollingArchiveFileHandler(FileTarget fileTarget) + :base(fileTarget) + { + } + + public virtual int ArchiveBeforeOpenFile(string newFileName, LogEventInfo firstLogEvent, DateTime? previousFileLastModified, int newSequenceNumber) + { + bool initialFileOpen = newSequenceNumber == 0; + + if (_fileTarget.MaxArchiveFiles >= 0 || _fileTarget.MaxArchiveDays > 0 || (initialFileOpen && _fileTarget.DeleteOldFileOnStartup)) + { + var newFilePath = FileTarget.CleanFullFilePath(newFileName); + bool deletedOldFiles = DeleteOldFilesBeforeArchive(newFilePath, initialFileOpen); + + if (_fileTarget.MaxArchiveFiles == 0 || _fileTarget.MaxArchiveFiles == 1 || (initialFileOpen && _fileTarget.DeleteOldFileOnStartup)) + { + if (deletedOldFiles) + { + FixWindowsFileSystemTunneling(newFilePath); + } + return 0; + } + } + + if (initialFileOpen) + { + if (_fileTarget.ArchiveOldFileOnStartup || _fileTarget.ArchiveAboveSize > 0 || _fileTarget.ArchiveEvery != FileArchivePeriod.None) + { + var newFilePath = FileTarget.CleanFullFilePath(newFileName); + return RollToInitialSequenceNumber(newFilePath); + } + } + + return newSequenceNumber; + } + + private int RollToInitialSequenceNumber(string newFilePath) + { + int newSequenceNumber = 0; + + try + { + if (AllowOptimizedRollingForArchiveAboveSize()) + { + // Fast FileSize check of a single file, and skip enumerating all archive-files + var newFileInfo = new FileInfo(newFilePath); + var newFileLength = newFileInfo.Exists ? newFileInfo.Length : 0; + if (newFileLength > 0 && newFileLength < _fileTarget.ArchiveAboveSize) + { + InternalLogger.Debug("{0}: Archive rolling skipped because file-size={1} < ArchiveAboveSize for file: {2}", _fileTarget, newFileLength, newFilePath); + return newSequenceNumber; + } + } + + var filedir = Path.GetDirectoryName(newFilePath); + if (string.IsNullOrEmpty(filedir)) + return 0; + + var directoryInfo = new DirectoryInfo(filedir); + if (!directoryInfo.Exists) + return 0; + + var filename = Path.GetFileNameWithoutExtension(newFilePath); + var fileext = Path.GetExtension(newFilePath); + var fileWildCard = filename + "*" + fileext; + var fileInfos = directoryInfo.GetFiles(fileWildCard); + InternalLogger.Debug("{0}: Archive Sequence Rolling found {1} files matching wildcard {2} in directory: {3}", _fileTarget, fileInfos.Length, fileWildCard, filedir); + if (fileInfos.Length == 0) + return 0; + + if (_fileTarget.DeleteOldFileOnStartup) + { + // Delete all files in the directory with matching filename-wildcard + foreach (var fileInfo in fileInfos) + { + DeleteOldArchiveFile(fileInfo.FullName); + } + + return 0; + } + else + { + var archivePathWildCard = _fileTarget.BuildFullFilePath(newFilePath, int.MaxValue, DateTime.MinValue).Replace(int.MaxValue.ToString(), "*"); + var archiveFileName = Path.GetFileName(archivePathWildCard); + var fileWildcardStartIndex = archiveFileName.IndexOf('*'); + var fileWildcardEndIndex = fileWildcardStartIndex >= 0 ? archiveFileName.Length - fileWildcardStartIndex : filename.Length; + + // Search for matching files to find the highest sequence-number + newSequenceNumber = GetMaxArchiveSequenceNo(fileInfos, fileWildcardStartIndex, fileWildcardEndIndex) ?? 0; + + if (_fileTarget.ArchiveOldFileOnStartup) + { + // Search for matching files to find the highest sequence-number, and roll to next sequence-no + newSequenceNumber += 1; + } + + return newSequenceNumber; + } + } + catch (Exception ex) + { + InternalLogger.Warn(ex, "{0}: Failed to resolve initial archive sequence number for file: {1}", _fileTarget, newFilePath); + if (ex.MustBeRethrown(_fileTarget)) + throw; + + return newSequenceNumber; + } + } + + private bool AllowOptimizedRollingForArchiveAboveSize() + { + return _fileTarget.ArchiveAboveSize > 0 && _fileTarget.ArchiveEvery == FileArchivePeriod.None && !_fileTarget.ArchiveOldFileOnStartup && !_fileTarget.DeleteOldFileOnStartup && _fileTarget.GetType().Equals(typeof(FileTarget)); + } + } +} diff --git a/src/NLog/Targets/FileArchiveHandlers/ZeroFileArchiveHandler.cs b/src/NLog/Targets/FileArchiveHandlers/ZeroFileArchiveHandler.cs new file mode 100644 index 0000000000..58bb19d74a --- /dev/null +++ b/src/NLog/Targets/FileArchiveHandlers/ZeroFileArchiveHandler.cs @@ -0,0 +1,94 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.FileArchiveHandlers +{ + using System; + using System.IO; + using System.Linq; + using NLog.Common; + using NLog.Internal; + + /// + /// Deletes/truncates the active logging-file when archive-roll-event is triggered + /// + internal sealed class ZeroFileArchiveHandler : BaseFileArchiveHandler, IFileArchiveHandler + { + public ZeroFileArchiveHandler(FileTarget fileTarget) + :base(fileTarget) + { + } + + public int ArchiveBeforeOpenFile(string newFileName, LogEventInfo firstLogEvent, DateTime? previousFileLastModified, int newSequenceNumber) + { + var newFilePath = FileTarget.CleanFullFilePath(newFileName); + + bool initialFileOpen = newSequenceNumber == 0; + if (DeleteOldArchiveFiles(newFilePath, initialFileOpen)) + { + FixWindowsFileSystemTunneling(newFilePath); + } + return 0; // No rolling of active file + } + + private bool DeleteOldArchiveFiles(string newFilePath, bool initialFileOpen) + { + try + { + if (initialFileOpen && _fileTarget.DeleteOldFileOnStartup) + { + var filePathWithoutExtension = Path.GetFileNameWithoutExtension(newFilePath); + if (filePathWithoutExtension.Any(chr => char.IsDigit(chr))) + { + // Delete with wildcard (also files from yesterday) + return DeleteOldFilesBeforeArchive(newFilePath, initialFileOpen); + } + } + + // Wildcard not detected as required, so just delete/truncate the active file + if (File.Exists(newFilePath)) + { + return DeleteOldArchiveFile(newFilePath); + } + } + catch (Exception ex) + { + InternalLogger.Debug(ex, "{0}: Failed to archive file: '{1}'", _fileTarget, newFilePath); + if (ex.MustBeRethrown(_fileTarget)) + throw; + } + + return false; + } + } +} diff --git a/src/NLog/Targets/FileArchivePeriod.cs b/src/NLog/Targets/FileArchivePeriod.cs index 3873739ed3..c4b7983d04 100644 --- a/src/NLog/Targets/FileArchivePeriod.cs +++ b/src/NLog/Targets/FileArchivePeriod.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen // // All rights reserved. @@ -44,63 +44,63 @@ public enum FileArchivePeriod None, /// - /// AddToArchive every year. + /// Archive every new year. /// Year, /// - /// AddToArchive every month. + /// Archive every new month. /// Month, /// - /// AddToArchive daily. + /// Archive every new day. /// Day, /// - /// AddToArchive every hour. + /// Archive every new hour. /// Hour, /// - /// AddToArchive every minute. + /// Archive every new minute. /// Minute, #region Weekdays /// - /// AddToArchive every Sunday. + /// Archive every Sunday. /// Sunday, /// - /// AddToArchive every Monday. + /// Archive every Monday. /// Monday, /// - /// AddToArchive every Tuesday. + /// Archive every Tuesday. /// Tuesday, /// - /// AddToArchive every Wednesday. + /// Archive every Wednesday. /// Wednesday, /// - /// AddToArchive every Thursday. + /// Archive every Thursday. /// Thursday, /// - /// AddToArchive every Friday. + /// Archive every Friday. /// Friday, /// - /// AddToArchive every Saturday. + /// Archive every Saturday. /// Saturday #endregion diff --git a/src/NLog/Targets/FileTarget.cs b/src/NLog/Targets/FileTarget.cs index 4af62c96d1..b3bec9ee45 100644 --- a/src/NLog/Targets/FileTarget.cs +++ b/src/NLog/Targets/FileTarget.cs @@ -31,160 +31,39 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 -#define SupportsMutex -#endif - namespace NLog.Targets { using System; using System.Collections.Generic; - using System.ComponentModel; - using System.Globalization; using System.IO; + using System.Linq; using System.Text; using System.Threading; using NLog.Common; using NLog.Config; using NLog.Internal; - using NLog.Internal.FileAppenders; + using NLog.Internal.Fakeables; using NLog.Layouts; - using NLog.Targets.FileArchiveModes; - using NLog.Time; + using NLog.Targets.FileAppenders; + using NLog.Targets.FileArchiveHandlers; /// - /// Writes log messages to one or more files. + /// FileTarget for writing formatted messages to one or more log-files. /// /// /// See NLog Wiki /// /// Documentation on NLog Wiki [Target("File")] - public class FileTarget : TargetWithLayoutHeaderAndFooter, ICreateFileParameters + public class FileTarget : TargetWithLayoutHeaderAndFooter { - /// - /// Default clean up period of the initialized files. When a file exceeds the clean up period is removed from the list. - /// - /// Clean up period is defined in days. - private const int InitializedFilesCleanupPeriod = 2; - - /// - /// This value disables file archiving based on the size. - /// - private const long ArchiveAboveSizeDisabled = -1L; - - /// - /// Holds the initialized files each given time by the instance. Against each file, the last write time is stored. - /// - /// Last write time is store in local time (no UTC). - private readonly Dictionary _initializedFiles = new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// List of the associated file appenders with the instance. - /// - private IFileAppenderCache _fileAppenderCache; - - IFileArchiveMode GetFileArchiveHelper(string archiveFilePattern) - { - return _fileArchiveHelper ?? (_fileArchiveHelper = FileArchiveModeFactory.CreateArchiveStyle(archiveFilePattern, ArchiveNumbering, GetArchiveDateFormatString(ArchiveDateFormat), ArchiveFileName != null, MaxArchiveFiles > 0 || MaxArchiveDays > 0)); - } - private IFileArchiveMode _fileArchiveHelper; - - private Timer _autoClosingTimer; - - /// - /// The number of initialized files at any one time. - /// - private int _initializedFilesCounter; - - /// - /// The maximum number of archive files that should be kept. - /// - private int _maxArchiveFiles; - - /// - /// The maximum days of archive files that should be kept. - /// - private int _maxArchiveDays; - - /// - /// The filename as target - /// - private FilePathLayout _fullFileName; - - /// - /// The archive file name as target - /// - private FilePathLayout _fullArchiveFileName; - - private FileArchivePeriod _archiveEvery; - private long _archiveAboveSize; - - private bool _enableArchiveFileCompression; - - /// - /// The date of the previous log event. - /// - private DateTime? _previousLogEventTimestamp; - - /// - /// The file name of the previous log event. - /// - private string _previousLogFileName; - - private bool _concurrentWrites; - private bool _cleanupFileName; - private FilePathKind _fileNameKind = FilePathKind.Unknown; - private FilePathKind _archiveFileKind; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} - /// - public FileTarget() : this(FileAppenderCache.Empty) - { - } - - internal FileTarget(IFileAppenderCache fileAppenderCache) - { - ArchiveNumbering = ArchiveNumberingMode.Sequence; - _maxArchiveFiles = 0; - _maxArchiveDays = 0; - - ArchiveEvery = FileArchivePeriod.None; - ArchiveAboveSize = ArchiveAboveSizeDisabled; - _cleanupFileName = true; - - _fileAppenderCache = fileAppenderCache; - } - -#if !NET35 && !NET40 - static FileTarget() - { - FileCompressor = new ZipArchiveFileCompressor(); - } -#endif - - /// - /// Initializes a new instance of the class. - /// - /// - /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} - /// - /// Name of the target. - public FileTarget(string name) : this() - { - Name = name; - } - /// /// Gets or sets the name of the file to write to. /// /// - /// This FileName string is a layout which may include instances of layout renderers. - /// This lets you use a single target to write to multiple files. + /// [Required] Default: . + /// When not absolute path then relative path will be resolved against . + /// The FileName Layout supports layout-renderers, where a single FileTarget can write to multiple files. /// /// /// The following value makes NLog write logging events to files based on the log level in the directory where @@ -194,73 +73,25 @@ public FileTarget(string name) : this() /// You can combine as many of the layout renderers as you want to produce an arbitrary log file name. /// /// - [RequiredParameter] public Layout FileName { - get - { - return _fullFileName?.GetLayout(); - } - set - { - _fullFileName = CreateFileNameLayout(value); - ResetFileAppenders("FileName Changed"); - } - } - - private FilePathLayout CreateFileNameLayout(Layout value) - { - if (value is null) - return null; - - return new FilePathLayout(value, CleanupFileName, FileNameKind); - } - - /// - /// Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. - /// If set to false, nothing gets written when the filename is wrong. - /// - /// - public bool CleanupFileName - { - get => _cleanupFileName; - set - { - if (_cleanupFileName != value) - { - _cleanupFileName = value; - _fullFileName = CreateFileNameLayout(FileName); - _fullArchiveFileName = CreateFileNameLayout(ArchiveFileName); - ResetFileAppenders("CleanupFileName Changed"); - } - } - } - - /// - /// Is the an absolute or relative path? - /// - /// - public FilePathKind FileNameKind - { - get => _fileNameKind; + get => _fileName; set { - if (_fileNameKind != value) - { - _fileNameKind = value; - _fullFileName = CreateFileNameLayout(FileName); - _fullArchiveFileName = CreateFileNameLayout(ArchiveFileName); - ResetFileAppenders("FileNameKind Changed"); - } + _fileName = value; + _fixedFileName = (value is SimpleLayout simpleLayout && simpleLayout.IsFixedText) ? simpleLayout.FixedText : null; } } + private Layout _fileName = Layout.Empty; + private string? _fixedFileName; /// /// Gets or sets a value indicating whether to create directories if they do not exist. /// /// - /// Setting this to false may improve performance a bit, but you'll receive an error - /// when attempting to write to a directory that's not present. + /// Default: . + /// Setting this to may improve performance a bit, but will always fail + /// when attempt writing to a non-existing directory. /// /// public bool CreateDirs { get; set; } = true; @@ -269,7 +100,8 @@ public FilePathKind FileNameKind /// Gets or sets a value indicating whether to delete old log file on startup. /// /// - /// This option works only when the "FileName" parameter denotes a single file. + /// Default: . + /// When current log-file exists, then it is deleted (and resetting sequence number) /// /// public bool DeleteOldFileOnStartup { get; set; } @@ -277,6 +109,7 @@ public FilePathKind FileNameKind /// /// Gets or sets a value indicating whether to replace file contents on each write instead of appending log message at the end. /// + /// Default: /// public bool ReplaceFileContentsOnEachWrite { get; set; } @@ -284,77 +117,43 @@ public FilePathKind FileNameKind /// Gets or sets a value indicating whether to keep log file open instead of opening and closing it on each logging event. /// /// - /// KeepFileOpen = true gives the best performance, and ensure the file-lock is not lost to other applications.
- /// KeepFileOpen = false gives the best compability, but slow performance and lead to file-locking issues with other applications. + /// Default: . + /// KeepFileOpen = gives the best performance, and ensure the file-lock is not lost to other applications.
+ /// KeepFileOpen = gives the best compatibility, but slow performance and lead to file-locking issues with other applications. ///
/// - public bool KeepFileOpen - { - get => _keepFileOpen; - set - { - if (_keepFileOpen != value) - { - _keepFileOpen = value; - ResetFileAppenders("KeepFileOpen Changed"); - } - } - } - private bool _keepFileOpen = true; + public bool KeepFileOpen { get; set; } = true; /// /// Gets or sets a value indicating whether to enable log file(s) to be deleted. /// + /// Default: /// public bool EnableFileDelete { get; set; } = true; - /// - /// Gets or sets the file attributes (Windows only). - /// - /// - public Win32FileAttributes FileAttributes - { - get => _fileAttributes; - set - { - if (value != Win32FileAttributes.Normal && PlatformDetector.IsWin32) - { - ForceManaged = false; - } - _fileAttributes = value; - } - } - Win32FileAttributes _fileAttributes = Win32FileAttributes.Normal; - - bool ICreateFileParameters.IsArchivingEnabled => IsArchivingEnabled; - - int ICreateFileParameters.FileOpenRetryCount => ConcurrentWrites ? ConcurrentWriteAttempts : (KeepFileOpen ? 0 : (_concurrentWriteAttempts ?? 2)); - - int ICreateFileParameters.FileOpenRetryDelay => ConcurrentWriteAttemptDelay; - /// /// Gets or sets the line ending mode. /// + /// Default: /// public LineEndingMode LineEnding { get; set; } = LineEndingMode.Default; /// /// Gets or sets a value indicating whether to automatically flush the file buffers after each log message. /// + /// Default: /// public bool AutoFlush { get; set; } = true; /// - /// Gets or sets the number of files to be kept open. Setting this to a higher value may improve performance - /// in a situation where a single File target is writing to many files - /// (such as splitting by level or by logger). + /// Gets or sets the maximum number of files to be kept open. /// /// - /// The files are managed on a LRU (least recently used) basis, which flushes - /// the files that have not been used for the longest period of time should the - /// cache become full. As a rule of thumb, you shouldn't set this parameter to - /// a very high value. A number like 10-15 shouldn't be exceeded, because you'd - /// be keeping a large number of files open which consumes system resources. + /// Default: . Higher number might improve performance when single FileTarget + /// is writing to many files (such as splitting by loglevel or by logger-name). + /// Files are closed in FIFO (First in First out) ordering, so the oldest + /// file-handle is closed first. Careful with number higher than 10-15, + /// because a large number of open files consumes system resources. /// /// public int OpenFileCacheSize { get; set; } = 5; @@ -362,24 +161,28 @@ public Win32FileAttributes FileAttributes /// /// Gets or sets the maximum number of seconds that files are kept open. Zero or negative means disabled. /// + /// Default: /// public int OpenFileCacheTimeout { get; set; } /// /// Gets or sets the maximum number of seconds before open files are flushed. Zero or negative means disabled. /// + /// Default: /// public int OpenFileFlushTimeout { get; set; } /// /// Gets or sets the log file buffer size in bytes. /// + /// Default: /// public int BufferSize { get; set; } = 32768; /// /// Gets or sets the file encoding. /// + /// Default: /// public Encoding Encoding { @@ -397,47 +200,14 @@ public Encoding Encoding /// Gets or sets whether or not this target should just discard all data that its asked to write. /// Mostly used for when testing NLog Stack except final write /// - /// + /// Default: + /// public bool DiscardAll { get; set; } - /// - /// Gets or sets a value indicating whether concurrent writes to the log file by multiple processes on the same host. - /// - /// - /// This makes multi-process logging possible. NLog uses a special technique - /// that lets it keep the files open for writing. - /// - /// - public bool ConcurrentWrites - { - get => _concurrentWrites; - set - { - if (_concurrentWrites != value) - { - _concurrentWrites = value; - ResetFileAppenders("ConcurrentWrites Changed"); - } - } - } - - /// - /// Obsolete and replaced by = false with NLog v5.3. - /// Gets or sets a value indicating whether concurrent writes to the log file by multiple processes on different network hosts. - /// - /// - /// This effectively prevents files from being kept open. - /// - /// - [Obsolete("Instead use KeepFileOpen = false. Marked obsolete with NLog v5.3")] - [EditorBrowsable(EditorBrowsableState.Never)] - public bool NetworkWrites { get => !KeepFileOpen; set => KeepFileOpen = !value; } - /// /// Gets or sets a value indicating whether to write BOM (byte order mark) in created files. - /// - /// Defaults to true for UTF-16 and UTF-32 /// + /// Default: (Unless UTF16 / UTF32) /// public bool WriteBom { @@ -447,122 +217,69 @@ public bool WriteBom private bool? _writeBom; /// - /// Gets or sets the number of times the write is appended on the file before NLog - /// discards the log message. - /// - /// - public int ConcurrentWriteAttempts { get => _concurrentWriteAttempts ?? 10; set => _concurrentWriteAttempts = value; } - private int? _concurrentWriteAttempts; - - /// - /// Gets or sets the delay in milliseconds to wait before attempting to write to the file again. - /// - /// - /// The actual delay is a random value between 0 and the value specified - /// in this parameter. On each failed attempt the delay base is doubled - /// up to times. - /// - /// - /// Assuming that ConcurrentWriteAttemptDelay is 10 the time to wait will be:

- /// a random value between 0 and 10 milliseconds - 1st attempt
- /// a random value between 0 and 20 milliseconds - 2nd attempt
- /// a random value between 0 and 40 milliseconds - 3rd attempt
- /// a random value between 0 and 80 milliseconds - 4th attempt
- /// ...

- /// and so on. - /// - /// - public int ConcurrentWriteAttemptDelay { get; set; } = 1; - - ///

- /// Gets or sets a value indicating whether to archive old log file on startup. + /// Gets or sets a value indicating whether any existing log-file should be archived on startup. /// - /// - /// This option works only when the "FileName" parameter denotes a single file. - /// After archiving the old file, the current log file will be empty. - /// + /// Default: /// - public bool ArchiveOldFileOnStartup - { - get => _archiveOldFileOnStartup ?? false; - set => _archiveOldFileOnStartup = value; - } - private bool? _archiveOldFileOnStartup; + public bool ArchiveOldFileOnStartup { get; set; } /// /// Gets or sets whether to write the Header on initial creation of file appender, even if the file is not empty. /// Default value is , which means only write header when initial file is empty (Ex. ensures valid CSV files) /// /// + /// Default: . /// Alternative use to ensure each application session gets individual log-file. /// - public bool WriteHeaderWhenInitialFileNotEmpty { get; set; } - - /// - /// Gets or sets a value of the file size threshold to archive old log file on startup. - /// - /// - /// This option won't work if is set to false - /// Default value is 0 which means that the file is archived as soon as archival on - /// startup is enabled. - /// /// - public long ArchiveOldFileOnStartupAboveSize { get; set; } + public bool WriteHeaderWhenInitialFileNotEmpty { get; set; } /// - /// Gets or sets a value specifying the date format to use when archiving files. + /// Gets or sets a value specifying the date format when using . + /// Obsolete and only here for Legacy reasons, instead use . /// - /// - /// This option works only when the "ArchiveNumbering" parameter is set either to Date or DateAndSequence. - /// + /// Default: /// - public string ArchiveDateFormat + [Obsolete("Instead use ArchiveSuffixFormat. Marked obsolete with NLog 6.0")] + public string? ArchiveDateFormat { get => _archiveDateFormat; set { - if (_archiveDateFormat != value) - { - _archiveDateFormat = value; - ResetFileAppenders("ArchiveDateFormat Changed"); // Reset archive file-monitoring - } + if (string.Equals(value, _archiveDateFormat)) + return; + + if (!string.IsNullOrEmpty(value)) + ArchiveSuffixFormat = @"_{1:" + value + @"}_{0:00}"; + _archiveDateFormat = value; } } - private string _archiveDateFormat = string.Empty; + private string? _archiveDateFormat; /// - /// Gets or sets the size in bytes above which log files will be automatically archived. + /// Gets or sets the size in bytes above which log files will be automatically archived. Zero or negative means disabled. /// - /// - /// Notice when combined with then it will attempt to append to any existing - /// archive file if grown above size multiple times. New archive file will be created when using - /// + /// Default: /// public long ArchiveAboveSize { get => _archiveAboveSize; set { - var newValue = value > 0 ? value : ArchiveAboveSizeDisabled; - if ((_archiveAboveSize > 0) != (newValue > 0)) - { - _archiveAboveSize = newValue; - ResetFileAppenders("ArchiveAboveSize Changed"); // Reset archive file-monitoring - } - else - { - _archiveAboveSize = newValue; - } + _archiveAboveSize = value > 0 ? value : 0; + _fileArchiveHandler = null; } } + private long _archiveAboveSize; /// - /// Gets or sets a value indicating whether to automatically archive log files every time the specified time passes. + /// Gets or sets a value indicating whether to trigger archive operation based on time-period, by moving active-file to file-path specified by /// /// - /// Files are moved to the archive as part of the write operation if the current period of time changes. For example - /// if the current hour changes from 10 to 11, the first write that will occur - /// on or after 11:00 will trigger the archiving. + /// Default: . + /// Archive move operation only works if is static in nature, and not rolling automatically because of ${date} or ${shortdate} + /// + /// NLog FileTarget probes the file-birthtime to recognize when time-period has passed, but file-birthtime is not supported by all filesystems. /// /// public FileArchivePeriod ArchiveEvery @@ -570,265 +287,260 @@ public FileArchivePeriod ArchiveEvery get => _archiveEvery; set { - if (_archiveEvery != value) - { - _archiveEvery = value; - ResetFileAppenders("ArchiveEvery Changed"); // Reset archive file-monitoring - } + _archiveEvery = value; + _fileArchiveHandler = null; } } + FileArchivePeriod _archiveEvery; /// - /// Is the an absolute or relative path? + /// Legacy archive logic where file-archive-logic moves active file to path specified by , and then recreates the active file. + /// + /// Use to control suffix format, instead of now obsolete token {#} /// + /// + /// Default: . + /// Archive file-move operation only works if is static in nature, and not rolling automatically because of ${date} or ${shortdate} . + /// + /// Legacy archive file-move operation can fail because of file-locks, so file-archiving can stop working because of environment issues (Other applications locking files). + /// + /// Avoid using when possible, and instead rely on only using and . + /// /// - public FilePathKind ArchiveFileKind + public Layout? ArchiveFileName { - get => _archiveFileKind; + get => _archiveFileName ?? (_archiveSuffixFormat?.IndexOf("{1") >= 0 ? FileName : null); set { - if (_archiveFileKind != value) + var archiveSuffixFormat = _archiveSuffixFormat; + if (value is SimpleLayout simpleLayout) { - _archiveFileKind = value; - _fullArchiveFileName = CreateFileNameLayout(ArchiveFileName); - ResetFileAppenders("ArchiveFileKind Changed"); // Reset archive file-monitoring + if (simpleLayout.OriginalText.IndexOf("${date", StringComparison.OrdinalIgnoreCase) >= 0 || simpleLayout.OriginalText.IndexOf("${shortdate", StringComparison.OrdinalIgnoreCase) >= 0) + { + if (_archiveSuffixFormat is null || ReferenceEquals(_legacySequenceArchiveSuffixFormat, _archiveSuffixFormat) || ReferenceEquals(_legacyDateArchiveSuffixFormat, _archiveSuffixFormat)) + { + archiveSuffixFormat = "_{0}"; + } + } + + if (simpleLayout.OriginalText.Contains('#')) + { + var repairLegacyLayout = simpleLayout.OriginalText.Replace(".{#}", string.Empty).Replace("_{#}", "").Replace("-{#}", "").Replace("{#}", "").Replace(".{#", "").Replace("_{#", "").Replace("-{#", "").Replace("{#", "").Replace("#}", "").Replace("#", ""); + archiveSuffixFormat = _archiveSuffixFormat ?? _legacySequenceArchiveSuffixFormat; + value = new SimpleLayout(repairLegacyLayout); + } } - } - } - /// - /// Gets or sets the name of the file to be used for an archive. - /// - /// - /// It may contain a special placeholder {#####} - /// that will be replaced with a sequence of numbers depending on - /// the archiving strategy. The number of hash characters used determines - /// the number of numerical digits to be used for numbering files. - /// - /// - public Layout ArchiveFileName - { - get - { - return _fullArchiveFileName?.GetLayout(); - } - set - { - _fullArchiveFileName = CreateFileNameLayout(value); - ResetFileAppenders("ArchiveFileName Changed"); // Reset archive file-monitoring + _archiveFileName = value; + if (!ReferenceEquals(_archiveSuffixFormat, archiveSuffixFormat) && archiveSuffixFormat != null) + { + ArchiveSuffixFormat = archiveSuffixFormat; + } + _fileArchiveHandler = null; } } + private Layout? _archiveFileName; + private static readonly string _legacyDateArchiveSuffixFormat = "_{1:yyyyMMdd}_{0:00}"; // Cater for ArchiveNumbering.DateAndSequence + private static readonly string _legacySequenceArchiveSuffixFormat = "_{0:00}"; // Cater for ArchiveNumbering.Sequence /// - /// Gets or sets the maximum number of archive files that should be kept. + /// Gets or sets the maximum number of archive files that should be kept. Negative means disabled. /// + /// Default: /// public int MaxArchiveFiles { get => _maxArchiveFiles; set { - if (_maxArchiveFiles != value) - { - _maxArchiveFiles = value; - ResetFileAppenders("MaxArchiveFiles Changed"); // Enforce archive cleanup - } + _maxArchiveFiles = value; + _fileArchiveHandler = null; } } + private int _maxArchiveFiles = -1; /// - /// Gets or sets the maximum days of archive files that should be kept. + /// Gets or sets the maximum days of archive files that should be kept. Zero or negative means disabled. /// + /// Default: /// public int MaxArchiveDays { get => _maxArchiveDays; set { - if (_maxArchiveDays != value) - { - _maxArchiveDays = value; - ResetFileAppenders("MaxArchiveDays Changed"); // Enforce archive cleanup - } + _maxArchiveDays = value > 0 ? value : 0; + _fileArchiveHandler = null; } } + private int _maxArchiveDays; /// /// Gets or sets the way file archives are numbered. + /// Obsolete and only here for Legacy reasons, instead use . /// + /// Default: Sequence /// - public ArchiveNumberingMode ArchiveNumbering + [Obsolete("Instead use ArchiveSuffixFormat. Marked obsolete with NLog 6.0")] + public string ArchiveNumbering { - get => _archiveNumbering; + get => _archiveNumbering ?? "Sequence"; set { - if (_archiveNumbering != value) + if (string.Equals(value, _archiveNumbering)) + return; + + _archiveNumbering = string.IsNullOrEmpty(value) ? null : value.Trim(); + if (_archiveNumbering is null || string.IsNullOrEmpty(_archiveNumbering)) + return; + + if (_archiveSuffixFormat is null || ReferenceEquals(_archiveSuffixFormat, _legacyDateArchiveSuffixFormat) || ReferenceEquals(_archiveSuffixFormat, _legacySequenceArchiveSuffixFormat)) { - _archiveNumbering = value; - ResetFileAppenders("ArchiveNumbering Changed"); // Reset archive file-monitoring + ArchiveSuffixFormat = _archiveNumbering.IndexOf("date", StringComparison.OrdinalIgnoreCase) >= 0 ? _legacyDateArchiveSuffixFormat : _legacySequenceArchiveSuffixFormat; } } } - private ArchiveNumberingMode _archiveNumbering; - - /// - /// Used to compress log files during archiving. - /// This may be used to provide your own implementation of a zip file compressor, - /// on platforms other than .Net4.5. - /// Defaults to ZipArchiveFileCompressor on .Net4.5 and to null otherwise. - /// - /// - public static IFileCompressor FileCompressor { get; set; } + private string? _archiveNumbering; /// - /// Gets or sets a value indicating whether to compress archive files into the zip archive format. + /// Gets or sets the format-string to convert archive sequence-number by using string.Format /// + /// + /// Default: _{0:00} . + /// Ex. to prefix archive sequence number with leading zero's then one can use _{0:000} . + /// + /// Legacy archive-logic with can use suffix _{1:yyyyMMdd}_{0:00} . + /// /// - public bool EnableArchiveFileCompression + public string ArchiveSuffixFormat { - get => _enableArchiveFileCompression && FileCompressor != null; + get + { + if (ArchiveEvery != FileArchivePeriod.None && (_archiveSuffixFormat is null || ReferenceEquals(_legacyDateArchiveSuffixFormat, _archiveSuffixFormat) || ReferenceEquals(_legacySequenceArchiveSuffixFormat, _archiveSuffixFormat)) && ArchiveFileName != null) + { + switch (ArchiveEvery) + { + case FileArchivePeriod.Year: + return "_{1:yyyy}_{0:00}"; + case FileArchivePeriod.Month: + return "_{1:yyyyMM}_{0:00}"; + case FileArchivePeriod.Hour: + return "_{1:yyyyMMddHH}_{0:00}"; + case FileArchivePeriod.Minute: + return "_{1:yyyyMMddHHmm}_{0:00}"; + default: + return _legacyDateArchiveSuffixFormat; // Also for weekdays + } + } + + return _archiveSuffixFormat ?? _legacySequenceArchiveSuffixFormat; + } set { - if (_enableArchiveFileCompression != value) + if (!string.IsNullOrEmpty(value) && ArchiveFileName is SimpleLayout simpleLayout) { - _enableArchiveFileCompression = value; - ResetFileAppenders("EnableArchiveFileCompression Changed"); // Reset archive file-monitoring + // When legacy ArchiveFileName only contains file-extension, then strip away leading underscore from suffix + var fileName = Path.GetFileNameWithoutExtension(simpleLayout.OriginalText); + if (StringHelpers.IsNullOrWhiteSpace(fileName) && value.IndexOf('_') == 0) + { + value = value.Substring(1); + } } + + _archiveSuffixFormat = value; } } - - /// - /// Gets or set a value indicating whether a managed file stream is forced, instead of using the native implementation. - /// - /// - public bool ForceManaged { get; set; } = true; - -#if SupportsMutex - /// - /// Gets or sets a value indicating whether file creation calls should be synchronized by a system global mutex. - /// - /// - public bool ForceMutexConcurrentWrites { get; set; } -#endif + private string? _archiveSuffixFormat; /// /// Gets or sets a value indicating whether the footer should be written only when the file is archived. /// + /// Default: /// public bool WriteFooterOnArchivingOnly { get; set; } - /// - /// Gets the characters that are appended after each line. - /// - protected internal string NewLineChars => LineEnding.NewLineCharacters; - - /// - /// Refresh the ArchiveFilePatternToWatch option of the . - /// The log file must be watched for archiving when multiple processes are writing to the same - /// open file. - /// - private void RefreshArchiveFilePatternToWatch(string fileName, LogEventInfo logEvent) + private int OpenFileMonitorTimerInterval { - _fileAppenderCache.CheckCloseAppenders -= AutoCloseAppendersAfterArchive; - - bool mustWatchArchiving = IsArchivingEnabled && KeepFileOpen && ConcurrentWrites; - bool mustWatchActiveFile = EnableFileDelete && ((KeepFileOpen && ConcurrentWrites) || (IsSimpleKeepFileOpen && !EnableFileDeleteSimpleMonitor)); - if (mustWatchArchiving || mustWatchActiveFile) + get { - _fileAppenderCache.CheckCloseAppenders += AutoCloseAppendersAfterArchive; // Activates FileSystemWatcher + if (OpenFileFlushTimeout <= 0 || AutoFlush || !KeepFileOpen) + return (OpenFileCacheTimeout > 500 && OpenFileCacheTimeout < 3600) ? 300 : OpenFileCacheTimeout; + else if (OpenFileCacheTimeout <= 0) + return OpenFileFlushTimeout; + else + return Math.Min(OpenFileFlushTimeout, OpenFileCacheTimeout); } + } -#if !NETSTANDARD1_3 - if (mustWatchArchiving) - { - string archiveFilePattern = GetArchiveFileNamePattern(fileName, logEvent); - var fileArchiveStyle = !string.IsNullOrEmpty(archiveFilePattern) ? GetFileArchiveHelper(archiveFilePattern) : null; - string fileNameMask = fileArchiveStyle != null ? _fileArchiveHelper.GenerateFileNameMask(archiveFilePattern) : string.Empty; - string directoryMask = !string.IsNullOrEmpty(fileNameMask) ? Path.Combine(Path.GetDirectoryName(archiveFilePattern), fileNameMask) : string.Empty; - _fileAppenderCache.ArchiveFilePatternToWatch = directoryMask; - } - else + private IFileArchiveHandler FileAchiveHandler => _fileArchiveHandler ?? (_fileArchiveHandler = CreateFileArchiveHandler()); + private IFileArchiveHandler? _fileArchiveHandler; + + private +#if !NETFRAMEWORK + readonly +#endif + struct OpenFileAppender + { + public IFileAppender FileAppender { get; } + public int SequenceNumber { get; } + + public OpenFileAppender(IFileAppender fileAppender, int sequenceNumber) { - _fileAppenderCache.ArchiveFilePatternToWatch = null; + FileAppender = fileAppender; + SequenceNumber = sequenceNumber; } -#endif } + private readonly Dictionary _openFileCache = new Dictionary(StringComparer.OrdinalIgnoreCase); + + private readonly ReusableStreamCreator _reusableFileWriteStream = new ReusableStreamCreator(); + private readonly ReusableStreamCreator _reusableBatchFileWriteStream = new ReusableStreamCreator(true); + private readonly ReusableBufferCreator _reusableEncodingBuffer = new ReusableBufferCreator(1024); + + private readonly SortHelpers.KeySelector _getFileNameFromLayout; + + private DateTime _lastWriteTime; + private Timer? _openFileMonitorTimer; + /// - /// Removes records of initialized files that have not been - /// accessed in the last two days. + /// Initializes a new instance of the class. /// /// - /// Files are marked 'initialized' for the purpose of writing footers when the logging finishes. + /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} /// - public void CleanupInitializedFiles() + public FileTarget() { - try - { - CleanupInitializedFiles(TimeSource.Current.Time.AddDays(-InitializedFilesCleanupPeriod)); - } - catch (Exception exception) - { - if (exception.MustBeRethrownImmediately()) - throw; - - InternalLogger.Error(exception, "{0}: Exception in CleanupInitializedFiles", this); - } + _getFileNameFromLayout = l => GetFileNameFromLayout(l.LogEvent); } /// - /// Removes records of initialized files that have not been - /// accessed after the specified date. + /// Initializes a new instance of the class. /// - /// The cleanup threshold. /// - /// Files are marked 'initialized' for the purpose of writing footers when the logging finishes. + /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} /// - public void CleanupInitializedFiles(DateTime cleanupThreshold) + /// Name of the target. + public FileTarget(string name) : this() { - InternalLogger.Trace("{0}: CleanupInitializedFiles with cleanupThreshold {1}", this, cleanupThreshold); - - List filesToFinalize = null; - - // Select the files require to be finalized. - foreach (var file in _initializedFiles) - { - if (file.Value < cleanupThreshold) - { - if (filesToFinalize is null) - { - filesToFinalize = new List(); - } - filesToFinalize.Add(file.Key); - } - } - - // Finalize the files. - if (filesToFinalize != null) - { - foreach (string fileName in filesToFinalize) - { - FinalizeFile(fileName); - } - } - - InternalLogger.Trace("{0}: CleanupInitializedFiles Completed and finalized {0} files", this, filesToFinalize?.Count ?? 0); + Name = name; } /// /// Flushes all pending file operations. /// /// The asynchronous continuation. - /// - /// The timeout parameter is ignored, because file APIs don't provide - /// the needed functionality. - /// protected override void FlushAsync(AsyncContinuation asyncContinuation) { try { InternalLogger.Trace("{0}: FlushAsync", this); - _fileAppenderCache.FlushAppenders(); + if (_openFileCache.Count > 0) + { + foreach (var openFile in _openFileCache) + { + openFile.Value.FileAppender.Flush(); + } + } asyncContinuation(null); InternalLogger.Trace("{0}: FlushAsync Done", this); } @@ -843,239 +555,144 @@ protected override void FlushAsync(AsyncContinuation asyncContinuation) } } - /// - /// Returns the suitable appender factory ( ) to be used to generate the file - /// appenders associated with the instance. - /// - /// The type of the file appender factory returned depends on the values of various properties. - /// - /// suitable for this instance. - private IFileAppenderFactory GetFileAppenderFactory() - { - if (DiscardAll) - { - return NullAppender.TheFactory; - } - else if (!KeepFileOpen) - { - return RetryingMultiProcessFileAppender.TheFactory; - } - else if (ConcurrentWrites) - { -#if SupportsMutex - if (!ForceMutexConcurrentWrites) - { -#if MONO - if (PlatformDetector.IsUnix) - { - return UnixMultiProcessFileAppender.TheFactory; - } -#elif NETFRAMEWORK - if (PlatformDetector.IsWin32 && !PlatformDetector.IsMono) - { - return WindowsMultiProcessFileAppender.TheFactory; - } -#endif - } - - if (MutexDetector.SupportsSharableMutex) - { - return MutexMultiProcessFileAppender.TheFactory; - } - else -#endif // SupportsMutex - { - return RetryingMultiProcessFileAppender.TheFactory; - } - } - else if (IsArchivingEnabled) - return CountingSingleProcessFileAppender.TheFactory; - else - return SingleProcessFileAppender.TheFactory; - } - - private bool IsArchivingEnabled => ArchiveAboveSize != ArchiveAboveSizeDisabled || ArchiveEvery != FileArchivePeriod.None; - - private bool IsSimpleKeepFileOpen => KeepFileOpen && !ConcurrentWrites && !ReplaceFileContentsOnEachWrite; - - private bool EnableFileDeleteSimpleMonitor => EnableFileDelete && IsSimpleKeepFileOpen -#if !NETSTANDARD - && !PlatformDetector.IsWin32 -#endif - ; - - bool ICreateFileParameters.EnableFileDeleteSimpleMonitor => EnableFileDeleteSimpleMonitor; - - /// - /// Initializes file logging by creating data structures that - /// enable efficient multi-file logging. - /// + /// protected override void InitializeTarget() { - base.InitializeTarget(); + if (FileName is null || ReferenceEquals(FileName, Layout.Empty)) + throw new NLogConfigurationException("FileTarget FileName-property must be assigned. FileName is needed for file writing."); - var appenderFactory = GetFileAppenderFactory(); - if (InternalLogger.IsTraceEnabled) + if (OpenFileMonitorTimerInterval > 0) { - InternalLogger.Trace("{0}: Using appenderFactory: {1}", this, appenderFactory.GetType()); + // Prepare Timer for periodic checking of flushing/closing open files (inactive until first file is opened) + _openFileMonitorTimer = new Timer(OpenFileMonitorTimer); } - _fileAppenderCache = new FileAppenderCache(OpenFileCacheSize, appenderFactory, this); - - if ((OpenFileCacheSize > 0 || EnableFileDelete) && (OpenFileCacheTimeout > 0 || OpenFileFlushTimeout > 0)) - { - int openFileAutoTimeoutSecs = (OpenFileCacheTimeout > 0 && OpenFileFlushTimeout > 0) ? Math.Min(OpenFileCacheTimeout, OpenFileFlushTimeout) : Math.Max(OpenFileCacheTimeout, OpenFileFlushTimeout); - InternalLogger.Trace("{0}: Start autoClosingTimer", this); - _autoClosingTimer = new Timer( - (state) => AutoClosingTimerCallback(this, EventArgs.Empty), - null, - openFileAutoTimeoutSecs * 1000, - openFileAutoTimeoutSecs * 1000); - } + base.InitializeTarget(); } - /// - /// Closes the file(s) opened for writing. - /// + /// protected override void CloseTarget() { - base.CloseTarget(); - - foreach (string fileName in new List(_initializedFiles.Keys)) - { - FinalizeFile(fileName); - } - - _fileArchiveHelper = null; + var openFileMonitorTimer = _openFileMonitorTimer; + _openFileMonitorTimer = null; + openFileMonitorTimer?.Change(Timeout.Infinite, Timeout.Infinite); + openFileMonitorTimer?.Dispose(); - var currentTimer = _autoClosingTimer; - if (currentTimer != null) + if (_openFileCache.Count > 0) { - InternalLogger.Trace("{0}: Stop autoClosingTimer", this); - _autoClosingTimer = null; - currentTimer.WaitForDispose(TimeSpan.Zero); + foreach (var openFile in _openFileCache.ToList()) + { + CloseFileWithFooter(openFile.Key, openFile.Value, false); + } + _openFileCache.Clear(); } - _fileAppenderCache.CloseAppenders("Dispose"); - _fileAppenderCache.Dispose(); - } - - private void ResetFileAppenders(string reason) - { - _fileArchiveHelper = null; - if (IsInitialized) - { - _fileAppenderCache.CloseAppenders(reason); - _initializedFiles.Clear(); - } + base.CloseTarget(); } - private readonly ReusableStreamCreator _reusableFileWriteStream = new ReusableStreamCreator(); - private readonly ReusableStreamCreator _reusableBatchFileWriteStream = new ReusableStreamCreator(true); - private readonly ReusableBufferCreator _reusableEncodingBuffer = new ReusableBufferCreator(1024); - - /// - /// Writes the specified logging event to a file specified in the FileName - /// parameter. - /// - /// The logging event. + /// protected override void Write(LogEventInfo logEvent) { - var logFileName = GetFullFileName(logEvent); - if (string.IsNullOrEmpty(logFileName)) + var filename = GetFileNameFromLayout(logEvent); + if (string.IsNullOrEmpty(filename)) { throw new ArgumentException("The path is not of a legal form."); } - using (var targetStream = _reusableBatchFileWriteStream.Allocate()) + try { - using (var targetBuilder = ReusableLayoutBuilder.Allocate()) - using (var targetBuffer = _reusableEncodingBuffer.Allocate()) + using (var targetStream = _reusableBatchFileWriteStream.Allocate()) { - RenderFormattedMessageToStream(logEvent, targetBuilder.Result, targetBuffer.Result, targetStream.Result); - } + using (var targetBuilder = ReusableLayoutBuilder.Allocate()) + using (var targetBuffer = _reusableEncodingBuffer.Allocate()) + { + RenderFormattedMessageToStream(logEvent, targetBuilder.Result, targetBuffer.Result, targetStream.Result); + } - ProcessLogEvent(logEvent, logFileName, new ArraySegment(targetStream.Result.GetBuffer(), 0, (int)targetStream.Result.Length)); + WriteBytesToFile(filename, logEvent, targetStream.Result); + } + } + catch (Exception ex) + { + InternalLogger.Error(ex, "{0}: Failed writing to FileName: '{1}'", this, filename); + throw; } } - /// - /// Get full filename (=absolute) and cleaned if needed. - /// - /// - /// - internal string GetFullFileName(LogEventInfo logEvent) + /// + protected override void Write(IList logEvents) { - if (_fullFileName is null) - return null; + var buckets = logEvents.BucketSort(_getFileNameFromLayout); + foreach (var bucket in buckets) + { + if (string.IsNullOrEmpty(bucket.Key)) + { + InternalLogger.Warn("{0}: FileName Layout returned empty string. The path is not of a legal form.", this); + var emptyPathException = new ArgumentException("The path is not of a legal form."); + for (int i = 0; i < logEvents.Count; ++i) + { + logEvents[i].Continuation(emptyPathException); + } + continue; + } - if (_fullFileName.IsFixedFilePath) - return _fullFileName.Render(logEvent); + try + { + WriteLogEventsToFile(bucket.Key, bucket.Value); + } + catch (Exception ex) + { + InternalLogger.Error(ex, "{0}: Failed writing to FileName: '{1}'", this, bucket.Key); + if (ExceptionMustBeRethrown(ex)) + throw; - using (var targetBuilder = ReusableLayoutBuilder.Allocate()) - { - return _fullFileName.RenderWithBuilder(logEvent, targetBuilder.Result); + for (int i = 0; i < bucket.Value.Count; ++i) + bucket.Value[i].Continuation(ex); + } } } - SortHelpers.KeySelector _getFullFileNameDelegate; - - /// - /// Writes the specified array of logging events to a file specified in the FileName - /// parameter. - /// - /// An array of objects. - /// - /// This function makes use of the fact that the events are batched by sorting - /// the requests by filename. This optimizes the number of open/close calls - /// and can help improve performance. - /// - protected override void Write(IList logEvents) + private void WriteLogEventsToFile(string filename, IList logEvents) { - if (_getFullFileNameDelegate is null) - _getFullFileNameDelegate = c => GetFullFileName(c.LogEvent); - - var buckets = logEvents.BucketSort(_getFullFileNameDelegate); - using (var reusableStream = _reusableBatchFileWriteStream.Allocate()) { var ms = reusableStream.Result ?? new MemoryStream(); - foreach (var bucket in buckets) + int currentIndex = 0; + while (currentIndex < logEvents.Count) { - int bucketCount = bucket.Value.Count; - if (bucketCount <= 0) - continue; - - string fileName = bucket.Key; - if (string.IsNullOrEmpty(fileName)) - { - InternalLogger.Warn("{0}: FileName Layout returned empty string. The path is not of a legal form.", this); - var emptyPathException = new ArgumentException("The path is not of a legal form."); - for (int i = 0; i < bucketCount; ++i) - { - bucket.Value[i].Continuation(emptyPathException); - } - continue; - } + ms.Position = 0; + ms.SetLength(0); - int currentIndex = 0; - while (currentIndex < bucketCount) - { - ms.Position = 0; - ms.SetLength(0); + var logEventWriteCount = WriteToMemoryStream(logEvents, currentIndex, ms); + WriteBytesToFile(filename, logEvents[currentIndex].LogEvent, ms); - var written = WriteToMemoryStream(bucket.Value, currentIndex, ms); - AppendMemoryStreamToFile(fileName, bucket.Value[currentIndex].LogEvent, ms, out var lastException); - for (int i = 0; i < written; ++i) - { - bucket.Value[currentIndex++].Continuation(lastException); - } - } + for (int i = 0; i < logEventWriteCount; ++i) + logEvents[currentIndex++].Continuation(null); } } } + private string GetFileNameFromLayout(LogEventInfo logEvent) + { + if (_fixedFileName != null) + return _fixedFileName; + + var lastFileNameFromLayout = _lastFileNameFromLayout; + using (var targetBuilder = ReusableLayoutBuilder.Allocate()) + { + FileName.Render(logEvent, targetBuilder.Result); + if (targetBuilder.Result.EqualTo(lastFileNameFromLayout)) + return _lastFileNameFromLayout; + + lastFileNameFromLayout = targetBuilder.Result.ToString(); + } + _lastFileNameFromLayout = lastFileNameFromLayout; + return lastFileNameFromLayout; + } + + private string _lastFileNameFromLayout = string.Empty; + private int WriteToMemoryStream(IList logEvents, int startIndex, MemoryStream ms) { long maxBufferSize = BufferSize * 100; // Max Buffer Default = 30 KiloByte * 100 = 3 MegaByte @@ -1106,87 +723,11 @@ private int WriteToMemoryStream(IList logEvents, int startInd return logEvents.Count - startIndex; } - private void ProcessLogEvent(LogEventInfo logEvent, string fileName, ArraySegment bytesToWrite) - { - DateTime previousLogEventTimestamp = InitializeFile(fileName, logEvent); - bool initializedNewFile = previousLogEventTimestamp == DateTime.MinValue; - if (initializedNewFile && fileName == _previousLogFileName && _previousLogEventTimestamp.HasValue) - previousLogEventTimestamp = _previousLogEventTimestamp.Value; - - bool archiveOccurred = TryArchiveFile(fileName, logEvent, bytesToWrite.Count, previousLogEventTimestamp, initializedNewFile); - if (archiveOccurred) - initializedNewFile = InitializeFile(fileName, logEvent) == DateTime.MinValue || initializedNewFile; - - if (ReplaceFileContentsOnEachWrite) - { - ReplaceFileContent(fileName, bytesToWrite, true); - } - else - { - WriteToFile(fileName, bytesToWrite, initializedNewFile); - } - - _previousLogFileName = fileName; - _previousLogEventTimestamp = logEvent.TimeStamp; - } - - /// - /// Obsolete and replaced by with NLog v5. - /// Formats the log event for write. - /// - /// The log event to be formatted. - /// A string representation of the log event. - [Obsolete("No longer used and replaced by RenderFormattedMessage. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected virtual string GetFormattedMessage(LogEventInfo logEvent) - { - return Layout.Render(logEvent); - } - - /// - /// Obsolete and replaced by with NLog v5. - /// Gets the bytes to be written to the file. - /// - /// Log event. - /// Array of bytes that are ready to be written. - [Obsolete("No longer used and replaced by RenderFormattedMessage. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected virtual byte[] GetBytesToWrite(LogEventInfo logEvent) - { - string text = GetFormattedMessage(logEvent); - int textBytesCount = Encoding.GetByteCount(text); - int newLineBytesCount = Encoding.GetByteCount(NewLineChars); - byte[] bytes = new byte[textBytesCount + newLineBytesCount]; - Encoding.GetBytes(text, 0, text.Length, bytes, 0); - Encoding.GetBytes(NewLineChars, 0, NewLineChars.Length, bytes, textBytesCount); - return TransformBytes(bytes); - } - - /// - /// Obsolete and replaced by with NLog v5. - /// Modifies the specified byte array before it gets sent to a file. - /// - /// The byte array. - /// The modified byte array. The function can do the modification in-place. - [Obsolete("No longer used and replaced by TransformStream. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected virtual byte[] TransformBytes(byte[] value) - { - return value; - } - - /// - /// Gets the bytes to be written to the file. - /// - /// The log event to be formatted. - /// to help format log event. - /// Optional temporary char-array to help format log event. - /// Destination for the encoded result. - protected virtual void RenderFormattedMessageToStream(LogEventInfo logEvent, StringBuilder formatBuilder, char[] transformBuffer, MemoryStream streamTarget) + private void RenderFormattedMessageToStream(LogEventInfo logEvent, StringBuilder formatBuilder, char[] transformBuffer, MemoryStream streamTarget) { RenderFormattedMessage(logEvent, formatBuilder); - formatBuilder.Append(NewLineChars); - TransformBuilderToStream(logEvent, formatBuilder, transformBuffer, streamTarget); + formatBuilder.Append(LineEnding.NewLineCharacters); + formatBuilder.CopyToStream(streamTarget, Encoding, transformBuffer); } /// @@ -1199,376 +740,117 @@ protected virtual void RenderFormattedMessage(LogEventInfo logEvent, StringBuild Layout.Render(logEvent, target); } - private void TransformBuilderToStream(LogEventInfo logEvent, StringBuilder builder, char[] transformBuffer, MemoryStream workStream) - { - builder.CopyToStream(workStream, Encoding, transformBuffer); - TransformStream(logEvent, workStream); - } - - /// - /// Modifies the specified byte array before it gets sent to a file. - /// - /// The LogEvent being written - /// The byte array. - protected virtual void TransformStream(LogEventInfo logEvent, MemoryStream stream) - { - } - - private void AppendMemoryStreamToFile(string currentFileName, LogEventInfo firstLogEvent, MemoryStream ms, out Exception lastException) + private void WriteBytesToFile(string filename, LogEventInfo firstLogEvent, MemoryStream ms) { - try + bool hasWritten = true; + if (!_openFileCache.TryGetValue(filename, out var openFile)) { - ArraySegment bytes = new ArraySegment(ms.GetBuffer(), 0, (int)ms.Length); - ProcessLogEvent(firstLogEvent, currentFileName, bytes); - lastException = null; + hasWritten = false; + openFile = OpenFile(filename, firstLogEvent, null); } - catch (Exception exception) + + try { - if (ExceptionMustBeRethrown(exception)) + if (ArchiveAboveSize > 0 || ArchiveEvery != FileArchivePeriod.None) { - throw; + openFile = RollArchiveFile(filename, openFile, firstLogEvent, hasWritten); } - lastException = exception; - } - } - - /// - /// Archives fileName to archiveFileName. - /// - /// File name to be archived. - /// Name of the archive file. - private void ArchiveFile(string fileName, string archiveFileName) - { - string archiveFolderPath = Path.GetDirectoryName(archiveFileName); - if (archiveFolderPath != null && !Directory.Exists(archiveFolderPath)) - Directory.CreateDirectory(archiveFolderPath); + openFile.FileAppender.Write(ms.GetBuffer(), 0, (int)ms.Length); - if (string.Equals(fileName, archiveFileName, StringComparison.OrdinalIgnoreCase)) - { - InternalLogger.Info("{0}: Archiving {1} skipped as ArchiveFileName equals FileName", this, fileName); - } - else if (EnableArchiveFileCompression) - { - InternalLogger.Info("{0}: Archiving {1} to compressed {2}", this, fileName, archiveFileName); - if (File.Exists(archiveFileName)) - { - InternalLogger.Warn("{0}: Failed archiving because compressed file already exists: {1}", this, archiveFileName); - } - else + if (AutoFlush) { - ArchiveFileCompress(fileName, archiveFileName); + openFile.FileAppender.Flush(); } } - else + catch { - InternalLogger.Info("{0}: Archiving {1} to {2}", this, fileName, archiveFileName); - if (File.Exists(archiveFileName)) - { - ArchiveFileAppendExisting(fileName, archiveFileName); - } - else - { - ArchiveFileMove(fileName, archiveFileName); - } + // Close file (any retry-logic must happen inside the FileStream-implementation) + _openFileCache.Remove(filename); + openFile.FileAppender.Dispose(); + throw; + } + finally + { + _lastWriteTime = firstLogEvent.TimeStamp; } } - private void ArchiveFileCompress(string fileName, string archiveFileName) + private OpenFileAppender RollArchiveFile(string filename, OpenFileAppender openFile, LogEventInfo firstLogEvent, bool hasWritten) { - int fileCompressRetryCount = ConcurrentWrites ? ConcurrentWriteAttempts : 2; - for (int i = 1; i <= fileCompressRetryCount; ++i) + var lastSequenceNo = -1; + + bool skipFileLastModified = ArchiveFileName is null; + + while (lastSequenceNo != openFile.SequenceNumber && MustArchiveFile(openFile.FileAppender, firstLogEvent)) { - try - { - if (FileCompressor is IArchiveFileCompressor archiveFileCompressor) - { - string entryName = (ArchiveNumbering != ArchiveNumberingMode.Rolling) ? (Path.GetFileNameWithoutExtension(archiveFileName) + Path.GetExtension(fileName)) : Path.GetFileName(fileName); - archiveFileCompressor.CompressFile(fileName, archiveFileName, entryName); - } - else - { - FileCompressor.CompressFile(fileName, archiveFileName); - } + lastSequenceNo = openFile.SequenceNumber; - break; // Success - } - catch (DirectoryNotFoundException) - { - throw; // Skip retry when directory does not exist - } - catch (FileNotFoundException) - { - throw; // Skip retry when file does not exist - } - catch (IOException ex) - { - if (i == fileCompressRetryCount) - throw; + DateTime? previousFileLastModified = skipFileLastModified ? default(DateTime?) : openFile.FileAppender.FileLastModified; + if (_lastWriteTime > DateTime.MinValue && previousFileLastModified > _lastWriteTime && (previousFileLastModified == openFile.FileAppender.OpenStreamTime || firstLogEvent.TimeStamp.Date == previousFileLastModified?.Date)) + previousFileLastModified = _lastWriteTime; - if (File.Exists(archiveFileName)) - throw; + // Close file and roll to next file + if (hasWritten) + CloseFileWithFooter(filename, openFile, true); + else + CloseFile(filename, openFile); - int sleepTimeMs = i * 50; - InternalLogger.Warn("{0}: Archiving Attempt #{1} to compress {2} to {3} failed - {4} {5}. Sleeping for {6}ms", this, i, fileName, archiveFileName, ex.GetType(), ex.Message, sleepTimeMs); - AsyncHelpers.WaitForDelay(TimeSpan.FromMilliseconds(sleepTimeMs)); - } + hasWritten = false; + openFile = OpenFile(filename, firstLogEvent, previousFileLastModified, openFile.SequenceNumber + 1); } - DeleteAndWaitForFileDelete(fileName); + return openFile; } - private void ArchiveFileAppendExisting(string fileName, string archiveFileName) + private bool MustArchiveFile(IFileAppender fileAppender, LogEventInfo firstLogEvent) { - //todo handle double footer - InternalLogger.Info("{0}: Already exists, append to {1}", this, archiveFileName); + if (ArchiveAboveSize > 0 && MustArchiveBySize(fileAppender)) + return true; - //copy to archive file. - var fileShare = FileShare.ReadWrite; - if (EnableFileDelete) - { - fileShare |= FileShare.Delete; - } + if (ArchiveEvery != FileArchivePeriod.None && MustArchiveEveryTimePeriod(fileAppender, firstLogEvent)) + return true; - using (FileStream fileStream = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, fileShare)) - using (FileStream archiveFileStream = File.Open(archiveFileName, FileMode.Append)) - { - fileStream.CopyAndSkipBom(archiveFileStream, Encoding); - //clear old content - fileStream.SetLength(0); + return false; + } - if (EnableFileDelete && !DeleteOldArchiveFile(fileName)) - { - // Attempt to delete file to reset File-Creation-Time (Delete under file-lock) - fileShare &= ~FileShare.Delete; // Retry after having released file-lock - } - - fileStream.Close(); // This flushes the content, too. -#if !NET35 - archiveFileStream.Flush(true); -#else - archiveFileStream.Flush(); -#endif - } - - if ((fileShare & FileShare.Delete) == FileShare.None) - { - DeleteOldArchiveFile(fileName); // Attempt to delete file to reset File-Creation-Time - } - } - - private void ArchiveFileMove(string fileName, string archiveFileName) - { - try - { - InternalLogger.Debug("{0}: Move file from '{1}' to '{2}'", this, fileName, archiveFileName); - File.Move(fileName, archiveFileName); - } - catch (IOException ex) - { - if (IsSimpleKeepFileOpen) - throw; // No need to retry, when only single process access - - if (!EnableFileDelete && KeepFileOpen) - throw; // No need to retry when file delete has been disabled - - if (ConcurrentWrites && !MutexDetector.SupportsSharableMutex) - throw; // No need to retry when not having a real archive mutex to protect us - - // It is possible to move a file while other processes has open file-handles. - // Unless the other process is actively writing, then the file move might fail. - // We are already holding the archive-mutex, so lets retry if things are stable - InternalLogger.Warn(ex, "{0}: Archiving failed. Checking for retry move of {1} to {2}.", this, fileName, archiveFileName); - if (!File.Exists(fileName) || File.Exists(archiveFileName)) - throw; - - AsyncHelpers.WaitForDelay(TimeSpan.FromMilliseconds(50)); - - if (!File.Exists(fileName) || File.Exists(archiveFileName)) - throw; - - InternalLogger.Debug("{0}: Archiving retrying move of {1} to {2}.", this, fileName, archiveFileName); - File.Move(fileName, archiveFileName); - } - } - - private bool DeleteOldArchiveFile(string fileName) + private bool MustArchiveBySize(IFileAppender fileAppender) { - try - { - InternalLogger.Info("{0}: Deleting old archive file: '{1}'.", this, fileName); - CloseInvalidFileHandle(fileName); - File.Delete(fileName); - return true; - } - catch (DirectoryNotFoundException exception) - { - //never rethrow this, as this isn't an exceptional case. - InternalLogger.Debug(exception, "{0}: Failed to delete old log file '{1}' as directory is missing.", this, fileName); + var currentFileSize = fileAppender.FileSize; + if (currentFileSize == 0 || currentFileSize + 1 < ArchiveAboveSize) return false; - } - catch (Exception exception) - { - InternalLogger.Warn(exception, "{0}: Failed to delete old archive file: '{1}'.", this, fileName); - if (ExceptionMustBeRethrown(exception)) - { - throw; - } - - return false; - } - } - - private void DeleteAndWaitForFileDelete(string fileName) - { - try - { - InternalLogger.Trace("{0}: Waiting for file delete of '{1}' for 12 sec", this, fileName); - var originalFileCreationTime = (new FileInfo(fileName)).CreationTime; - if (DeleteOldArchiveFile(fileName) && File.Exists(fileName)) - { - FileInfo currentFileInfo; - for (int i = 0; i < 120; ++i) - { - AsyncHelpers.WaitForDelay(TimeSpan.FromMilliseconds(100)); - currentFileInfo = new FileInfo(fileName); - if (!currentFileInfo.Exists || currentFileInfo.CreationTime != originalFileCreationTime) - return; - } - - InternalLogger.Warn("{0}: Timeout while deleting old archive file: '{1}'.", this, fileName); - } - } - catch (Exception exception) - { - InternalLogger.Warn(exception, "{0}: Failed to delete old archive file: '{1}'.", this, fileName); - if (ExceptionMustBeRethrown(exception)) - { - throw; - } - } - } - - /// - /// Gets the correct formatting to be used based on the value of for converting values which will be inserting into file - /// names during archiving. - /// - /// This value will be computed only when a empty value or is passed into - /// - /// Date format to used irrespectively of value. - /// Formatting for dates. - private string GetArchiveDateFormatString(string defaultFormat) - { - // If archiveDateFormat is not set in the config file, use a default - // date format string based on the archive period. - if (!string.IsNullOrEmpty(defaultFormat)) - return defaultFormat; - - switch (ArchiveEvery) - { - case FileArchivePeriod.Year: - return "yyyy"; - case FileArchivePeriod.Month: - return "yyyyMM"; - case FileArchivePeriod.Hour: - return "yyyyMMddHH"; - case FileArchivePeriod.Minute: - return "yyyyMMddHHmm"; - default: - return "yyyyMMdd"; // Also for Weekdays - } - } - - private DateTime? GetArchiveDate(string fileName, LogEventInfo logEvent, DateTime previousLogEventTimestamp) - { - // Using File LastModified to handle FileArchivePeriod.Month (where file creation time is one month ago) - var fileLastModifiedUtc = _fileAppenderCache.GetFileLastWriteTimeUtc(fileName); - - InternalLogger.Trace("{0}: Calculating archive date. File-LastModifiedUtc: {1}; Previous LogEvent-TimeStamp: {2}", this, fileLastModifiedUtc, previousLogEventTimestamp); - if (!fileLastModifiedUtc.HasValue) - { - if (previousLogEventTimestamp == DateTime.MinValue) - { - InternalLogger.Info("{0}: Unable to acquire useful timestamp to archive file: {1}", this, fileName); - return null; - } - return previousLogEventTimestamp; - } - - var lastWriteTimeSource = Time.TimeSource.Current.FromSystemTime(fileLastModifiedUtc.Value); - if (previousLogEventTimestamp != DateTime.MinValue) - { - if (previousLogEventTimestamp > lastWriteTimeSource) - { - InternalLogger.Trace("{0}: Using previous LogEvent-TimeStamp {1}, because more recent than File-LastModified {2}", this, previousLogEventTimestamp, lastWriteTimeSource); - return previousLogEventTimestamp; - } - - if (PreviousLogOverlappedPeriod(logEvent, previousLogEventTimestamp, lastWriteTimeSource)) - { - InternalLogger.Trace("{0}: Using previous LogEvent-TimeStamp {1}, because archive period is overlapping with File-LastModified {2}", this, previousLogEventTimestamp, lastWriteTimeSource); - return previousLogEventTimestamp; - } - - if (!AutoFlush && IsSimpleKeepFileOpen && previousLogEventTimestamp < lastWriteTimeSource) - { - InternalLogger.Trace("{0}: Using previous LogEvent-TimeStamp {1}, because AutoFlush=false affects File-LastModified {2}", this, previousLogEventTimestamp, lastWriteTimeSource); - return previousLogEventTimestamp; - } - } - InternalLogger.Trace("{0}: Using last write time: {1}", this, lastWriteTimeSource); - return lastWriteTimeSource; + InternalLogger.Debug("{0}: Archive because of filesize={1} of file: {2}", this, currentFileSize, fileAppender.FilePath); + return true; } - private bool PreviousLogOverlappedPeriod(LogEventInfo logEvent, DateTime previousLogEventTimestamp, DateTime lastFileWrite) + private bool MustArchiveEveryTimePeriod(IFileAppender fileAppender, LogEventInfo firstLogEvent) { - string formatString = GetArchiveDateFormatString(string.Empty); - string lastWriteTimeString = lastFileWrite.ToString(formatString, CultureInfo.InvariantCulture); - string logEventTimeString = logEvent.TimeStamp.ToString(formatString, CultureInfo.InvariantCulture); - - if (lastWriteTimeString != logEventTimeString) + var nextArchiveTime = fileAppender.NextArchiveTime; + if (nextArchiveTime >= firstLogEvent.TimeStamp) return false; - DateTime? periodAfterPreviousLogEventTime = CalculateNextArchiveEventTime(previousLogEventTimestamp); - if (!periodAfterPreviousLogEventTime.HasValue) - return false; - - string periodAfterPreviousLogEventTimeString = periodAfterPreviousLogEventTime.Value.ToString(formatString, CultureInfo.InvariantCulture); - return lastWriteTimeString == periodAfterPreviousLogEventTimeString; + InternalLogger.Debug("{0}: Archive because of filetime of file: {1}", this, fileAppender.FilePath); + return true; } - DateTime? CalculateNextArchiveEventTime(DateTime timestamp) + internal static DateTime CalculateNextArchiveEventTime(FileArchivePeriod archiveEvery, DateTime fileBirthTime) { - switch (ArchiveEvery) + switch (archiveEvery) { - case FileArchivePeriod.Year: - return timestamp.AddYears(1); - case FileArchivePeriod.Month: - return timestamp.AddMonths(1); - case FileArchivePeriod.Day: - return timestamp.AddDays(1); - case FileArchivePeriod.Hour: - return timestamp.AddHours(1); - case FileArchivePeriod.Minute: - return timestamp.AddMinutes(1); - case FileArchivePeriod.Sunday: - return CalculateNextWeekday(timestamp, DayOfWeek.Sunday); - case FileArchivePeriod.Monday: - return CalculateNextWeekday(timestamp, DayOfWeek.Monday); - case FileArchivePeriod.Tuesday: - return CalculateNextWeekday(timestamp, DayOfWeek.Tuesday); - case FileArchivePeriod.Wednesday: - return CalculateNextWeekday(timestamp, DayOfWeek.Wednesday); - case FileArchivePeriod.Thursday: - return CalculateNextWeekday(timestamp, DayOfWeek.Thursday); - case FileArchivePeriod.Friday: - return CalculateNextWeekday(timestamp, DayOfWeek.Friday); - case FileArchivePeriod.Saturday: - return CalculateNextWeekday(timestamp, DayOfWeek.Saturday); - default: - return null; + case FileArchivePeriod.Year: return new DateTime(fileBirthTime.Year, 1, 1, 0, 0, 0, fileBirthTime.Kind).AddYears(1); + case FileArchivePeriod.Month: return new DateTime(fileBirthTime.Year, fileBirthTime.Month, 1, 0, 0, 0, fileBirthTime.Kind).AddMonths(1); + case FileArchivePeriod.Day: return new DateTime(fileBirthTime.Year, fileBirthTime.Month, fileBirthTime.Day, 0, 0, 0, fileBirthTime.Kind).AddDays(1); + case FileArchivePeriod.Hour: return new DateTime(fileBirthTime.Year, fileBirthTime.Month, fileBirthTime.Day, fileBirthTime.Hour, 0, 0, fileBirthTime.Kind).AddHours(1); + case FileArchivePeriod.Minute: return new DateTime(fileBirthTime.Year, fileBirthTime.Month, fileBirthTime.Day, fileBirthTime.Hour, fileBirthTime.Minute, 0, fileBirthTime.Kind).AddMinutes(1); + case FileArchivePeriod.Monday: return CalculateNextWeekday(fileBirthTime, DayOfWeek.Monday); + case FileArchivePeriod.Tuesday: return CalculateNextWeekday(fileBirthTime, DayOfWeek.Tuesday); + case FileArchivePeriod.Wednesday: return CalculateNextWeekday(fileBirthTime, DayOfWeek.Wednesday); + case FileArchivePeriod.Thursday: return CalculateNextWeekday(fileBirthTime, DayOfWeek.Thursday); + case FileArchivePeriod.Friday: return CalculateNextWeekday(fileBirthTime, DayOfWeek.Friday); + case FileArchivePeriod.Saturday: return CalculateNextWeekday(fileBirthTime, DayOfWeek.Saturday); + case FileArchivePeriod.Sunday: return CalculateNextWeekday(fileBirthTime, DayOfWeek.Sunday); + default: return DateTime.MaxValue; } } @@ -1587,970 +869,484 @@ public static DateTime CalculateNextWeekday(DateTime previousLogEventTimestamp, int target = (int)dayOfWeek; if (target <= start) target += 7; - return previousLogEventTimestamp.AddDays(target - start); - } - - /// - /// Invokes the archiving process after determining when and which type of archiving is required. - /// - /// File name to be checked and archived. - /// Log event that the instance is currently processing. - /// The DateTime of the previous log event for this file. - /// File has just been opened. - private void DoAutoArchive(string fileName, LogEventInfo eventInfo, DateTime previousLogEventTimestamp, bool initializedNewFile) - { - InternalLogger.Debug("{0}: Do archive file: '{1}'", this, fileName); - var fileInfo = new FileInfo(fileName); - if (!fileInfo.Exists) - { - CloseInvalidFileHandle(fileName); // Close possible stale file handles - return; - } - - string archiveFilePattern = GetArchiveFileNamePattern(fileName, eventInfo); - if (string.IsNullOrEmpty(archiveFilePattern)) - { - InternalLogger.Warn("{0}: Skip auto archive because archiveFilePattern is blank", this); - return; - } - - DateTime? archiveDate = GetArchiveDate(fileName, eventInfo, previousLogEventTimestamp); - - var archiveFileName = GenerateArchiveFileNameAfterCleanup(fileName, fileInfo, archiveFilePattern, archiveDate, initializedNewFile); - if (!string.IsNullOrEmpty(archiveFileName)) - { - ArchiveFile(fileInfo.FullName, archiveFileName); - } - } - - private string GenerateArchiveFileNameAfterCleanup(string fileName, FileInfo fileInfo, string archiveFilePattern, DateTime? archiveDate, bool initializedNewFile) - { - InternalLogger.Trace("{0}: Archive pattern '{1}'", this, archiveFilePattern); - - var fileArchiveStyle = GetFileArchiveHelper(archiveFilePattern); - var existingArchiveFiles = fileArchiveStyle.GetExistingArchiveFiles(archiveFilePattern); - - if (MaxArchiveFiles == 1) - { - InternalLogger.Trace("{0}: MaxArchiveFiles = 1", this); - // Perform archive cleanup before generating the next filename, - // as next archive-filename can be affected by existing files. - for (int i = existingArchiveFiles.Count - 1; i >= 0; i--) - { - var oldArchiveFile = existingArchiveFiles[i]; - if (!string.Equals(oldArchiveFile.FileName, fileInfo.FullName, StringComparison.OrdinalIgnoreCase)) - { - DeleteOldArchiveFile(oldArchiveFile.FileName); - existingArchiveFiles.RemoveAt(i); - } - } - - if (initializedNewFile && string.Equals(Path.GetDirectoryName(archiveFilePattern), fileInfo.DirectoryName, StringComparison.OrdinalIgnoreCase)) - { - DeleteOldArchiveFile(fileName); - return null; - } - } - - var archiveFileName = archiveDate.HasValue ? fileArchiveStyle.GenerateArchiveFileName(archiveFilePattern, archiveDate.Value, existingArchiveFiles) : null; - if (archiveFileName is null) - return null; - - if (!initializedNewFile) - { - FinalizeFile(fileName, isArchiving: true); - } - - if (existingArchiveFiles.Count > 0) - { - CleanupOldArchiveFiles(fileInfo, archiveFilePattern, existingArchiveFiles, archiveFileName); - } - - return archiveFileName.FileName; - } - - private void CleanupOldArchiveFiles(FileInfo currentFile, string archiveFilePattern, List existingArchiveFiles, DateAndSequenceArchive newArchiveFile = null) - { - var fileArchiveStyle = GetFileArchiveHelper(archiveFilePattern); - - if (fileArchiveStyle.IsArchiveCleanupEnabled) - { - if (currentFile != null) - ExcludeActiveFileFromOldArchiveFiles(currentFile, existingArchiveFiles); - - if (newArchiveFile != null) - existingArchiveFiles.Add(newArchiveFile); - - var cleanupArchiveFiles = fileArchiveStyle.CheckArchiveCleanup(archiveFilePattern, existingArchiveFiles, MaxArchiveFiles, MaxArchiveDays); - foreach (var oldArchiveFile in cleanupArchiveFiles) - { - DeleteOldArchiveFile(oldArchiveFile.FileName); - } - } - } - - private static void ExcludeActiveFileFromOldArchiveFiles(FileInfo currentFile, List existingArchiveFiles) - { - if (existingArchiveFiles.Count > 0) - { - var archiveDirectory = Path.GetDirectoryName(existingArchiveFiles[0].FileName); - if (string.Equals(archiveDirectory, currentFile.DirectoryName, StringComparison.OrdinalIgnoreCase)) - { - // Extra handling when archive-directory is the same as logging-directory - for (int i = 0; i < existingArchiveFiles.Count; ++i) - { - if (string.Equals(existingArchiveFiles[i].FileName, currentFile.FullName, StringComparison.OrdinalIgnoreCase)) - { - existingArchiveFiles.RemoveAt(i); - break; - } - } - } - } - } - - /// - /// Gets the pattern that archive files will match - /// - /// Filename of the log file - /// Log event that the instance is currently processing. - /// A string with a pattern that will match the archive filenames - private string GetArchiveFileNamePattern(string fileName, LogEventInfo eventInfo) - { - if (_fullArchiveFileName is null) - { - if (EnableArchiveFileCompression) - return Path.ChangeExtension(fileName, ".zip"); - else - return fileName; - } - else - { - //The archive file name is given. There are two possibilities - //(1) User supplied the Filename with pattern - //(2) User supplied the normal filename - string archiveFileName = _fullArchiveFileName.Render(eventInfo); - return archiveFileName; - } - } - - /// - /// Archives the file if it should be archived. - /// - /// The file name to check for. - /// Log event that the instance is currently processing. - /// The size in bytes of the next chunk of data to be written in the file. - /// The DateTime of the previous log event for this file. - /// File has just been opened. - /// True when archive operation of the file was completed (by this target or a concurrent target) - private bool TryArchiveFile(string fileName, LogEventInfo ev, int upcomingWriteSize, DateTime previousLogEventTimestamp, bool initializedNewFile) - { - if (!IsArchivingEnabled) - return false; - - string archiveFile = string.Empty; - - BaseFileAppender archivedAppender = null; - - try - { - archiveFile = GetArchiveFileName(fileName, ev, upcomingWriteSize, previousLogEventTimestamp, initializedNewFile); - if (!string.IsNullOrEmpty(archiveFile)) - { - archivedAppender = TryCloseFileAppenderBeforeArchive(fileName, archiveFile); - } - -#if !NETSTANDARD1_3 - // Closes all file handles if any archive operation has been detected by file-watcher - _fileAppenderCache.InvalidateAppendersForArchivedFiles(); -#endif - } - catch (Exception exception) - { - InternalLogger.Warn(exception, "{0}: Failed to check archive for file '{1}'.", this, fileName); - if (ExceptionMustBeRethrown(exception)) - { - throw; - } - } - - if (string.IsNullOrEmpty(archiveFile)) - return false; - - try - { -#if SupportsMutex - try - { - if (archivedAppender is BaseMutexFileAppender mutexFileAppender && mutexFileAppender.ArchiveMutex != null) - { - mutexFileAppender.ArchiveMutex.WaitOne(); - } - else if (!IsSimpleKeepFileOpen) - { - InternalLogger.Debug("{0}: Archive mutex not available for file '{1}'", this, archiveFile); - } - } - catch (AbandonedMutexException) - { - // ignore the exception, another process was killed without properly releasing the mutex - // the mutex has been acquired, so proceed to writing - // See: https://msdn.microsoft.com/en-us/library/system.threading.abandonedmutexexception.aspx - } -#endif - - ArchiveFileAfterCloseFileAppender(archivedAppender, archiveFile, ev, upcomingWriteSize, previousLogEventTimestamp); - return true; - } - finally - { -#if SupportsMutex - if (archivedAppender is BaseMutexFileAppender mutexFileAppender) - mutexFileAppender.ArchiveMutex?.ReleaseMutex(); -#endif - archivedAppender?.Dispose(); // Dispose of Archive Mutex - } + return previousLogEventTimestamp.Date.AddDays(target - start); } - /// - /// Closes any active file-appenders that matches the input filenames. - /// File-appender is requested to invalidate/close its filehandle, but keeping its archive-mutex alive - /// - private BaseFileAppender TryCloseFileAppenderBeforeArchive(string fileName, string archiveFile) + private OpenFileAppender OpenFile(string filename, LogEventInfo firstLogEvent, DateTime? previousFileLastModified, int sequenceNumber = 0) { - InternalLogger.Trace("{0}: Archive attempt for file '{1}'", this, archiveFile); - BaseFileAppender archivedAppender = _fileAppenderCache.InvalidateAppender(fileName); - if (fileName != archiveFile) - { - var fileAppender = _fileAppenderCache.InvalidateAppender(archiveFile); - archivedAppender = archivedAppender ?? fileAppender; - } - - if (!string.IsNullOrEmpty(_previousLogFileName) && _previousLogFileName != archiveFile && _previousLogFileName != fileName) - { - var fileAppender = _fileAppenderCache.InvalidateAppender(_previousLogFileName); - archivedAppender = archivedAppender ?? fileAppender; - } - - return archivedAppender; - } - - private void ArchiveFileAfterCloseFileAppender(BaseFileAppender archivedAppender, string archiveFile, LogEventInfo ev, int upcomingWriteSize, DateTime previousLogEventTimestamp) - { - try - { - DateTime fallbackFileCreationTimeSource = previousLogEventTimestamp; - - if (archivedAppender != null && IsSimpleKeepFileOpen) - { - var fileCreationTimeUtc = archivedAppender.GetFileCreationTimeUtc(); - if (fileCreationTimeUtc > DateTime.MinValue) - { - var fileCreationTimeSource = Time.TimeSource.Current.FromSystemTime(fileCreationTimeUtc.Value); - if (fileCreationTimeSource < fallbackFileCreationTimeSource || fallbackFileCreationTimeSource == DateTime.MinValue) - { - fallbackFileCreationTimeSource = fileCreationTimeSource; - } - } - } + bool createDirs = sequenceNumber == 0 && CreateDirs && _openFileCache.Count == 0; - // Check again if archive is needed. We could have been raced by another process - var validatedArchiveFile = GetArchiveFileName(archiveFile, ev, upcomingWriteSize, fallbackFileCreationTimeSource, false); - if (string.IsNullOrEmpty(validatedArchiveFile)) - { - InternalLogger.Debug("{0}: Skip archiving '{1}' because no longer necessary", this, archiveFile); - _initializedFiles.Remove(archiveFile); - } - else - { - if (archiveFile != validatedArchiveFile) - { - _initializedFiles.Remove(archiveFile); - archiveFile = validatedArchiveFile; - } - _initializedFiles.Remove(archiveFile); - - DoAutoArchive(archiveFile, ev, previousLogEventTimestamp, false); - } - - if (_previousLogFileName == archiveFile) - { - _previousLogFileName = null; - _previousLogEventTimestamp = null; - } - } - catch (Exception exception) - { - InternalLogger.Warn(exception, "{0}: Failed to archive file '{1}'.", this, archiveFile); - if (ExceptionMustBeRethrown(exception)) - { - throw; - } - } - } - - /// - /// Indicates if the automatic archiving process should be executed. - /// - /// File name to be written. - /// Log event that the instance is currently processing. - /// The size in bytes of the next chunk of data to be written in the file. - /// The DateTime of the previous log event for this file. - /// File has just been opened. - /// Filename to archive. If null, then nothing to archive. - private string GetArchiveFileName(string fileName, LogEventInfo ev, int upcomingWriteSize, DateTime previousLogEventTimestamp, bool initializedNewFile) - { - fileName = fileName ?? _previousLogFileName; - if (!string.IsNullOrEmpty(fileName)) - { - return GetArchiveFileNameBasedOnFileSize(fileName, upcomingWriteSize, initializedNewFile) ?? - GetArchiveFileNameBasedOnTime(fileName, ev, previousLogEventTimestamp, initializedNewFile); - } - - return null; - } - - /// - /// Returns the correct filename to archive - /// - private string GetPotentialFileForArchiving(string fileName) - { - if (!string.IsNullOrEmpty(fileName)) - { - return fileName; - } - - if (!string.IsNullOrEmpty(_previousLogFileName)) - { - return _previousLogFileName; - } - - return fileName; - } - - /// - /// Gets the file name for archiving, or null if archiving should not occur based on file size. - /// - /// File name to be written. - /// The size in bytes of the next chunk of data to be written in the file. - /// File has just been opened. - /// Filename to archive. If null, then nothing to archive. - private string GetArchiveFileNameBasedOnFileSize(string fileName, int upcomingWriteSize, bool initializedNewFile) - { - if (ArchiveAboveSize == ArchiveAboveSizeDisabled) - { - return null; - } + PruneOpenFileCache(); - var archiveFileName = GetPotentialFileForArchiving(fileName); - if (string.IsNullOrEmpty(archiveFileName)) - { - return null; - } + sequenceNumber = FileAchiveHandler.ArchiveBeforeOpenFile(filename, firstLogEvent, previousFileLastModified, sequenceNumber); + var fullFilePath = BuildFullFilePath(filename, sequenceNumber); - //this is an expensive call - var fileLength = _fileAppenderCache.GetFileLength(archiveFileName); - if (!fileLength.HasValue) + if (createDirs) { - archiveFileName = TryFallbackToPreviousLogFileName(archiveFileName, initializedNewFile); - if (!string.IsNullOrEmpty(archiveFileName)) - { - upcomingWriteSize = 0; - return GetArchiveFileNameBasedOnFileSize(archiveFileName, upcomingWriteSize, false); - } - else + InternalLogger.Debug("{0}: Verify directory and creating writer to file: {1}", this, fullFilePath); + + var directory = Path.GetDirectoryName(fullFilePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { - return null; + Directory.CreateDirectory(directory); } } - - if (archiveFileName != fileName) - { - upcomingWriteSize = 0; // Not going to write to this file - } - - var shouldArchive = (fileLength.Value + upcomingWriteSize) > ArchiveAboveSize; - if (shouldArchive) + else { - InternalLogger.Debug("{0}: Start archiving '{1}' because FileSize={2} + {3} is larger than ArchiveAboveSize={4}", this, archiveFileName, fileLength.Value, upcomingWriteSize, ArchiveAboveSize); - return archiveFileName; // Will re-check if archive is still necessary after flush/close file + InternalLogger.Debug("{0}: Creating writer to file: {1}", this, fullFilePath); } - return null; - } - - /// - /// Check if archive operation should check previous filename, because FileAppenderCache tells us current filename no longer exists - /// - private string TryFallbackToPreviousLogFileName(string archiveFileName, bool initializedNewFile) - { - if (!initializedNewFile && _initializedFiles.Remove(archiveFileName)) - { - // Register current filename needs re-initialization - InternalLogger.Debug("{0}: Invalidate appender as archive file no longer exists: '{1}'", this, archiveFileName); - _fileAppenderCache.InvalidateAppender(archiveFileName)?.Dispose(); - } + var fileAppender = CreateFileAppender(fullFilePath); + var openFile = new OpenFileAppender(fileAppender, sequenceNumber); + _openFileCache[filename] = openFile; - if (!string.IsNullOrEmpty(_previousLogFileName) && !string.Equals(archiveFileName, _previousLogFileName, StringComparison.OrdinalIgnoreCase)) + if (_openFileCache.Count == 1) { - return _previousLogFileName; + _openFileMonitorTimer?.Change(OpenFileMonitorTimerInterval * 1000, Timeout.Infinite); } - return string.Empty; + return openFile; } - /// - /// Returns the file name for archiving, or null if archiving should not occur based on date/time. - /// - /// File name to be written. - /// Log event that the instance is currently processing. - /// The DateTime of the previous log event for this file. - /// File has just been opened. - /// Filename to archive. If null, then nothing to archive. - private string GetArchiveFileNameBasedOnTime(string fileName, LogEventInfo logEvent, DateTime previousLogEventTimestamp, bool initializedNewFile) + private void PruneOpenFileCache() { - if (ArchiveEvery == FileArchivePeriod.None) - { - return null; - } - - var archiveFileName = GetPotentialFileForArchiving(fileName); - if (string.IsNullOrEmpty(archiveFileName)) - { - return null; - } - - DateTime? creationTimeSource = TryGetArchiveFileCreationTimeSource(archiveFileName, previousLogEventTimestamp); - if (!creationTimeSource.HasValue) + while (_openFileCache.Count > 0) { - archiveFileName = TryFallbackToPreviousLogFileName(archiveFileName, initializedNewFile); - if (!string.IsNullOrEmpty(archiveFileName)) - { - return GetArchiveFileNameBasedOnTime(archiveFileName, logEvent, previousLogEventTimestamp, false); - } - else + // Close files if the filepath no longer exists (without writing footer) + KeyValuePair openFileDeleted = default; + foreach (var openFile in _openFileCache) { - return null; + if (!openFile.Value.FileAppender.VerifyFileExists()) + { + openFileDeleted = openFile; + break; + } } + + if (string.IsNullOrEmpty(openFileDeleted.Key)) + break; + + CloseFile(openFileDeleted.Key, openFileDeleted.Value); } - DateTime fileCreateTime = TruncateArchiveTime(creationTimeSource.Value, ArchiveEvery); - DateTime logEventTime = TruncateArchiveTime(logEvent.TimeStamp, ArchiveEvery); - if (fileCreateTime != logEventTime) + while (_openFileCache.Count >= OpenFileCacheSize) { - string formatString = GetArchiveDateFormatString(string.Empty); - var validLogEventTime = EnsureValidLogEventTimeStamp(logEvent.TimeStamp, creationTimeSource.Value); - string fileCreated = creationTimeSource.Value.ToString(formatString, CultureInfo.InvariantCulture); - string logEventRecorded = validLogEventTime.ToString(formatString, CultureInfo.InvariantCulture); - var shouldArchive = fileCreated != logEventRecorded; - if (shouldArchive) + // Close the oldest filestream (not the least recently used) + DateTime oldestFileTime = DateTime.MaxValue; + KeyValuePair oldestOpenFile = default; + foreach (var oldOpenFile in _openFileCache) { - InternalLogger.Debug("{0}: Start archiving '{1}' because FileCreatedTime='{2}' is older than now '{3}' using ArchiveEvery='{4}'", this, archiveFileName, fileCreated, logEventRecorded, formatString); - return archiveFileName; // Will re-check if archive is still necessary after flush/close file + if (oldOpenFile.Value.FileAppender.OpenStreamTime < oldestFileTime) + { + oldestOpenFile = oldOpenFile; + } } - } + if (!string.IsNullOrEmpty(oldestOpenFile.Key)) + break; - return null; + CloseFileWithFooter(oldestOpenFile.Key, oldestOpenFile.Value, false); + } } - private DateTime? TryGetArchiveFileCreationTimeSource(string fileName, DateTime previousLogEventTimestamp) + internal void CloseOpenFileBeforeArchiveCleanup(string filepath) { - // Linux FileSystems doesn't always have file-birth-time, so NLog tries to provide a little help - DateTime? fallbackTimeSourceLinux = (previousLogEventTimestamp != DateTime.MinValue && IsSimpleKeepFileOpen) ? previousLogEventTimestamp : (DateTime?)null; - var creationTimeSource = _fileAppenderCache.GetFileCreationTimeSource(fileName, fallbackTimeSourceLinux); - if (!creationTimeSource.HasValue) - return null; + KeyValuePair foundOpenFile; + + string fileName = _openFileCache.Count > 0 ? Path.GetFileName(filepath) : string.Empty; - if (previousLogEventTimestamp > DateTime.MinValue && previousLogEventTimestamp < creationTimeSource) + do { - if (TruncateArchiveTime(previousLogEventTimestamp, FileArchivePeriod.Minute) < TruncateArchiveTime(creationTimeSource.Value, FileArchivePeriod.Minute) && PlatformDetector.IsUnix) + foundOpenFile = default; + + foreach (var openFile in _openFileCache) { - if (IsSimpleKeepFileOpen) - { - InternalLogger.Debug("{0}: Adjusted file creation time from {1} to {2}. Linux FileSystem probably don't support file birthtime.", this, creationTimeSource, previousLogEventTimestamp); - creationTimeSource = previousLogEventTimestamp; - } - else + var openFileName = Path.GetFileName(openFile.Value.FileAppender.FilePath); + if (string.Equals(fileName, openFileName, StringComparison.OrdinalIgnoreCase)) { - InternalLogger.Debug("{0}: File creation time {1} newer than previous file write time {2}. Linux FileSystem probably don't support file birthtime, unless multiple applications are writing to the same file. Configure FileTarget.KeepFileOpen=true AND FileTarget.ConcurrentWrites=false, so NLog can fix this.", this, creationTimeSource, previousLogEventTimestamp); + foundOpenFile = openFile; + break; } } - } - return creationTimeSource; + if (foundOpenFile.Key != null) + { + InternalLogger.Debug("{0}: Archive cleanup closing file: {1}", this, filepath); + CloseFile(foundOpenFile.Key, foundOpenFile.Value); + } + } while (foundOpenFile.Key != null); } - /// - /// Truncates the input-time, so comparison of low resolution times (like dates) are not affected by ticks - /// - /// High resolution Time - /// Time Resolution Level - /// Truncated Low Resolution Time - private static DateTime TruncateArchiveTime(DateTime input, FileArchivePeriod resolution) + private void CloseFile(string filename, OpenFileAppender openFile) { - switch (resolution) + try + { + _openFileCache.Remove(filename); + if (_openFileCache.Count == 0) + { + _openFileMonitorTimer?.Change(Timeout.Infinite, Timeout.Infinite); + } + } + finally { - case FileArchivePeriod.Year: - return new DateTime(input.Year, 1, 1, 0, 0, 0, 0, input.Kind); - case FileArchivePeriod.Month: - return new DateTime(input.Year, input.Month, 1, 0, 0, 0, input.Kind); - case FileArchivePeriod.Day: - return input.Date; - case FileArchivePeriod.Hour: - return input.AddTicks(-(input.Ticks % TimeSpan.TicksPerHour)); - case FileArchivePeriod.Minute: - return input.AddTicks(-(input.Ticks % TimeSpan.TicksPerMinute)); - case FileArchivePeriod.Sunday: - return CalculateNextWeekday(input.Date, DayOfWeek.Sunday); - case FileArchivePeriod.Monday: - return CalculateNextWeekday(input.Date, DayOfWeek.Monday); - case FileArchivePeriod.Tuesday: - return CalculateNextWeekday(input.Date, DayOfWeek.Tuesday); - case FileArchivePeriod.Wednesday: - return CalculateNextWeekday(input.Date, DayOfWeek.Wednesday); - case FileArchivePeriod.Thursday: - return CalculateNextWeekday(input.Date, DayOfWeek.Thursday); - case FileArchivePeriod.Friday: - return CalculateNextWeekday(input.Date, DayOfWeek.Friday); - case FileArchivePeriod.Saturday: - return CalculateNextWeekday(input.Date, DayOfWeek.Saturday); - default: - return input; // Unknown time-resolution-truncate, leave unchanged + openFile.FileAppender.Dispose(); } } - private void AutoCloseAppendersAfterArchive(object sender, EventArgs state) + private void CloseFileWithFooter(string filename, OpenFileAppender openFile, bool archiveFile) { - bool lockTaken = Monitor.TryEnter(SyncRoot, TimeSpan.FromSeconds(2)); - if (!lockTaken) - return; // Archive events triggered by FileWatcher are important, but not life critical - try { - if (!IsInitialized) + if (!ReplaceFileContentsOnEachWrite && (!WriteFooterOnArchivingOnly || archiveFile)) { - return; + var footerBytes = GetFooterLayoutBytes(); + if (footerBytes?.Length > 0) + { + openFile.FileAppender.Write(footerBytes, 0, footerBytes.Length); + } } - - InternalLogger.Trace("{0}: Auto Close FileAppenders after archive", this); - _fileAppenderCache.CloseExpiredAppenders(DateTime.MinValue); } - catch (Exception exception) + catch (Exception ex) { -#if DEBUG - if (exception.MustBeRethrownImmediately()) - { - throw; // Throwing exceptions here will crash the entire application (.NET 2.0 behavior) - } -#endif - - InternalLogger.Warn(exception, "{0}: Exception in AutoCloseAppendersAfterArchive", this); + InternalLogger.Error(ex, "{0}: Failed closing file: '{1}'", this, filename); + if (ex.MustBeRethrownImmediately()) + throw; } finally { - Monitor.Exit(SyncRoot); + CloseFile(filename, openFile); } } - private void AutoClosingTimerCallback(object sender, EventArgs state) + private void OpenFileMonitorTimer(object state) { - bool lockTaken = Monitor.TryEnter(SyncRoot, TimeSpan.FromSeconds(0.5)); - if (!lockTaken) - return; // Timer will trigger again, no need for timers to queue up + bool startTimer = !(_openFileMonitorTimer is null); try { - if (!IsInitialized) + lock (SyncRoot) { - return; - } + startTimer = _openFileCache.Count != 0 && !(_openFileMonitorTimer is null); - if (OpenFileCacheTimeout > 0) - { - DateTime expireTimeUtc = DateTime.UtcNow.AddSeconds(-OpenFileCacheTimeout); - InternalLogger.Trace("{0}: Auto Close FileAppenders", this); - _fileAppenderCache.CloseExpiredAppenders(expireTimeUtc); - } + if (OpenFileCacheTimeout > 0) + { + PruneOpenFileCacheUsingTimeout(); + } - if (OpenFileFlushTimeout > 0 && !AutoFlush) - { - ConditionalFlushOpenFileAppenders(); + if (OpenFileFlushTimeout > 0 && !AutoFlush) + { + DateTime flushTime = Time.TimeSource.Current.Time.AddSeconds(-(OpenFileFlushTimeout + 1) * 1.5); + if (_lastWriteTime > flushTime) + { + // Only Flush when something has been written + foreach (var openFile in _openFileCache) + { + openFile.Value.FileAppender.Flush(); + } + } + } + + startTimer = startTimer && _openFileCache.Count != 0; } } - catch (Exception exception) + catch (Exception ex) { -#if DEBUG - if (exception.MustBeRethrownImmediately()) - { - throw; // Throwing exceptions here will crash the entire application (.NET 2.0 behavior) - } -#endif - - InternalLogger.Warn(exception, "{0}: Exception in AutoClosingTimerCallback", this); + InternalLogger.Warn(ex, "{0}: Exception in OpenFileMonitorTimer", this); } finally { - Monitor.Exit(SyncRoot); + if (startTimer) + _openFileMonitorTimer?.Change(OpenFileMonitorTimerInterval * 1000, Timeout.Infinite); } } - private void ConditionalFlushOpenFileAppenders() + private void PruneOpenFileCacheUsingTimeout() { - DateTime flushTime = Time.TimeSource.Current.Time.AddSeconds(-Math.Max(OpenFileFlushTimeout, 5) * 2); + DateTime closeTime = Time.TimeSource.Current.Time.AddSeconds(-OpenFileCacheTimeout); + bool oldFilesMustBeClosed = false; - bool flushAppenders = false; - foreach (var file in _initializedFiles) + foreach (var openFile in _openFileCache) { - if (file.Value > flushTime) + if (openFile.Value.FileAppender.OpenStreamTime < closeTime) { - flushAppenders = true; + oldFilesMustBeClosed = true; break; } } - if (flushAppenders) + if (oldFilesMustBeClosed) { - // Only request flush of file-handles, when something has been written - InternalLogger.Trace("{0}: Auto Flush FileAppenders", this); - _fileAppenderCache.FlushAppenders(); + foreach (var openFile in _openFileCache.ToList()) + { + if (openFile.Value.FileAppender.OpenStreamTime < closeTime) + { + CloseFile(openFile.Key, openFile.Value); + } + } } } - /// - /// Evaluates which parts of a file should be written (header, content, footer) based on various properties of - /// instance and writes them. - /// - /// File name to be written. - /// Raw sequence of to be written into the content part of the file. - /// File has just been opened. - private void WriteToFile(string fileName, ArraySegment bytes, bool initializedNewFile) + internal string BuildFullFilePath(string newFileName, int sequenceNumber, DateTime fileLastModified = default) { - BaseFileAppender appender = _fileAppenderCache.AllocateAppender(fileName); - try + if (sequenceNumber > 0 || fileLastModified != default) { - if (initializedNewFile) + var fileName = Path.GetFileName(newFileName) ?? string.Empty; + var fileExt = Path.GetExtension(fileName) ?? string.Empty; + newFileName = newFileName.Substring(0, newFileName.Length - fileName.Length); + if (!string.IsNullOrEmpty(fileExt)) + fileName = fileName.Substring(0, fileName.Length - fileExt.Length); + + object fileLastModifiedObj = fileLastModified == default ? string.Empty : (object)fileLastModified; + try { - WriteHeaderAndBom(appender); + newFileName = newFileName + fileName + string.Format(ArchiveSuffixFormat, sequenceNumber, fileLastModifiedObj) + fileExt; } - - appender.Write(bytes.Array, bytes.Offset, bytes.Count); - - if (AutoFlush) + catch (Exception ex) { - appender.Flush(); + InternalLogger.Error(ex, "{0}: Failed to apply ArchiveSuffixFormat={1} using SequenceNumber={2} for file: '{3}'", this, ArchiveSuffixFormat, sequenceNumber, newFileName); + if (ExceptionMustBeRethrown(ex)) + throw; + newFileName = newFileName + fileName + string.Format(_legacySequenceArchiveSuffixFormat, sequenceNumber) + fileExt; } } - catch (Exception ex) - { - InternalLogger.Error(ex, "{0}: Failed write to file '{1}'.", this, fileName); - _fileAppenderCache.InvalidateAppender(fileName)?.Dispose(); - throw; - } + + var filepath = CleanFullFilePath(newFileName); + return filepath; } - /// - /// Initialize a file to be used by the instance. Based on the number of initialized - /// files and the values of various instance properties clean up and/or archiving processes can be invoked. - /// - /// File name to be written. - /// Log event that the instance is currently processing. - /// The DateTime of the previous log event for this file (DateTime.MinValue if just initialized). - private DateTime InitializeFile(string fileName, LogEventInfo logEvent) + internal static string CleanFullFilePath(string filename) { - if (_initializedFiles.Count != 0 && logEvent.TimeStamp == _previousLogEventTimestamp && _previousLogFileName == fileName) - { - return logEvent.TimeStamp; - } + var lastDirSeparator = filename.LastIndexOfAny(DirectorySeparatorChars); - var now = logEvent.TimeStamp; - if (!_initializedFiles.TryGetValue(fileName, out var lastTime)) - { - PrepareForNewFile(fileName, logEvent); + char[]? fileNameChars = null; // defer char[] memory-allocation until detecting invalid char - _initializedFilesCounter++; - if (_initializedFilesCounter > OpenFileCacheSize) + for (int i = lastDirSeparator + 1; i < filename.Length; i++) + { + if (InvalidFileNameChars.Contains(filename[i])) { - // Attempt to write footer, before closing file appender - _initializedFilesCounter = 0; - CleanupInitializedFiles(); - _initializedFilesCounter = Math.Min(_initializedFiles.Count, OpenFileCacheSize / 2); + if (fileNameChars is null) + { + fileNameChars = filename.Substring(lastDirSeparator + 1).ToCharArray(); + } + fileNameChars[i - (lastDirSeparator + 1)] = '_'; } - - _initializedFiles[fileName] = now; - return DateTime.MinValue; - } - else if (lastTime != now) - { - now = EnsureValidLogEventTimeStamp(now, lastTime); - _initializedFiles[fileName] = now; } - return lastTime; - } - - private static DateTime EnsureValidLogEventTimeStamp(DateTime logEventTimeStamp, DateTime previousTimeStamp) - { - // Truncating using DateTime.Date is "expensive", so first check if it look like it is from the past - if (logEventTimeStamp < previousTimeStamp && logEventTimeStamp.Date < previousTimeStamp.Date) + //only if an invalid char was replaced do we create a new string. + if (fileNameChars != null) { - // Received LogEvent from the past when comparing to the previous timestamp - var currentTime = TimeSource.Current.Time; - if (logEventTimeStamp.Date < currentTime.AddMinutes(-1).Date) - { - // It is not because the machine-time has changed. Probably a LogEvent from the past - if (currentTime.Date < previousTimeStamp.Date) - return currentTime; // Previous timestamp is from the future. We choose machine-time - else - return previousTimeStamp; - } + //keep the / in the dirname, because dirname could be c:/ and combine of c: and file name won't work well. + var dirName = lastDirSeparator > 0 ? filename.Substring(0, lastDirSeparator + 1) : string.Empty; + filename = Path.Combine(dirName, new string(fileNameChars)); } - return logEventTimeStamp; + var filepath = FileInfoHelper.IsRelativeFilePath(filename) ? Path.Combine(AppEnvironmentWrapper.FixFilePathWithLongUNC(LogManager.LogFactory.CurrentAppEnvironment.AppDomainBaseDirectory), filename) : filename; + filepath = Path.GetFullPath(filepath); + return filepath; } + private static readonly char[] DirectorySeparatorChars = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; + private static readonly HashSet InvalidFileNameChars = new HashSet(Path.GetInvalidFileNameChars()); - private void CloseInvalidFileHandle(string fileName) + private static bool InitialValueBom(Encoding encoding) { - try - { - _fileAppenderCache.InvalidateAppender(fileName)?.Dispose(); - } - finally - { - _initializedFiles.Remove(fileName); // Skip finalize non-existing file - } + // Initial of true for UTF 16 and UTF 32 + const int utf16 = 1200; + const int utf16Be = 1201; + const int utf32 = 12000; + const int urf32Be = 12001; + var codePage = encoding?.CodePage ?? 0; + return codePage == utf16 + || codePage == utf16Be + || codePage == utf32 + || codePage == urf32Be; } /// - /// Writes the file footer and finalizes the file in instance internal structures. + /// The sequence of to be written in a file after applying any formatting and any + /// transformations required from the . /// - /// File name to close. - /// Indicates if the file is being finalized for archiving. - private void FinalizeFile(string fileName, bool isArchiving = false) + /// The layout used to render output message. + /// Sequence of to be written. + /// Usually it is used to render the header and hooter of the files. + private byte[] GetLayoutBytes(Layout layout) { - try + if (layout is null) { - InternalLogger.Trace("{0}: FinalizeFile '{1}, isArchiving: {2}'", this, fileName, isArchiving); - if ((isArchiving) || (!WriteFooterOnArchivingOnly)) - WriteFooter(fileName); + return ArrayHelper.Empty(); } - finally + + using (var targetBuilder = ReusableLayoutBuilder.Allocate()) + using (var targetBuffer = _reusableEncodingBuffer.Allocate()) { - CloseInvalidFileHandle(fileName); + layout.Render(LogEventInfo.CreateNullEvent(), targetBuilder.Result); + targetBuilder.Result.Append(LineEnding.NewLineCharacters); + using (MemoryStream ms = new MemoryStream(targetBuilder.Result.Length)) + { + targetBuilder.Result.CopyToStream(ms, Encoding, targetBuffer.Result); + return ms.ToArray(); + } } } - /// - /// Writes the footer information to a file. - /// - /// The file path to write to. - private void WriteFooter(string fileName) + internal byte[] GetFooterLayoutBytes() { - ArraySegment footerBytes = GetLayoutBytes(Footer); - if (footerBytes.Count > 0 && File.Exists(fileName)) + if (Footer != null) { - WriteToFile(fileName, footerBytes, false); + InternalLogger.Trace("{0}: Write footer", this); + return GetLayoutBytes(Footer); } + return ArrayHelper.Empty(); } - /// - /// Decision logic whether to archive logfile on startup. - /// and properties. - /// - /// File name to be written. - /// Decision whether to archive or not. - internal bool ShouldArchiveOldFileOnStartup(string fileName) + internal Stream CreateFileStreamWithRetry(IFileAppender fileAppender, int bufferSize, bool initialFileOpen) { - if (_archiveOldFileOnStartup == false) - { - // explicitly disabled and not the default - return false; - } + int currentDelay = 1; + int retryCount = KeepFileOpen ? 0 : 5; + var filePath = fileAppender.FilePath; - var aboveSizeSet = ArchiveOldFileOnStartupAboveSize > 0; - if (aboveSizeSet) + for (int i = 0; i <= retryCount; ++i) { - // Check whether size threshold exceeded - var length = _fileAppenderCache.GetFileLength(fileName); - return length.HasValue && length.Value > ArchiveOldFileOnStartupAboveSize; - } + try + { + return OpenNewFileStream(filePath, bufferSize, initialFileOpen); + } + catch (DirectoryNotFoundException) + { + if (!CreateDirs) + throw; - // No size threshold specified, use archiveOldFileOnStartup flag - return _archiveOldFileOnStartup == true; - } + InternalLogger.Debug("{0}: DirectoryNotFoundException - Attempting to create directory for FileName: {1}", this, filePath); - /// - /// Invokes the archiving and clean up of older archive file based on the values of - /// and - /// properties respectively. - /// - /// File name to be written. - /// Log event that the instance is currently processing. - private void PrepareForNewFile(string fileName, LogEventInfo logEvent) - { - InternalLogger.Debug("{0}: Preparing for new file: '{1}'", this, fileName); - RefreshArchiveFilePatternToWatch(fileName, logEvent); + var directoryName = Path.GetDirectoryName(filePath); - try - { - if (ShouldArchiveOldFileOnStartup(fileName)) - { - DoAutoArchive(fileName, logEvent, DateTime.MinValue, true); + try + { + Directory.CreateDirectory(directoryName); + } + catch (Exception ex) + { + // If creating a directory failed, don't retry for this message + throw new NLogRuntimeException($"Could not create directory {directoryName}", ex); + } } - } - catch (Exception exception) - { - InternalLogger.Warn(exception, "{0}: Unable to archive old log file '{1}'.", this, fileName); - if (ExceptionMustBeRethrown(exception)) + catch (IOException ex) { - throw; + if (i >= retryCount) + { + throw; // rethrow + } + + var actualDelay = currentDelay > 4 ? new Random().Next(4, currentDelay) : currentDelay; + InternalLogger.Warn("{0}: Attempt #{1} to open {2} failed - {3} {4}. Sleeping for {5}ms", this, i, filePath, ex.GetType(), ex.Message, actualDelay); + currentDelay *= 4; + System.Threading.Thread.Sleep(actualDelay); } } - if (DeleteOldFileOnStartup) - { - DeleteOldArchiveFile(fileName); - } + throw new InvalidOperationException("Should not be reached."); + } + + private Stream OpenNewFileStream(string filePath, int bufferSize, bool initialFileOpen) + { + var fileStream = CreateFileStream(filePath, bufferSize); try { - string archiveFilePattern = GetArchiveFileNamePattern(fileName, logEvent); - if (!string.IsNullOrEmpty(archiveFilePattern)) + bool? fileWasEmpty = null; + + if (WriteBom) { - var fileArchiveStyle = GetFileArchiveHelper(archiveFilePattern); - if (fileArchiveStyle.AttemptCleanupOnInitializeFile(archiveFilePattern, MaxArchiveFiles, MaxArchiveDays)) + fileWasEmpty = ReplaceFileContentsOnEachWrite || fileStream.Length == 0; + if (fileWasEmpty == true) { - var existingArchiveFiles = fileArchiveStyle.GetExistingArchiveFiles(archiveFilePattern); - if (existingArchiveFiles.Count > 0) + InternalLogger.Trace("{0}: Write byte order mark from encoding={1}", this, Encoding); + var preamble = Encoding.GetPreamble(); + if (preamble.Length > 0) { - CleanupOldArchiveFiles(new FileInfo(fileName), archiveFilePattern, existingArchiveFiles); + fileStream.Write(preamble, 0, preamble.Length); } } } - } - catch (Exception exception) - { - InternalLogger.Warn(exception, "FileTarget(Name={0}): Failed to cleanup old archive files when starting on new file: '{1}'", Name, fileName); - if (ExceptionMustBeRethrown(exception)) + if (Header != null) { - throw; + bool writeHeader = (initialFileOpen && WriteHeaderWhenInitialFileNotEmpty) || ReplaceFileContentsOnEachWrite || (fileWasEmpty ?? fileStream.Length == 0); + if (writeHeader) + { + InternalLogger.Trace("{0}: Write header", this); + var headerBytes = GetLayoutBytes(Header); + fileStream.Write(headerBytes, 0, headerBytes.Length); + } } + + return fileStream; + } + catch + { + fileStream?.Dispose(); + throw; } } /// - /// Creates the file specified in and writes the file content in each entirety i.e. - /// Header, Content and Footer. + /// Creates stream for appending to the specified /// - /// The name of the file to be written. - /// Sequence of to be written in the content section of the file. - /// First attempt to write? - /// This method is used when the content of the log file is re-written on every write. - private void ReplaceFileContent(string fileName, ArraySegment bytes, bool firstAttempt) + /// Path of the file to be written + /// Wanted internal buffer size for the stream + /// Stream for appending to the file + protected virtual Stream CreateFileStream(string filePath, int bufferSize) { - try + var fileShare = FileShare.Read; + if (EnableFileDelete) { - using (FileStream fs = File.Create(fileName)) - { - ArraySegment headerBytes = GetLayoutBytes(Header); - if (headerBytes.Count > 0) - { - fs.Write(headerBytes.Array, headerBytes.Offset, headerBytes.Count); - } - - fs.Write(bytes.Array, bytes.Offset, bytes.Count); - - ArraySegment footerBytes = GetLayoutBytes(Footer); - if (footerBytes.Count > 0) - { - fs.Write(footerBytes.Array, footerBytes.Offset, footerBytes.Count); - } - } + fileShare |= FileShare.Delete; } - catch (DirectoryNotFoundException) + + var fileMode = FileMode.Append; + if (ReplaceFileContentsOnEachWrite) { - if (!CreateDirs || !firstAttempt) - { - throw; - } - Directory.CreateDirectory(Path.GetDirectoryName(fileName)); - //retry. - ReplaceFileContent(fileName, bytes, false); + fileMode = FileMode.Create; // Create or truncate } - } - private static bool InitialValueBom(Encoding encoding) - { - // Initial of true for UTF 16 and UTF 32 - const int utf16 = 1200; - const int utf16Be = 1201; - const int utf32 = 12000; - const int urf32Be = 12001; - var codePage = encoding?.CodePage ?? 0; - return codePage == utf16 - || codePage == utf16Be - || codePage == utf32 - || codePage == urf32Be; + return new FileStream(filePath, fileMode, FileAccess.Write, fileShare, bufferSize); } - /// - /// Writes the header information and byte order mark to a file. - /// - /// File appender associated with the file. - private void WriteHeaderAndBom(BaseFileAppender appender) + private IFileAppender CreateFileAppender(string filePath) { - //performance: cheap check before checking file info - if (Header is null && !WriteBom) return; - - var length = appender.GetFileLength(); - // File is empty or file info cannot be obtained - var isNewOrEmptyFile = length is null || length == 0; + if (DiscardAll) + { + return new DiscardAllFileAppender(filePath); + } - if (isNewOrEmptyFile && WriteBom) + if (ReplaceFileContentsOnEachWrite) { - InternalLogger.Trace("{0}: Write byte order mark from encoding={1}", this, Encoding); - var preamble = Encoding.GetPreamble(); - if (preamble.Length > 0) - appender.Write(preamble, 0, preamble.Length); + return new MinimalFileLockingAppender(this, filePath); } - if (Header != null && (isNewOrEmptyFile || WriteHeaderWhenInitialFileNotEmpty)) + if (KeepFileOpen) { - InternalLogger.Trace("{0}: Write header", this); - ArraySegment headerBytes = GetLayoutBytes(Header); - if (headerBytes.Count > 0) - { - appender.Write(headerBytes.Array, headerBytes.Offset, headerBytes.Count); - } + return new ExclusiveFileLockingAppender(this, filePath); } + + return new MinimalFileLockingAppender(this, filePath); } - /// - /// The sequence of to be written in a file after applying any formatting and any - /// transformations required from the . - /// - /// The layout used to render output message. - /// Sequence of to be written. - /// Usually it is used to render the header and hooter of the files. - private ArraySegment GetLayoutBytes(Layout layout) + private IFileArchiveHandler CreateFileArchiveHandler() { - if (layout is null) + if (MaxArchiveFiles < 0 && MaxArchiveDays == 0 && ArchiveAboveSize == 0 && ArchiveEvery == FileArchivePeriod.None) { - return default(ArraySegment); + if (!DeleteOldFileOnStartup && !ArchiveOldFileOnStartup) + return DisabledFileArchiveHandler.Default; // Archive-logic disabled, always append to active file without rolling + else if (!ArchiveOldFileOnStartup) + return new ZeroFileArchiveHandler(this); // No archive but cleanup old files at startup } - using (var targetBuilder = ReusableLayoutBuilder.Allocate()) - using (var targetBuffer = _reusableEncodingBuffer.Allocate()) - { - var nullEvent = LogEventInfo.CreateNullEvent(); - layout.Render(nullEvent, targetBuilder.Result); - targetBuilder.Result.Append(NewLineChars); - using (MemoryStream ms = new MemoryStream(targetBuilder.Result.Length)) - { - TransformBuilderToStream(nullEvent, targetBuilder.Result, targetBuffer.Result, ms); - return new ArraySegment(ms.ToArray()); - } - } + if (MaxArchiveFiles == 0) + return new ZeroFileArchiveHandler(this); // MaxArchiveFiles = 0 means truncate active file on archive-event + + if (ArchiveFileName is null) + return new RollingArchiveFileHandler(this); // Updated dynamic sequence handling without file-move-logic + + return new LegacyArchiveFileNameHandler(this); // Legacy / unstable because file-move can fail because of file-locks from other applications } } } diff --git a/src/NLog/Targets/IColoredConsolePrinter.cs b/src/NLog/Targets/IColoredConsolePrinter.cs index 56252124ad..d8301eb487 100644 --- a/src/NLog/Targets/IColoredConsolePrinter.cs +++ b/src/NLog/Targets/IColoredConsolePrinter.cs @@ -31,8 +31,6 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD1_3 - namespace NLog.Targets { using System; @@ -117,7 +115,4 @@ internal interface IColoredConsolePrinter /// IList DefaultConsoleRowHighlightingRules { get; } } - } - -#endif diff --git a/src/NLog/Targets/JsonSerializeOptions.cs b/src/NLog/Targets/JsonSerializeOptions.cs index 882f13b7e7..bff64fe168 100644 --- a/src/NLog/Targets/JsonSerializeOptions.cs +++ b/src/NLog/Targets/JsonSerializeOptions.cs @@ -44,49 +44,65 @@ public class JsonSerializeOptions /// /// Add quotes around object keys? /// - [Obsolete("Marked obsolete on NLog 5.0.")] + /// Default: + [Obsolete("Marked obsolete with NLog 5.0.")] [EditorBrowsable(EditorBrowsableState.Never)] public bool QuoteKeys { get; set; } = true; /// /// Format provider for value /// - [Obsolete("Marked obsolete on NLog 5.0. Should always be InvariantCulture.")] + /// Default: + [Obsolete("Marked obsolete with NLog 5.0. Should always be InvariantCulture.")] [EditorBrowsable(EditorBrowsableState.Never)] - public IFormatProvider FormatProvider { get; set; } + public IFormatProvider? FormatProvider { get; set; } /// /// Format string for value /// - [Obsolete("Marked obsolete on NLog 5.0. Should always be InvariantCulture.")] + /// Default: + [Obsolete("Marked obsolete with NLog 5.0. Should always be InvariantCulture.")] [EditorBrowsable(EditorBrowsableState.Never)] - public string Format { get; set; } + public string? Format { get; set; } /// - /// Should non-ascii characters be encoded + /// Should non-ascii characters be encoded. /// + /// Default: public bool EscapeUnicode { get; set; } /// - /// Should forward slashes be escaped? If true, / will be converted to \/ + /// Should forward slashes be escaped? If , / will be converted to \/ /// + /// Default: + [Obsolete("Marked obsolete with NLog 5.5. Should never escape forward slash")] + [EditorBrowsable(EditorBrowsableState.Never)] public bool EscapeForwardSlash { get; set; } /// - /// Serialize enum as string value + /// Serialize enum as integer value. /// + /// Default: public bool EnumAsInteger { get; set; } + /// + /// Gets or sets the option to suppress the extra spaces in the output json. + /// + /// Default: + public bool SuppressSpaces { get; set; } = true; + /// /// Should dictionary keys be sanitized. All characters must either be letters, numbers or underscore character (_). /// /// Any other characters will be converted to underscore character (_) /// + /// Default: public bool SanitizeDictionaryKeys { get; set; } /// /// How far down the rabbit hole should the Json Serializer go with object-reflection before stopping /// + /// Default: public int MaxRecursionLimit { get; set; } = 10; } } diff --git a/src/NLog/Targets/LineEndingMode.cs b/src/NLog/Targets/LineEndingMode.cs index 5e1fa84c7e..7b6cef07eb 100644 --- a/src/NLog/Targets/LineEndingMode.cs +++ b/src/NLog/Targets/LineEndingMode.cs @@ -34,7 +34,6 @@ using System; using System.ComponentModel; using System.Globalization; -using JetBrains.Annotations; using NLog.Internal; namespace NLog.Targets @@ -46,9 +45,9 @@ namespace NLog.Targets public sealed class LineEndingMode : IEquatable { /// - /// Insert platform-dependent end-of-line sequence after each line. + /// Insert platform-dependent sequence after each line. /// - public static readonly LineEndingMode Default = new LineEndingMode("Default", EnvironmentHelper.NewLine); + public static readonly LineEndingMode Default = new LineEndingMode("Default", Environment.NewLine); /// /// Insert CR LF sequence (ASCII 13, ASCII 10) after each line. @@ -88,7 +87,11 @@ public sealed class LineEndingMode : IEquatable /// public string NewLineCharacters => _newLineCharacters; - private LineEndingMode() { } + private LineEndingMode() + { + _name = string.Empty; + _newLineCharacters = string.Empty; + } /// /// Initializes a new instance of . @@ -111,7 +114,7 @@ private LineEndingMode(string name, string newLineCharacters) /// /// The value, that corresponds to the . /// There is no line ending mode with the specified name. - public static LineEndingMode FromString([NotNull] string name) + public static LineEndingMode FromString(string name) { Guard.ThrowIfNull(name); @@ -188,7 +191,7 @@ public override bool Equals(object obj) } /// Indicates whether the current object is equal to another object of the same type. - /// true if the current object is equal to the parameter; otherwise, false. + /// if the current object is equal to the parameter; otherwise, . /// An object to compare with this object. public bool Equals(LineEndingMode other) { diff --git a/src/NLog/Targets/MemoryTarget.cs b/src/NLog/Targets/MemoryTarget.cs index b0ecb02ec6..549f1bf9e4 100644 --- a/src/NLog/Targets/MemoryTarget.cs +++ b/src/NLog/Targets/MemoryTarget.cs @@ -89,8 +89,9 @@ public MemoryTarget(string name) : this() public IList Logs => _logs; /// - /// Gets or sets the max number of items to have in memory + /// Gets or sets the max number of items to have in memory. Zero or Negative means no limit. /// + /// Default: 0 /// public int MaxLogsCount { get; set; } diff --git a/src/NLog/Targets/MethodCallParameter.cs b/src/NLog/Targets/MethodCallParameter.cs index 706c525612..db06bee798 100644 --- a/src/NLog/Targets/MethodCallParameter.cs +++ b/src/NLog/Targets/MethodCallParameter.cs @@ -92,20 +92,22 @@ public MethodCallParameter(string name, Layout layout, Type type) /// /// Gets or sets the name of the parameter. /// + /// [Required] Default: /// - public string Name { get; set; } + public string Name { get; set; } = string.Empty; /// - /// Gets or sets the layout that should be use to calculate the value for the parameter. + /// Gets or sets the layout used for rendering the method-parameter value. /// + /// [Required] Default: /// - [RequiredParameter] public Layout Layout { get => _layoutInfo.Layout; set => _layoutInfo.Layout = value; } /// /// Obsolete and replaced by with NLog v4.6. /// Gets or sets the type of the parameter. Obsolete alias for /// + /// Default: typeof(string) /// [Obsolete("Use property ParameterType instead. Marked obsolete on NLog 4.6")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -114,20 +116,22 @@ public MethodCallParameter(string name, Layout layout, Type type) /// /// Gets or sets the type of the parameter. /// + /// Default: typeof(string) /// public Type ParameterType { get => _layoutInfo.ValueType ?? typeof(string); set => _layoutInfo.ValueType = value; } /// /// Gets or sets the fallback value when result value is not available /// + /// Default: /// - public Layout DefaultValue { get => _layoutInfo.DefaultValue; set => _layoutInfo.DefaultValue = value; } + public Layout? DefaultValue { get => _layoutInfo.DefaultValue; set => _layoutInfo.DefaultValue = value; } /// /// Render Result Value /// /// Log event for rendering /// Result value when available, else fallback to defaultValue - public object RenderValue(LogEventInfo logEvent) => _layoutInfo.RenderValue(logEvent); + public object? RenderValue(LogEventInfo logEvent) => _layoutInfo.RenderValue(logEvent); } } diff --git a/src/NLog/Targets/MethodCallTarget.cs b/src/NLog/Targets/MethodCallTarget.cs index 72d3ab6cf7..1a79b75745 100644 --- a/src/NLog/Targets/MethodCallTarget.cs +++ b/src/NLog/Targets/MethodCallTarget.cs @@ -34,6 +34,7 @@ namespace NLog.Targets { using System; + using System.Diagnostics; using System.Reflection; using NLog.Common; using NLog.Config; @@ -64,23 +65,24 @@ public sealed class MethodCallTarget : MethodCallTargetBase /// Gets or sets the class name. /// /// - public string ClassName { get; set; } + /// Default: + public string ClassName { get; set; } = string.Empty; /// /// Gets or sets the method name. The method must be public and static. /// - /// Use the AssemblyQualifiedName , https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname(v=vs.110).aspx - /// e.g. + /// Use the AssemblyQualifiedName - /// + /// Default: /// - public string MethodName { get; set; } + public string MethodName { get; set; } = string.Empty; - Action _logEventAction; + Action? _logEventAction; /// /// Initializes a new instance of the class. /// - public MethodCallTarget() : base() + public MethodCallTarget() { } @@ -88,8 +90,9 @@ public MethodCallTarget() : base() /// Initializes a new instance of the class. /// /// Name of the target. - public MethodCallTarget(string name) : this(name, null) + public MethodCallTarget(string name) { + Name = name; } /// @@ -97,7 +100,7 @@ public MethodCallTarget(string name) : this(name, null) /// /// Name of the target. /// Method to call on logevent. - public MethodCallTarget(string name, Action logEventAction) : this() + public MethodCallTarget(string name, Action logEventAction) { Name = name; _logEventAction = logEventAction; @@ -119,7 +122,7 @@ protected override void InitializeTarget() } [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming - Allow method lookup from config", "IL2075")] - private static Action BuildLogEventAction(string className, string methodName) + private static Action BuildLogEventAction(string className, string methodName) { var targetType = PropertyTypeConverter.ConvertToType(className.Trim(), false); if (targetType is null) @@ -144,18 +147,18 @@ private static Action BuildLogEventAction(string classNa } } - private static Action BuildLogEventAction(MethodInfo methodInfo) + private static Action BuildLogEventAction(MethodInfo methodInfo) { var neededParameters = methodInfo.GetParameters().Length; - ReflectionHelpers.LateBoundMethod lateBoundMethod = null; + ReflectionHelpers.LateBoundMethod? lateBoundMethod = null; return (logEvent, parameters) => { var missingParameters = neededParameters - parameters.Length; if (missingParameters > 0) { //fill missing parameters with Type.Missing - var newParams = new object[neededParameters]; + var newParams = new object?[neededParameters]; for (int i = 0; i < parameters.Length; ++i) newParams[i] = parameters[i]; for (int i = parameters.Length; i < neededParameters; ++i) @@ -173,17 +176,17 @@ private static Action BuildLogEventAction(MethodInfo met if (lateBoundMethod is null) lateBoundMethod = CreateFastInvoke(methodInfo, parameters) ?? CreateNormalInvoke(methodInfo, parameters); else - lateBoundMethod.Invoke(null, parameters); + CallMethod(lateBoundMethod, parameters); } }; } - private static ReflectionHelpers.LateBoundMethod CreateFastInvoke(MethodInfo methodInfo, object[] parameters) + private static ReflectionHelpers.LateBoundMethod? CreateFastInvoke(MethodInfo methodInfo, object?[] parameters) { try { var lateBoundMethod = ReflectionHelpers.CreateLateBoundMethod(methodInfo); - lateBoundMethod.Invoke(null, parameters); + CallMethod(lateBoundMethod, parameters); return lateBoundMethod; } catch (Exception ex) @@ -193,13 +196,20 @@ private static ReflectionHelpers.LateBoundMethod CreateFastInvoke(MethodInfo met } } - private static ReflectionHelpers.LateBoundMethod CreateNormalInvoke(MethodInfo methodInfo, object[] parameters) + private static void CallMethod(ReflectionHelpers.LateBoundMethod lateBoundMethod, object?[] parameters) + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + lateBoundMethod.Invoke(null, parameters); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + private static ReflectionHelpers.LateBoundMethod CreateNormalInvoke(MethodInfo methodInfo, object?[] parameters) { ReflectionHelpers.LateBoundMethod reflectionMethod = (target, args) => methodInfo.Invoke(null, args); try { - reflectionMethod.Invoke(null, parameters); + CallMethod(reflectionMethod, parameters); return reflectionMethod; } catch (Exception ex) @@ -214,7 +224,7 @@ private static ReflectionHelpers.LateBoundMethod CreateNormalInvoke(MethodInfo m /// /// Method parameters. /// The logging event. - protected override void DoInvoke(object[] parameters, AsyncLogEventInfo logEvent) + protected override void DoInvoke(object?[] parameters, AsyncLogEventInfo logEvent) { try { @@ -236,12 +246,13 @@ protected override void DoInvoke(object[] parameters, AsyncLogEventInfo logEvent /// Calls the specified Method. /// /// Method parameters. - protected override void DoInvoke(object[] parameters) + protected override void DoInvoke(object?[] parameters) { - ExecuteLogMethod(parameters, null); + // method is not used, instead asynchronous overload will be used + throw new NotSupportedException(); } - private void ExecuteLogMethod(object[] parameters, LogEventInfo logEvent) + private void ExecuteLogMethod(object?[] parameters, LogEventInfo logEvent) { if (_logEventAction is null) { diff --git a/src/NLog/Targets/MethodCallTargetBase.cs b/src/NLog/Targets/MethodCallTargetBase.cs index e87bd04c5d..ff4970820a 100644 --- a/src/NLog/Targets/MethodCallTargetBase.cs +++ b/src/NLog/Targets/MethodCallTargetBase.cs @@ -58,7 +58,7 @@ protected MethodCallTargetBase() /// /// [ArrayParameter(typeof(MethodCallParameter), "parameter")] - public IList Parameters { get; private set; } + public IList Parameters { get; } /// /// Prepares an array of parameters to be passed based on the logging event and calls DoInvoke(). @@ -66,7 +66,7 @@ protected MethodCallTargetBase() /// The logging event. protected override void Write(AsyncLogEventInfo logEvent) { - object[] parameters = Parameters.Count > 0 ? new object[Parameters.Count] : ArrayHelper.Empty(); + object?[] parameters = Parameters.Count > 0 ? new object?[Parameters.Count] : ArrayHelper.Empty(); for (int i = 0; i < parameters.Length; ++i) { try @@ -92,22 +92,12 @@ protected override void Write(AsyncLogEventInfo logEvent) /// /// Method call parameters. /// The logging event. - protected virtual void DoInvoke(object[] parameters, AsyncLogEventInfo logEvent) - { - DoInvoke(parameters, logEvent.Continuation); - } - - /// - /// Calls the target DoInvoke method, and handles AsyncContinuation callback - /// - /// Method call parameters. - /// The continuation. - protected virtual void DoInvoke(object[] parameters, AsyncContinuation continuation) + protected virtual void DoInvoke(object?[] parameters, AsyncLogEventInfo logEvent) { try { DoInvoke(parameters); - continuation(null); + logEvent.Continuation(null); } catch (Exception ex) { @@ -116,7 +106,7 @@ protected virtual void DoInvoke(object[] parameters, AsyncContinuation continuat throw; } - continuation(ex); + logEvent.Continuation(ex); } } @@ -124,6 +114,6 @@ protected virtual void DoInvoke(object[] parameters, AsyncContinuation continuat /// Calls the target method. Must be implemented in concrete classes. /// /// Method call parameters. - protected abstract void DoInvoke(object[] parameters); + protected abstract void DoInvoke(object?[] parameters); } } diff --git a/src/NLog/Targets/NullTarget.cs b/src/NLog/Targets/NullTarget.cs index 573cf08962..e5bde8b5f4 100644 --- a/src/NLog/Targets/NullTarget.cs +++ b/src/NLog/Targets/NullTarget.cs @@ -57,13 +57,14 @@ public sealed class NullTarget : TargetWithLayout /// /// Gets or sets a value indicating whether to perform layout calculation. /// + /// Default: /// public bool FormatMessage { get; set; } /// /// Initializes a new instance of the class. /// - public NullTarget() : base() + public NullTarget() { } diff --git a/src/NLog/Targets/Target.cs b/src/NLog/Targets/Target.cs index be46cc4650..40ec07afd8 100644 --- a/src/NLog/Targets/Target.cs +++ b/src/NLog/Targets/Target.cs @@ -38,7 +38,6 @@ namespace NLog.Targets using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; - using JetBrains.Annotations; using NLog.Common; using NLog.Config; using NLog.Internal; @@ -50,7 +49,7 @@ namespace NLog.Targets [NLogConfigurationItem] public abstract class Target : ISupportsInitialize, IInternalLoggerContext, IDisposable { - internal string _tostring; + internal string? _tostring; private Layout[] _allLayouts = ArrayHelper.Empty(); @@ -58,18 +57,19 @@ public abstract class Target : ISupportsInitialize, IInternalLoggerContext, IDis private bool _allLayoutsAreThreadAgnostic; private bool _anyLayoutsAreThreadAgnosticImmutable; private bool _scannedForLayouts; - private Exception _initializeException; + private Exception? _initializeException; /// /// The Max StackTraceUsage of all the in this Target /// internal StackTraceUsage StackTraceUsage { get; private set; } - internal Exception InitializeException => _initializeException; + internal Exception? InitializeException => _initializeException; /// /// Gets or sets the name of the target. /// + /// [Required] Default: /// public string Name { @@ -80,7 +80,7 @@ public string Name _tostring = null; } } - private string _name; + private string _name = string.Empty; /// /// Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers @@ -115,9 +115,9 @@ public string Name /// /// Gets the logging configuration this target is part of. /// - protected LoggingConfiguration LoggingConfiguration { get; private set; } + protected LoggingConfiguration? LoggingConfiguration { get; private set; } - LogFactory IInternalLoggerContext.LogFactory => LoggingConfiguration?.LogFactory; + LogFactory? IInternalLoggerContext.LogFactory => LoggingConfiguration?.LogFactory; /// /// Gets a value indicating whether the target has been initialized. @@ -139,13 +139,13 @@ protected bool IsInitialized private volatile bool _isInitialized; internal readonly ReusableBuilderCreator ReusableLayoutBuilder = new ReusableBuilderCreator(); - private StringBuilderPool _precalculateStringBuilderPool; + private StringBuilderPool? _precalculateStringBuilderPool; /// /// Initializes this instance. /// /// The configuration. - void ISupportsInitialize.Initialize(LoggingConfiguration configuration) + void ISupportsInitialize.Initialize(LoggingConfiguration? configuration) { lock (SyncRoot) { @@ -185,27 +185,38 @@ public void Flush(AsyncContinuation asyncContinuation) asyncContinuation = AsyncHelpers.PreventMultipleCalls(asyncContinuation); - lock (SyncRoot) + if (System.Threading.Monitor.TryEnter(SyncRoot, 15000)) { - if (!IsInitialized) - { - // In case target was Closed - asyncContinuation(null); - return; - } - try { - FlushAsync(asyncContinuation); + if (!IsInitialized) + { + // In case target was Closed + asyncContinuation(null); + return; + } + + try + { + FlushAsync(asyncContinuation); + } + catch (Exception exception) + { + if (ExceptionMustBeRethrown(exception)) + throw; + + asyncContinuation(exception); + } } - catch (Exception exception) + finally { - if (ExceptionMustBeRethrown(exception)) - throw; - - asyncContinuation(exception); + System.Threading.Monitor.Exit(SyncRoot); } } + else + { + asyncContinuation(new NLogRuntimeException($"Target {this} failed to flush after lock timeout.")); + } } /// @@ -274,7 +285,7 @@ public override string ToString() return _tostring ?? (_tostring = GenerateTargetToString(false)); } - internal string GenerateTargetToString(bool targetWrapper, string targetName = null) + internal string GenerateTargetToString(bool targetWrapper, string? targetName = null) { var targetAttribute = GetType().GetFirstCustomAttribute(); string targetType = (targetAttribute?.Name ?? GetType().Name).Trim(); @@ -403,6 +414,15 @@ public void WriteAsyncLogEvents(IList logEvents) /// protected virtual void WriteFailedNotInitialized(AsyncLogEventInfo logEvent, Exception initializeException) { + if (!_scannedForLayouts) + { + _scannedForLayouts = true; + InternalLogger.Error(_initializeException, "{0}: No output because target failed initialize.", this); + } + else + { + InternalLogger.Debug("{0}: No output because target failed initialize. {1} {2}", this, _initializeException?.GetType(), _initializeException?.Message); + } var initializeFailedException = new NLogRuntimeException($"Target {this} failed to initialize.", initializeException); logEvent.Continuation(initializeFailedException); } @@ -411,7 +431,7 @@ protected virtual void WriteFailedNotInitialized(AsyncLogEventInfo logEvent, Exc /// Initializes this instance. /// /// The configuration. - internal void Initialize(LoggingConfiguration configuration) + internal void Initialize(LoggingConfiguration? configuration) { lock (SyncRoot) { @@ -421,7 +441,7 @@ internal void Initialize(LoggingConfiguration configuration) { try { - PropertyHelper.CheckRequiredParameters(ConfigurationItemFactory.Default, this); + _scannedForLayouts = false; InitializeTarget(); _initializeException = null; @@ -436,6 +456,7 @@ internal void Initialize(LoggingConfiguration configuration) { // Target is now in disabled state, and cannot be used for writing LogEvents _initializeException = exception; + _scannedForLayouts = false; if (ExceptionMustBeRethrown(exception)) throw; } @@ -443,6 +464,7 @@ internal void Initialize(LoggingConfiguration configuration) { // Target is now in disabled state, and cannot be used for writing LogEvents _initializeException = exception; + _scannedForLayouts = false; if (ExceptionMustBeRethrown(exception)) throw; @@ -495,7 +517,7 @@ internal void Close() /// /// Releases unmanaged and - optionally - managed resources. /// - /// True to release both managed and unmanaged resources; false to release only unmanaged resources. + /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (disposing && _isInitialized) @@ -676,14 +698,16 @@ protected void MergeEventProperties(LogEventInfo logEvent) /// The layout. /// The logevent info. /// String representing log event. - protected string RenderLogEvent([CanBeNull] Layout layout, [CanBeNull] LogEventInfo logEvent) + protected string RenderLogEvent(Layout? layout, LogEventInfo logEvent) { if (layout is null || logEvent is null) - return null; // Signal that input was wrong + return string.Empty; // Signal that input was wrong - if (layout is SimpleLayout simpleLayout && (simpleLayout.IsFixedText || simpleLayout.IsSimpleStringText)) + if (layout is IStringValueRenderer stringLayout) { - return simpleLayout.Render(logEvent, false); + var result = stringLayout.GetFormattedString(logEvent); + if (result != null) + return result; } if (TryGetCachedValue(layout, logEvent, out var value)) @@ -705,7 +729,7 @@ protected string RenderLogEvent([CanBeNull] Layout layout, [CanBeNull] LogEventI /// The logevent info. /// Fallback value when no value available /// Result value when available, else fallback to defaultValue - protected T RenderLogEvent([CanBeNull] Layout layout, [CanBeNull] LogEventInfo logEvent, T defaultValue = default(T)) + protected T? RenderLogEvent(Layout? layout, LogEventInfo logEvent, T? defaultValue = default(T)) { if (layout is null) return defaultValue; @@ -745,20 +769,20 @@ internal bool ExceptionMustBeRethrown(Exception exception, #if !NET35 [System.Runtime.CompilerServices.CallerMemberName] #endif - string callerMemberName = null) + string? callerMemberName = null) { return exception.MustBeRethrown(this, callerMemberName); } - private static bool TryGetCachedValue(Layout layout, LogEventInfo logEvent, out object value) + private static bool TryGetCachedValue(Layout layout, LogEventInfo logEvent, out object? value) { - if ((!layout.ThreadAgnostic || layout.ThreadAgnosticImmutable) && logEvent.TryGetCachedLayoutValue(layout, out value)) + if (layout.ThreadAgnostic && !layout.ThreadAgnosticImmutable) { - return true; + value = null; + return false; } - value = null; - return false; + return logEvent.TryGetCachedLayoutValue(layout, out value); } /// diff --git a/src/NLog/Targets/TargetAttribute.cs b/src/NLog/Targets/TargetAttribute.cs index 6dda7c6ae8..065becfa95 100644 --- a/src/NLog/Targets/TargetAttribute.cs +++ b/src/NLog/Targets/TargetAttribute.cs @@ -34,7 +34,7 @@ namespace NLog.Targets { using System; - using Config; + using NLog.Config; /// /// Marks class as logging target and attaches a type-alias name for use in NLog configuration. diff --git a/src/NLog/Targets/TargetPropertyWithContext.cs b/src/NLog/Targets/TargetPropertyWithContext.cs index 1f5da8558a..76efd31d03 100644 --- a/src/NLog/Targets/TargetPropertyWithContext.cs +++ b/src/NLog/Targets/TargetPropertyWithContext.cs @@ -48,7 +48,7 @@ public class TargetPropertyWithContext /// /// Initializes a new instance of the class. /// - public TargetPropertyWithContext() : this(null, null) { } + public TargetPropertyWithContext() : this(string.Empty, Layout.Empty) { } /// /// Initializes a new instance of the class. @@ -59,38 +59,40 @@ public TargetPropertyWithContext(string name, Layout layout) { Name = name; Layout = layout; - IncludeEmptyValue = false; } /// - /// Gets or sets the name of the attribute. + /// Gets or sets the name of the property. /// + /// [Required] Default: /// - [RequiredParameter] public string Name { get; set; } /// - /// Gets or sets the layout that will be rendered as the attribute's value. + /// Gets or sets the layout used for rendering the property value. /// + /// [Required] Default: /// - [RequiredParameter] public Layout Layout { get => _layoutInfo.Layout; set => _layoutInfo.Layout = value; } /// /// Gets or sets the type of the property. /// + /// Default: /// public Type PropertyType { get => _layoutInfo.ValueType ?? typeof(string); set => _layoutInfo.ValueType = value; } /// /// Gets or sets the fallback value when result value is not available /// + /// Default: /// - public Layout DefaultValue { get => _layoutInfo.DefaultValue; set => _layoutInfo.DefaultValue = value; } + public Layout? DefaultValue { get => _layoutInfo.DefaultValue; set => _layoutInfo.DefaultValue = value; } /// - /// Gets or sets when an empty value should cause the property to be included + /// Gets or sets whether empty property value should be included in the output. /// + /// Default: . Empty value is either null or empty string /// public bool IncludeEmptyValue { @@ -108,6 +110,6 @@ public bool IncludeEmptyValue /// /// Log event for rendering /// Result value when available, else fallback to defaultValue - public object RenderValue(LogEventInfo logEvent) => _layoutInfo.RenderValue(logEvent); + public object? RenderValue(LogEventInfo logEvent) => _layoutInfo.RenderValue(logEvent); } } diff --git a/src/NLog/Targets/TargetWithContext.cs b/src/NLog/Targets/TargetWithContext.cs index f318c3b939..16335ebc1f 100644 --- a/src/NLog/Targets/TargetWithContext.cs +++ b/src/NLog/Targets/TargetWithContext.cs @@ -56,7 +56,6 @@ namespace NLog.Targets /// this.Host = "localhost"; /// } /// - /// [RequiredParameter] /// public Layout Host { get; set; } /// /// protected override void Write(LogEventInfo logEvent) @@ -93,18 +92,21 @@ public sealed override Layout Layout /// /// Gets or sets the option to include all properties from the log events /// + /// Default: /// public bool IncludeEventProperties { get => _contextLayout.IncludeEventProperties; set => _contextLayout.IncludeEventProperties = value; } /// /// Gets or sets whether to include the contents of the properties-dictionary. /// + /// Default: /// public bool IncludeScopeProperties { get => _contextLayout.IncludeScopeProperties; set => _contextLayout.IncludeScopeProperties = value; } /// /// Gets or sets whether to include the contents of the nested-state-stack. /// + /// Default: /// public bool IncludeScopeNested { get => _contextLayout.IncludeScopeNested; set => _contextLayout.IncludeScopeNested = value; } @@ -112,6 +114,7 @@ public sealed override Layout Layout /// Obsolete and replaced by with NLog v5. /// Gets or sets whether to include the contents of the -dictionary. /// + /// Default: /// [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -121,6 +124,7 @@ public sealed override Layout Layout /// Obsolete and replaced by with NLog v5. /// Gets or sets whether to include the contents of the -stack. /// + /// Default: /// [Obsolete("Replaced by IncludeScopeNested. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -130,6 +134,7 @@ public sealed override Layout Layout /// Obsolete and replaced by with NLog v5. /// Gets or sets whether to include the contents of the -properties. /// + /// Default: /// [Obsolete("Replaced by IncludeScopeProperties. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -139,6 +144,7 @@ public sealed override Layout Layout /// Obsolete and replaced by with NLog v5. /// Gets or sets whether to include the contents of the -stack. /// + /// Default: /// [Obsolete("Replaced by IncludeScopeNested. Marked obsolete on NLog 5.0")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -147,18 +153,21 @@ public sealed override Layout Layout /// /// Gets or sets a value indicating whether to include contents of the dictionary /// + /// Default: /// public bool IncludeGdc { get; set; } /// /// Gets or sets a value indicating whether to include call site (class and method name) in the /// + /// Default: /// public bool IncludeCallSite { get => _contextLayout.IncludeCallSite; set => _contextLayout.IncludeCallSite = value; } /// /// Gets or sets a value indicating whether to include source info (file name and line number) in the /// + /// Default: /// public bool IncludeCallSiteStackTrace { get => _contextLayout.IncludeCallSiteStackTrace; set => _contextLayout.IncludeCallSiteStackTrace = value; } @@ -170,7 +179,7 @@ public sealed override Layout Layout public virtual IList ContextProperties { get; } = new List(); /// - /// List of property names to exclude when is true + /// List of property names to exclude when is /// /// #if !NET35 @@ -188,11 +197,26 @@ protected TargetWithContext() ExcludeProperties = new HashSet(StringComparer.OrdinalIgnoreCase); } + /// + protected override void InitializeTarget() + { + base.InitializeTarget(); + + if (ContextProperties?.Count > 0) + { + foreach (var contextProperty in ContextProperties) + { + if (string.IsNullOrEmpty(contextProperty.Name)) + throw new NLogConfigurationException($"{this}: Contains invalid ContextProperty with unassigned Name-property"); + } + } + } + /// /// Check if logevent has properties (or context properties) /// /// - /// True if properties should be included + /// if properties should be included protected bool ShouldIncludeProperties(LogEventInfo logEvent) { return IncludeGdc @@ -205,7 +229,7 @@ protected bool ShouldIncludeProperties(LogEventInfo logEvent) /// /// /// Dictionary with any context properties for the logEvent (Null if none found) - protected IDictionary GetContextProperties(LogEventInfo logEvent) + protected IDictionary? GetContextProperties(LogEventInfo logEvent) { return GetContextProperties(logEvent, null); } @@ -214,9 +238,9 @@ protected IDictionary GetContextProperties(LogEventInfo logEvent /// Checks if any context properties, and if any returns them as a single dictionary /// /// - /// Optional prefilled dictionary + /// Optional pre-allocated dictionary for the snapshot /// Dictionary with any context properties for the logEvent (Null if none found) - protected IDictionary GetContextProperties(LogEventInfo logEvent, IDictionary combinedProperties) + protected IDictionary? GetContextProperties(LogEventInfo logEvent, IDictionary? combinedProperties) { if (ContextProperties?.Count > 0) { @@ -241,7 +265,7 @@ protected IDictionary GetContextProperties(LogEventInfo logEvent /// /// /// Dictionary with all collected properties for logEvent - protected IDictionary GetAllProperties(LogEventInfo logEvent) + protected IDictionary GetAllProperties(LogEventInfo logEvent) { return GetAllProperties(logEvent, null); } @@ -252,15 +276,16 @@ protected IDictionary GetAllProperties(LogEventInfo logEvent) /// /// Optional prefilled dictionary /// Dictionary with all collected properties for logEvent - protected IDictionary GetAllProperties(LogEventInfo logEvent, IDictionary combinedProperties) + protected IDictionary GetAllProperties(LogEventInfo logEvent, IDictionary? combinedProperties) { if (IncludeEventProperties && logEvent.HasProperties) { // TODO Make Dictionary-Lazy-adapter for PropertiesDictionary to skip extra Dictionary-allocation combinedProperties = combinedProperties ?? CreateNewDictionary(logEvent.Properties.Count + (ContextProperties?.Count ?? 0)); bool checkForDuplicates = combinedProperties.Count > 0; - bool checkExcludeProperties = ExcludeProperties?.Count > 0; - using (var propertyEnumerator = logEvent.CreateOrUpdatePropertiesInternal().GetPropertyEnumerator()) + + var excludeProperties = (ExcludeProperties is null || ExcludeProperties.Count == 0) ? null : ExcludeProperties; + using (var propertyEnumerator = logEvent.CreatePropertiesInternal().GetPropertyEnumerator()) { while (propertyEnumerator.MoveNext()) { @@ -268,7 +293,7 @@ protected IDictionary GetAllProperties(LogEventInfo logEvent, ID if (string.IsNullOrEmpty(property.Key)) continue; - if (checkExcludeProperties && ExcludeProperties.Contains(property.Key)) + if (excludeProperties?.Contains(property.Key) == true) continue; AddContextProperty(logEvent, property.Key, property.Value, checkForDuplicates, combinedProperties); @@ -279,9 +304,9 @@ protected IDictionary GetAllProperties(LogEventInfo logEvent, ID return combinedProperties ?? CreateNewDictionary(0); } - private static IDictionary CreateNewDictionary(int initialCapacity) + private static IDictionary CreateNewDictionary(int initialCapacity) { - return new Dictionary(initialCapacity < 3 ? 0 : initialCapacity, StringComparer.Ordinal); + return new Dictionary(initialCapacity < 3 ? 0 : initialCapacity, StringComparer.Ordinal); } /// @@ -292,19 +317,19 @@ private static IDictionary CreateNewDictionary(int initialCapaci /// Item Value /// Dictionary of context values /// New (unique) value (or null to skip value). If the same value is used then the item will be overwritten - protected virtual string GenerateUniqueItemName(LogEventInfo logEvent, string itemName, object itemValue, IDictionary combinedProperties) + protected virtual string GenerateUniqueItemName(LogEventInfo logEvent, string itemName, object? itemValue, IDictionary combinedProperties) { return PropertiesDictionary.GenerateUniquePropertyName(itemName, combinedProperties, (newKey, props) => props.ContainsKey(newKey)); } - private bool CombineProperties(LogEventInfo logEvent, Layout contextLayout, ref IDictionary combinedProperties) + private bool CombineProperties(LogEventInfo logEvent, Layout contextLayout, ref IDictionary? combinedProperties) { - if (!logEvent.TryGetCachedLayoutValue(contextLayout, out object value)) + if (!logEvent.TryGetCachedLayoutValue(contextLayout, out var value)) { return false; } - if (value is IDictionary contextProperties) + if (value is IDictionary contextProperties) { if (combinedProperties != null) { @@ -322,121 +347,51 @@ private bool CombineProperties(LogEventInfo logEvent, Layout contextLayout, ref return true; } - private void AddContextProperty(LogEventInfo logEvent, string propertyName, object propertyValue, bool checkForDuplicates, IDictionary combinedProperties) + private void AddContextProperty(LogEventInfo logEvent, string propertyName, object? propertyValue, bool checkForDuplicates, IDictionary combinedProperties) { if (checkForDuplicates && combinedProperties.ContainsKey(propertyName)) { propertyName = GenerateUniqueItemName(logEvent, propertyName, propertyValue, combinedProperties); - if (propertyName is null) - return; } combinedProperties[propertyName] = propertyValue; } - /// - /// Obsolete and replaced by with NLog v5. - /// Returns the captured snapshot of for the - /// - /// - /// Dictionary with MDC context if any, else null - [Obsolete("Replaced by GetScopeContextProperties. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected IDictionary GetContextMdc(LogEventInfo logEvent) - { - if (logEvent.TryGetCachedLayoutValue(_contextLayout.ScopeContextPropertiesLayout, out object value)) - { - return value as IDictionary; - } - return CaptureContextMdc(logEvent, null); - } - /// /// Returns the captured snapshot of dictionary for the /// /// /// Dictionary with ScopeContext properties if any, else null - protected IDictionary GetScopeContextProperties(LogEventInfo logEvent) + protected IDictionary? GetScopeContextProperties(LogEventInfo logEvent) { - if (logEvent.TryGetCachedLayoutValue(_contextLayout.ScopeContextPropertiesLayout, out object value)) + if (logEvent.TryGetCachedLayoutValue(_contextLayout.ScopeContextPropertiesLayout, out var value)) { - return value as IDictionary; + return value as IDictionary; } return CaptureScopeContextProperties(logEvent, null); } - /// - /// Obsolete and replaced by with NLog v5. - /// Returns the captured snapshot of for the - /// - /// - /// Dictionary with MDLC context if any, else null - [Obsolete("Replaced by GetScopeContextProperties. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected IDictionary GetContextMdlc(LogEventInfo logEvent) - { - if (logEvent.TryGetCachedLayoutValue(_contextLayout.ScopeContextPropertiesLayout, out object value)) - { - return value as IDictionary; - } - return CaptureContextMdlc(logEvent, null); - } - - /// - /// Obsolete and replaced by with NLog v5. - /// Returns the captured snapshot of for the - /// - /// - /// Collection with NDC context if any, else null - [Obsolete("Replaced by GetScopeContextNested. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected IList GetContextNdc(LogEventInfo logEvent) - { - if (logEvent.TryGetCachedLayoutValue(_contextLayout.ScopeContextNestedStatesLayout, out object value)) - { - return value as IList; - } - return CaptureContextNdc(logEvent); - } - /// /// Returns the captured snapshot of nested states from for the /// /// /// Collection of nested state objects if any, else null - protected IList GetScopeContextNested(LogEventInfo logEvent) + protected IList? GetScopeContextNested(LogEventInfo logEvent) { - if (logEvent.TryGetCachedLayoutValue(_contextLayout.ScopeContextNestedStatesLayout, out object value)) + if (logEvent.TryGetCachedLayoutValue(_contextLayout.ScopeContextNestedStatesLayout, out var value)) { return value as IList; } return CaptureScopeContextNested(logEvent); } - /// - /// Obsolete and replaced by with NLog v5. - /// Returns the captured snapshot of for the - /// - /// - /// Collection with NDLC context if any, else null - [Obsolete("Replaced by GetScopeContextNested. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected IList GetContextNdlc(LogEventInfo logEvent) - { - if (logEvent.TryGetCachedLayoutValue(_contextLayout.ScopeContextNestedStatesLayout, out object value)) - { - return value as IList; - } - return CaptureContextNdlc(logEvent); - } - - private IDictionary CaptureContextProperties(LogEventInfo logEvent, IDictionary combinedProperties) + private IDictionary? CaptureContextProperties(LogEventInfo logEvent, IDictionary? combinedProperties) { combinedProperties = combinedProperties ?? CreateNewDictionary(ContextProperties.Count); for (int i = 0; i < ContextProperties.Count; ++i) { var contextProperty = ContextProperties[i]; - if (string.IsNullOrEmpty(contextProperty?.Name)) + if (contextProperty is null || string.IsNullOrEmpty(contextProperty.Name)) continue; try @@ -458,7 +413,7 @@ private IDictionary CaptureContextProperties(LogEventInfo logEve return combinedProperties; } - private static bool TryGetContextPropertyValue(LogEventInfo logEvent, TargetPropertyWithContext contextProperty, out object propertyValue) + private static bool TryGetContextPropertyValue(LogEventInfo logEvent, TargetPropertyWithContext contextProperty, out object? propertyValue) { propertyValue = contextProperty.RenderValue(logEvent); if (!contextProperty.IncludeEmptyValue && StringHelpers.IsNullOrEmptyString(propertyValue)) @@ -475,7 +430,7 @@ private static bool TryGetContextPropertyValue(LogEventInfo logEvent, TargetProp /// /// Optional pre-allocated dictionary for the snapshot /// Dictionary with GDC context if any, else null - protected virtual IDictionary CaptureContextGdc(LogEventInfo logEvent, IDictionary contextProperties) + protected virtual IDictionary? CaptureContextGdc(LogEventInfo logEvent, IDictionary? contextProperties) { var globalNames = GlobalDiagnosticsContext.GetNames(); if (globalNames.Count == 0) @@ -483,13 +438,13 @@ protected virtual IDictionary CaptureContextGdc(LogEventInfo log contextProperties = contextProperties ?? CreateNewDictionary(globalNames.Count); bool checkForDuplicates = contextProperties.Count > 0; - bool checkExcludeProperties = ExcludeProperties?.Count > 0; + var excludeProperties = (ExcludeProperties is null || ExcludeProperties.Count == 0) ? null : ExcludeProperties; foreach (string propertyName in globalNames) { if (string.IsNullOrEmpty(propertyName)) continue; - if (checkExcludeProperties && ExcludeProperties.Contains(propertyName)) + if (excludeProperties?.Contains(propertyName) == true) continue; var propertyValue = GlobalDiagnosticsContext.GetObject(propertyName); @@ -502,82 +457,18 @@ protected virtual IDictionary CaptureContextGdc(LogEventInfo log return contextProperties; } - /// - /// Obsolete and replaced by with NLog v5. - /// Takes snapshot of for the - /// - /// - /// Optional pre-allocated dictionary for the snapshot - /// Dictionary with MDC context if any, else null - [Obsolete("Replaced by CaptureScopeContextProperties. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected virtual IDictionary CaptureContextMdc(LogEventInfo logEvent, IDictionary contextProperties) - { - var names = MappedDiagnosticsContext.GetNames(); - if (names.Count == 0) - return contextProperties; - - contextProperties = contextProperties ?? CreateNewDictionary(names.Count); - bool checkForDuplicates = contextProperties.Count > 0; - foreach (var name in names) - { - object value = MappedDiagnosticsContext.GetObject(name); - if (SerializeMdcItem(logEvent, name, value, out var serializedValue)) - { - AddContextProperty(logEvent, name, serializedValue, checkForDuplicates, contextProperties); - } - } - return contextProperties; - } - - /// - /// Obsolete and replaced by with NLog v5. - /// Take snapshot of a single object value from - /// - /// Log event - /// MDC key - /// MDC value - /// Snapshot of MDC value - /// Include object value in snapshot - [Obsolete("Replaced by SerializeScopeContextProperty. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected virtual bool SerializeMdcItem(LogEventInfo logEvent, string name, object value, out object serializedValue) - { - if (string.IsNullOrEmpty(name)) - { - serializedValue = null; - return false; - } - - return SerializeItemValue(logEvent, name, value, out serializedValue); - } - - /// - /// Obsolete and replaced by with NLog v5. - /// Takes snapshot of for the - /// - /// - /// Optional pre-allocated dictionary for the snapshot - /// Dictionary with MDLC context if any, else null - [Obsolete("Replaced by CaptureScopeContextProperties. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected virtual IDictionary CaptureContextMdlc(LogEventInfo logEvent, IDictionary contextProperties) - { - return CaptureScopeContextProperties(logEvent, contextProperties); - } - /// /// Takes snapshot of dictionary for the /// /// /// Optional pre-allocated dictionary for the snapshot /// Dictionary with ScopeContext properties if any, else null - protected virtual IDictionary CaptureScopeContextProperties(LogEventInfo logEvent, IDictionary contextProperties) + protected virtual IDictionary? CaptureScopeContextProperties(LogEventInfo logEvent, IDictionary? contextProperties) { using (var scopeEnumerator = ScopeContext.GetAllPropertiesEnumerator()) { bool checkForDuplicates = contextProperties?.Count > 0; - bool checkExcludeProperties = ExcludeProperties?.Count > 0; + var excludeProperties = (ExcludeProperties is null || ExcludeProperties.Count == 0) ? null : ExcludeProperties; while (scopeEnumerator.MoveNext()) { var scopeProperty = scopeEnumerator.Current; @@ -585,12 +476,12 @@ protected virtual IDictionary CaptureScopeContextProperties(LogE if (string.IsNullOrEmpty(propertyName)) continue; - if (checkExcludeProperties && ExcludeProperties.Contains(propertyName)) + if (excludeProperties?.Contains(propertyName) == true) continue; contextProperties = contextProperties ?? CreateNewDictionary(0); - object propertyValue = scopeProperty.Value; + var propertyValue = scopeProperty.Value; if (SerializeScopeContextProperty(logEvent, propertyName, propertyValue, out var serializedValue)) { AddContextProperty(logEvent, propertyName, serializedValue, checkForDuplicates, contextProperties); @@ -601,22 +492,6 @@ protected virtual IDictionary CaptureScopeContextProperties(LogE return contextProperties; } - /// - /// Obsolete and replaced by with NLog v5. - /// Take snapshot of a single object value from - /// - /// Log event - /// MDLC key - /// MDLC value - /// Snapshot of MDLC value - /// Include object value in snapshot - [Obsolete("Replaced by SerializeScopeContextProperty. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected bool SerializeMdlcItem(LogEventInfo logEvent, string name, object value, out object serializedValue) - { - return SerializeScopeContextProperty(logEvent, name, value, out serializedValue); - } - /// /// Take snapshot of a single object value from dictionary /// @@ -625,7 +500,7 @@ protected bool SerializeMdlcItem(LogEventInfo logEvent, string name, object valu /// ScopeContext Dictionary value /// Snapshot of ScopeContext property-value /// Include object value in snapshot - protected virtual bool SerializeScopeContextProperty(LogEventInfo logEvent, string name, object value, out object serializedValue) + protected virtual bool SerializeScopeContextProperty(LogEventInfo logEvent, string name, object? value, out object? serializedValue) { if (string.IsNullOrEmpty(name)) { @@ -636,72 +511,6 @@ protected virtual bool SerializeScopeContextProperty(LogEventInfo logEvent, stri return SerializeItemValue(logEvent, name, value, out serializedValue); } - /// - /// Obsolete and replaced by with NLog v5. - /// Takes snapshot of for the - /// - /// - /// Collection with NDC context if any, else null - [Obsolete("Replaced by CaptureScopeContextNested. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected virtual IList CaptureContextNdc(LogEventInfo logEvent) - { - var stack = NestedDiagnosticsContext.GetAllObjects(); - if (stack.Length == 0) - return stack; - - IList filteredStack = null; - for (int i = 0; i < stack.Length; ++i) - { - var ndcValue = stack[i]; - if (SerializeNdcItem(logEvent, ndcValue, out var serializedValue)) - { - if (filteredStack != null) - filteredStack.Add(serializedValue); - else - stack[i] = serializedValue; - } - else - { - if (filteredStack is null) - { - filteredStack = new List(stack.Length); - for (int j = 0; j < i; ++j) - filteredStack.Add(stack[j]); - } - } - } - return filteredStack ?? stack; - } - - /// - /// Obsolete and replaced by with NLog v5. - /// Take snapshot of a single object value from - /// - /// Log event - /// NDC value - /// Snapshot of NDC value - /// Include object value in snapshot - [Obsolete("Replaced by SerializeScopeContextNestedState. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected virtual bool SerializeNdcItem(LogEventInfo logEvent, object value, out object serializedValue) - { - return SerializeItemValue(logEvent, null, value, out serializedValue); - } - - /// - /// Obsolete and replaced by with NLog v5. - /// Takes snapshot of for the - /// - /// - /// Collection with NDLC context if any, else null - [Obsolete("Replaced by CaptureScopeContextNested. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected virtual IList CaptureContextNdlc(LogEventInfo logEvent) - { - return CaptureScopeContextNested(logEvent); - } - /// /// Takes snapshot of nested states from for the /// @@ -713,11 +522,11 @@ protected virtual IList CaptureScopeContextNested(LogEventInfo logEvent) if (stack.Count == 0) return stack; - IList filteredStack = null; + IList? filteredStack = null; for (int i = 0; i < stack.Count; ++i) { var ndcValue = stack[i]; - if (SerializeScopeContextNestedState(logEvent, ndcValue, out var serializedValue)) + if (SerializeScopeContextNestedState(logEvent, ndcValue, out var serializedValue) && serializedValue != null) { if (filteredStack != null) filteredStack.Add(serializedValue); @@ -737,21 +546,6 @@ protected virtual IList CaptureScopeContextNested(LogEventInfo logEvent) return filteredStack ?? stack; } - /// - /// Obsolete and replaced by with NLog v5. - /// Take snapshot of a single object value from - /// - /// Log event - /// NDLC value - /// Snapshot of NDLC value - /// Include object value in snapshot - [Obsolete("Replaced by SerializeScopeContextNestedState. Marked obsolete on NLog 5.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - protected virtual bool SerializeNdlcItem(LogEventInfo logEvent, object value, out object serializedValue) - { - return SerializeScopeContextNestedState(logEvent, value, out serializedValue); - } - /// /// Take snapshot of a single object value from nested states /// @@ -759,9 +553,9 @@ protected virtual bool SerializeNdlcItem(LogEventInfo logEvent, object value, ou /// nested state value /// Snapshot of stack item value /// Include object value in snapshot - protected virtual bool SerializeScopeContextNestedState(LogEventInfo logEvent, object value, out object serializedValue) + protected virtual bool SerializeScopeContextNestedState(LogEventInfo logEvent, object value, out object? serializedValue) { - return SerializeItemValue(logEvent, null, value, out serializedValue); + return SerializeItemValue(logEvent, string.Empty, value, out serializedValue); } /// @@ -772,7 +566,7 @@ protected virtual bool SerializeScopeContextNestedState(LogEventInfo logEvent, o /// Object Value /// Snapshot of value /// Include object value in snapshot - protected virtual bool SerializeItemValue(LogEventInfo logEvent, string name, object value, out object serializedValue) + protected virtual bool SerializeItemValue(LogEventInfo logEvent, string name, object? value, out object? serializedValue) { if (value is null) { @@ -780,22 +574,49 @@ protected virtual bool SerializeItemValue(LogEventInfo logEvent, string name, ob return true; } - if (value is string || Convert.GetTypeCode(value) != TypeCode.Object || value.GetType().IsValueType()) + if (value is string || Convert.GetTypeCode(value) != TypeCode.Object || value.GetType().IsValueType) { serializedValue = value; // Already immutable, snapshot is not needed return true; } // Make snapshot of the context value - serializedValue = Convert.ToString(value, logEvent.FormatProvider ?? LoggingConfiguration?.DefaultCultureInfo); + serializedValue = Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture); return true; } + /// + /// Returns the captured snapshot of for the + /// + /// + /// Collection with NDLC context if any, else null + [Obsolete("Replaced by GetScopeContextNested. Marked obsolete on NLog 5.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + protected IList? GetContextNdlc(LogEventInfo logEvent) => GetScopeContextNested(logEvent); + + /// + /// Returns the captured snapshot of for the + /// + /// + /// Dictionary with MDLC context if any, else null + [Obsolete("Replaced by GetScopeContextProperties. Marked obsolete on NLog 5.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + protected IDictionary? GetContextMdlc(LogEventInfo logEvent) => GetScopeContextProperties(logEvent); + [ThreadAgnostic] - internal sealed class TargetWithContextLayout : Layout, IIncludeContext, IUsesStackTrace + internal sealed class TargetWithContextLayout : Layout, IIncludeContext, IUsesStackTrace, IStringValueRenderer { - public Layout TargetLayout { get => _targetLayout; set => _targetLayout = ReferenceEquals(this, value) ? _targetLayout : value; } - private Layout _targetLayout; + public Layout TargetLayout + { + get => _targetLayout; + set + { + _targetLayout = ReferenceEquals(this, value) ? _targetLayout : value; + _targetStringLayout = _targetLayout as IStringValueRenderer; + } + } + private Layout _targetLayout = Layout.Empty; + private IStringValueRenderer? _targetStringLayout; /// Internal Layout that allows capture of properties-dictionary internal LayoutScopeContextProperties ScopeContextPropertiesLayout { get; } @@ -896,6 +717,7 @@ public TargetWithContextLayout(TargetWithContext owner, Layout targetLayout) protected override void InitializeLayout() { base.InitializeLayout(); + if (IncludeScopeProperties || IncludeScopeNested) ThreadAgnostic = false; if (IncludeEventProperties) @@ -955,6 +777,11 @@ protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuil TargetLayout?.Render(logEvent, target); } + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) + { + return _targetStringLayout?.GetFormattedString(logEvent); + } + public class LayoutScopeContextProperties : Layout { private readonly TargetWithContext _owner; diff --git a/src/NLog/Targets/TargetWithLayout.cs b/src/NLog/Targets/TargetWithLayout.cs index 0ef8baa6da..808f7e5c10 100644 --- a/src/NLog/Targets/TargetWithLayout.cs +++ b/src/NLog/Targets/TargetWithLayout.cs @@ -33,7 +33,6 @@ namespace NLog.Targets { - using NLog.Config; using NLog.Layouts; /// @@ -65,17 +64,14 @@ public abstract class TargetWithLayout : Target /// protected TargetWithLayout() { - Layout = new SimpleLayout(DefaultLayout, DefaultLayoutText, ConfigurationItemFactory.Default); + Layout = new SimpleLayout(DefaultLayout, DefaultLayoutText); } /// /// Gets or sets the layout used to format log messages. /// - /// - /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} - /// + /// [Required] Default: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} /// - [RequiredParameter] public virtual Layout Layout { get; set; } } } diff --git a/src/NLog/Targets/TargetWithLayoutHeaderAndFooter.cs b/src/NLog/Targets/TargetWithLayoutHeaderAndFooter.cs index 9e83ebf1fe..6ecf13723e 100644 --- a/src/NLog/Targets/TargetWithLayoutHeaderAndFooter.cs +++ b/src/NLog/Targets/TargetWithLayoutHeaderAndFooter.cs @@ -33,14 +33,15 @@ namespace NLog.Targets { - using Config; - using Layouts; + using NLog.Layouts; /// /// Represents target that supports string formatting using layouts. /// public abstract class TargetWithLayoutHeaderAndFooter : TargetWithLayout { + private Layout _layout; + /// /// Initializes a new instance of the class. /// @@ -49,36 +50,30 @@ public abstract class TargetWithLayoutHeaderAndFooter : TargetWithLayout /// protected TargetWithLayoutHeaderAndFooter() { + var layout = base.Layout; + _layout = layout is LayoutWithHeaderAndFooter headerAndFooter ? headerAndFooter.Layout : layout; } - /// - /// Gets or sets the text to be rendered. - /// - /// - /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true} - /// - /// - [RequiredParameter] + /// public override Layout Layout { - get => LHF.Layout; - + get => _layout; set { - if (value is LayoutWithHeaderAndFooter) + if (value is LayoutWithHeaderAndFooter layout) { base.Layout = value; + _layout = layout.Layout; } - else if (LHF is null) + else if (base.Layout is LayoutWithHeaderAndFooter headerAndFooter) { - LHF = new LayoutWithHeaderAndFooter() - { - Layout = value - }; + headerAndFooter.Layout = value; + _layout = value; } else { - LHF.Layout = value; + base.Layout = new LayoutWithHeaderAndFooter() { Layout = value }; + _layout = value; } } } @@ -86,31 +81,43 @@ public override Layout Layout /// /// Gets or sets the footer. /// + /// Default: /// - public Layout Footer + public Layout? Footer { - get => LHF.Footer; - set => LHF.Footer = value; + get => (base.Layout as LayoutWithHeaderAndFooter)?.Footer; + set + { + if (base.Layout is LayoutWithHeaderAndFooter headerAndFooter) + { + headerAndFooter.Footer = value; + } + else if (value is not null) + { + base.Layout = new LayoutWithHeaderAndFooter() { Layout = base.Layout, Footer = value }; + } + } } /// /// Gets or sets the header. /// + /// Default: /// - public Layout Header + public Layout? Header { - get => LHF.Header; - set => LHF.Header = value; - } - - /// - /// Gets or sets the layout with header and footer. - /// - /// The layout with header and footer. - private LayoutWithHeaderAndFooter LHF - { - get => (LayoutWithHeaderAndFooter)base.Layout; - set => base.Layout = value; + get => (base.Layout as LayoutWithHeaderAndFooter)?.Header; + set + { + if (base.Layout is LayoutWithHeaderAndFooter headerAndFooter) + { + headerAndFooter.Header = value; + } + else if (value is not null) + { + base.Layout = new LayoutWithHeaderAndFooter() { Layout = base.Layout, Header = value }; + } + } } } } diff --git a/src/NLog/Targets/Wrappers/AsyncRequestQueue-T.cs b/src/NLog/Targets/Wrappers/AsyncRequestQueue-T.cs index 74df5d231a..a4a00bb56e 100644 --- a/src/NLog/Targets/Wrappers/AsyncRequestQueue-T.cs +++ b/src/NLog/Targets/Wrappers/AsyncRequestQueue-T.cs @@ -46,11 +46,11 @@ internal sealed class AsyncRequestQueue : AsyncRequestQueueBase /// /// Initializes a new instance of the AsyncRequestQueue class. /// - /// Request limit. + /// Queue max size. /// The overflow action. - public AsyncRequestQueue(int requestLimit, AsyncTargetWrapperOverflowAction overflowAction) + public AsyncRequestQueue(int queueLimit, AsyncTargetWrapperOverflowAction overflowAction) { - RequestLimit = requestLimit; + QueueLimit = queueLimit; OnOverflow = overflowAction; } @@ -81,7 +81,7 @@ public override bool Enqueue(AsyncLogEventInfo logEventInfo) lock (_logEventInfoQueue) { var currentCount = _logEventInfoQueue.Count; - if (currentCount >= RequestLimit) + if (currentCount >= QueueLimit) { switch (OnOverflow) { @@ -93,12 +93,12 @@ public override bool Enqueue(AsyncLogEventInfo logEventInfo) case AsyncTargetWrapperOverflowAction.Grow: InternalLogger.Debug("AsyncQueue - Growing the size of queue, because queue is full"); + QueueLimit *= 2; OnLogEventQueueGrows(currentCount + 1); - RequestLimit *= 2; break; case AsyncTargetWrapperOverflowAction.Block: - while (currentCount >= RequestLimit) + while (currentCount >= QueueLimit) { InternalLogger.Debug("AsyncQueue - Blocking until ready, because queue is full"); System.Threading.Monitor.Wait(_logEventInfoQueue); diff --git a/src/NLog/Targets/Wrappers/AsyncRequestQueueBase.cs b/src/NLog/Targets/Wrappers/AsyncRequestQueueBase.cs index f0cc71855d..e958df0410 100644 --- a/src/NLog/Targets/Wrappers/AsyncRequestQueueBase.cs +++ b/src/NLog/Targets/Wrappers/AsyncRequestQueueBase.cs @@ -31,10 +31,9 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; - namespace NLog.Targets.Wrappers { + using System; using System.Collections.Generic; using NLog.Common; @@ -43,9 +42,9 @@ internal abstract class AsyncRequestQueueBase public abstract bool IsEmpty { get; } /// - /// Gets or sets the request limit. + /// Gets or sets the queue max-size /// - public int RequestLimit { get; set; } + public int QueueLimit { get; set; } /// /// Gets or sets the action to be taken when there's no more room in @@ -56,12 +55,12 @@ internal abstract class AsyncRequestQueueBase /// /// Occurs when LogEvent has been dropped, because internal queue is full and set to /// - public event EventHandler LogEventDropped; + public event EventHandler? LogEventDropped; /// /// Occurs when internal queue size is growing, because internal queue is full and set to /// - public event EventHandler LogEventQueueGrow; + public event EventHandler? LogEventQueueGrow; public abstract bool Enqueue(AsyncLogEventInfo logEventInfo); @@ -78,9 +77,9 @@ internal abstract class AsyncRequestQueueBase protected void OnLogEventDropped(LogEventInfo logEventInfo) => LogEventDropped?.Invoke(this, new LogEventDroppedEventArgs(logEventInfo)); /// - /// Raise event when RequestCount overflow + /// Raise event when RequestCount overflow /// /// current requests count - protected void OnLogEventQueueGrows(long requestsCount) => LogEventQueueGrow?.Invoke(this, new LogEventQueueGrowEventArgs(RequestLimit, requestsCount)); + protected void OnLogEventQueueGrows(long requestsCount) => LogEventQueueGrow?.Invoke(this, new LogEventQueueGrowEventArgs(QueueLimit, requestsCount)); } } diff --git a/src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs b/src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs index 0815ebc598..200bdae6d7 100644 --- a/src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs +++ b/src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs @@ -81,18 +81,29 @@ public class AsyncTargetWrapper : WrapperTargetBase { private readonly object _writeLockObject = new object(); private readonly object _timerLockObject = new object(); - private Timer _lazyWriterTimer; + private Timer? _lazyWriterTimer; private readonly ReusableAsyncLogEventList _reusableAsyncLogEventList = new ReusableAsyncLogEventList(200); - private event EventHandler _logEventDroppedEvent; - private event EventHandler _eventQueueGrowEvent; + private readonly WaitCallback _flushEventsInQueueDelegate; + private event EventHandler? _logEventDroppedEvent; + private event EventHandler? _eventQueueGrowEvent; private bool _missingServiceTypes; /// /// Initializes a new instance of the class. /// public AsyncTargetWrapper() - : this(null) { +#if NETFRAMEWORK + _requestQueue = new AsyncRequestQueue(10000, AsyncTargetWrapperOverflowAction.Discard); +#else + // NetStandard20 includes many optimizations for ConcurrentQueue: + // - See: https://blogs.msdn.microsoft.com/dotnet/2017/06/07/performance-improvements-in-net-core/ + // Net40 ConcurrencyQueue can seem to leak, because it doesn't clear properly on dequeue + // - See: https://blogs.msdn.microsoft.com/pfxteam/2012/05/08/concurrentqueuet-holding-on-to-a-few-dequeued-elements/ + _requestQueue = new ConcurrentRequestQueue(10000, AsyncTargetWrapperOverflowAction.Discard); +#endif + + _flushEventsInQueueDelegate = FlushEventsInQueue; } /// @@ -122,18 +133,10 @@ public AsyncTargetWrapper(Target wrappedTarget) /// Maximum number of requests in the queue. /// The action to be taken when the queue overflows. public AsyncTargetWrapper(Target wrappedTarget, int queueLimit, AsyncTargetWrapperOverflowAction overflowAction) + : this() { - Name = string.IsNullOrEmpty(wrappedTarget?.Name) ? Name : (wrappedTarget.Name + "_wrapper"); + Name = (wrappedTarget is null || string.IsNullOrEmpty(wrappedTarget.Name)) ? Name : (wrappedTarget.Name + "_wrapper"); WrappedTarget = wrappedTarget; -#if NETSTANDARD2_0 - // NetStandard20 includes many optimizations for ConcurrentQueue: - // - See: https://blogs.msdn.microsoft.com/dotnet/2017/06/07/performance-improvements-in-net-core/ - // Net40 ConcurrencyQueue can seem to leak, because it doesn't clear properly on dequeue - // - See: https://blogs.msdn.microsoft.com/pfxteam/2012/05/08/concurrentqueuet-holding-on-to-a-few-dequeued-elements/ - _requestQueue = new ConcurrentRequestQueue(10000, AsyncTargetWrapperOverflowAction.Discard); -#else - _requestQueue = new AsyncRequestQueue(10000, AsyncTargetWrapperOverflowAction.Discard); -#endif QueueLimit = queueLimit; OverflowAction = overflowAction; } @@ -142,12 +145,14 @@ public AsyncTargetWrapper(Target wrappedTarget, int queueLimit, AsyncTargetWrapp /// Gets or sets the number of log events that should be processed in a batch /// by the lazy writer thread. /// + /// Default: /// public int BatchSize { get; set; } = 200; /// /// Gets or sets the time in milliseconds to sleep between batches. (1 or less means trigger on new activity) /// + /// Default: ms /// public int TimeToSleepBetweenBatches { get; set; } = 1; @@ -158,7 +163,7 @@ public event EventHandler LogEventDropped { add { - if (_eventQueueGrowEvent == null && _requestQueue != null) + if (_logEventDroppedEvent is null) { _requestQueue.LogEventDropped += OnRequestQueueDropItem; } @@ -169,7 +174,7 @@ public event EventHandler LogEventDropped { _logEventDroppedEvent -= value; - if (_eventQueueGrowEvent == null && _requestQueue != null) + if (_logEventDroppedEvent is null) { _requestQueue.LogEventDropped -= OnRequestQueueDropItem; } @@ -183,7 +188,7 @@ public event EventHandler EventQueueGrow { add { - if (_eventQueueGrowEvent == null && _requestQueue != null) + if (_eventQueueGrowEvent is null) { _requestQueue.LogEventQueueGrow += OnRequestQueueGrow; } @@ -194,7 +199,7 @@ public event EventHandler EventQueueGrow { _eventQueueGrowEvent -= value; - if (_eventQueueGrowEvent == null && _requestQueue != null) + if (_eventQueueGrowEvent is null) { _requestQueue.LogEventQueueGrow -= OnRequestQueueGrow; } @@ -205,6 +210,7 @@ public event EventHandler EventQueueGrow /// Gets or sets the action to be taken when the lazy writer thread request queue count /// exceeds the set limit. /// + /// Default: /// public AsyncTargetWrapperOverflowAction OverflowAction { @@ -215,28 +221,25 @@ public AsyncTargetWrapperOverflowAction OverflowAction /// /// Gets or sets the limit on the number of requests in the lazy writer thread request queue. /// + /// Default: /// public int QueueLimit { - get => _requestQueue.RequestLimit; - set => _requestQueue.RequestLimit = value; + get => _requestQueue.QueueLimit; + set => _requestQueue.QueueLimit = value; } /// /// Gets or sets the number of batches of to write before yielding into /// - /// - /// Performance is better when writing many small batches, than writing a single large batch - /// + /// Default: . Better performance when writing small batches, than single large batch. /// - public int FullBatchSizeWriteLimit { get; set; } = 5; + public int FullBatchSizeWriteLimit { get; set; } = 10; /// /// Gets or sets whether to use the locking queue, instead of a lock-free concurrent queue /// - /// - /// The locking queue is less concurrent when many logger threads, but reduces memory allocation - /// + /// Default: . Queue with Monitor.Lock is less concurrent when many logger threads, but reduces memory allocation /// public bool ForceLockingQueue { get => _forceLockingQueue ?? false; set => _forceLockingQueue = value; } private bool? _forceLockingQueue; @@ -252,13 +255,9 @@ public int QueueLimit /// The asynchronous continuation. protected override void FlushAsync(AsyncContinuation asyncContinuation) { - if (_flushEventsInQueueDelegate is null) - _flushEventsInQueueDelegate = new AsyncHelpersTask(FlushEventsInQueue); - AsyncHelpers.StartAsyncTask(_flushEventsInQueueDelegate.Value, asyncContinuation); + AsyncHelpers.StartAsyncTask(_flushEventsInQueueDelegate, asyncContinuation); } - private AsyncHelpersTask? _flushEventsInQueueDelegate; - /// /// Initializes the target by starting the lazy writer timer. /// @@ -304,7 +303,8 @@ protected override void CloseTarget() { StopLazyWriterThread(); - if (Monitor.TryEnter(_writeLockObject, 500)) + var lockTimeoutMs = OverflowAction == AsyncTargetWrapperOverflowAction.Discard ? 500 : 1500; + if (Monitor.TryEnter(_writeLockObject, lockTimeoutMs)) { try { @@ -315,6 +315,10 @@ protected override void CloseTarget() Monitor.Exit(_writeLockObject); } } + else + { + InternalLogger.Debug("{0}: Failed to flush after lock timeout", this); + } if (OverflowAction == AsyncTargetWrapperOverflowAction.Block) { @@ -347,7 +351,7 @@ protected virtual void StartLazyWriterTimer() /// Attempts to start an instant timer-worker-thread which can write /// queued log messages. /// - /// Returns true when scheduled a timer-worker-thread + /// Returns when scheduled a timer-worker-thread protected virtual bool StartInstantWriterTimer() { return StartLazyWriterThread(true); @@ -518,32 +522,37 @@ private void ProcessPendingEvents(object state) private void FlushEventsInQueue(object state) { - try + var asyncContinuation = state as AsyncContinuation; + if (Monitor.TryEnter(_writeLockObject, 1500)) { - var asyncContinuation = state as AsyncContinuation; - lock (_writeLockObject) + try { WriteLogEventsInQueue(int.MaxValue, "Flush Async"); if (asyncContinuation != null) base.FlushAsync(asyncContinuation); } - } - catch (Exception exception) - { + catch (Exception exception) + { #if DEBUG - if (exception.MustBeRethrownImmediately()) + if (exception.MustBeRethrownImmediately()) + { + throw; // Throwing exceptions here will crash the entire application (.NET 2.0 behavior) + } +#endif + InternalLogger.Error(exception, "{0}: Error in flush procedure.", this); + } + finally { - throw; // Throwing exceptions here will crash the entire application (.NET 2.0 behavior) + Monitor.Exit(_writeLockObject); + if (TimeToSleepBetweenBatches <= 1 && !_requestQueue.IsEmpty) + { + StartLazyWriterTimer(); + } } -#endif - InternalLogger.Error(exception, "{0}: Error in flush procedure.", this); } - finally + else { - if (TimeToSleepBetweenBatches <= 1 && !_requestQueue.IsEmpty) - { - StartLazyWriterTimer(); - } + asyncContinuation?.Invoke(new NLogRuntimeException($"Target {this} failed to flush after lock timeout.")); } } @@ -599,7 +608,7 @@ private int WriteLogEventsToTarget(IList logEvents, string re { if (reason != null) InternalLogger.Trace("{0}: Writing {1} events ({2})", this, batchSize, reason); - WrappedTarget.WriteAsyncLogEvents(logEvents); + WrappedTarget?.WriteAsyncLogEvents(logEvents); } return batchSize; diff --git a/src/NLog/Targets/Wrappers/AutoFlushTargetWrapper.cs b/src/NLog/Targets/Wrappers/AutoFlushTargetWrapper.cs index 02f30bcf2e..87c0c11c2c 100644 --- a/src/NLog/Targets/Wrappers/AutoFlushTargetWrapper.cs +++ b/src/NLog/Targets/Wrappers/AutoFlushTargetWrapper.cs @@ -63,14 +63,14 @@ public class AutoFlushTargetWrapper : WrapperTargetBase /// Gets or sets the condition expression. Log events who meet this condition will cause /// a flush on the wrapped target. /// + /// Default: /// - public ConditionExpression Condition { get; set; } + public ConditionExpression? Condition { get; set; } /// /// Delay the flush until the LogEvent has been confirmed as written /// - /// If not explicitly set, then disabled by default for and AsyncTaskTarget - /// + /// Default: . When not explicitly set, then automatically disabled when or AsyncTaskTarget /// public bool AsyncFlush { @@ -82,6 +82,7 @@ public bool AsyncFlush /// /// Only flush when LogEvent matches condition. Ignore explicit-flush, config-reload-flush and shutdown-flush /// + /// Default: /// public bool FlushOnConditionOnly { get; set; } @@ -92,8 +93,8 @@ public bool AsyncFlush /// Initializes a new instance of the class. /// public AutoFlushTargetWrapper() - : this(null) { + _flushCompletedContinuation = (ex) => _pendingManualFlushList.CompleteOperation(ex); } /// @@ -112,29 +113,29 @@ public AutoFlushTargetWrapper(string name, Target wrappedTarget) /// /// The wrapped target. public AutoFlushTargetWrapper(Target wrappedTarget) + : this() { - Name = string.IsNullOrEmpty(wrappedTarget?.Name) ? Name : (wrappedTarget.Name + "_wrapper"); + Name = (wrappedTarget is null || string.IsNullOrEmpty(wrappedTarget.Name)) ? Name : (wrappedTarget.Name + "_wrapper"); WrappedTarget = wrappedTarget; - _flushCompletedContinuation = (ex) => _pendingManualFlushList.CompleteOperation(ex); } /// protected override void InitializeTarget() { base.InitializeTarget(); - if (!_asyncFlush.HasValue && !TargetSupportsAsyncFlush(WrappedTarget)) + if (!_asyncFlush.HasValue && !TargetSupportsAsyncFlush()) { AsyncFlush = false; // Disable AsyncFlush, so the intended trigger works } } - private static bool TargetSupportsAsyncFlush(Target wrappedTarget) + private bool TargetSupportsAsyncFlush() { - if (wrappedTarget is BufferingTargetWrapper) + if (WrappedTarget is BufferingTargetWrapper) return false; #if !NET35 - if (wrappedTarget is AsyncTaskTarget) + if (WrappedTarget is AsyncTaskTarget) return false; #endif @@ -165,18 +166,18 @@ protected override void Write(AsyncLogEventInfo logEvent) currentContinuation(ex); }; _pendingManualFlushList.BeginOperation(); - WrappedTarget.WriteAsyncLogEvent(logEvent.LogEvent.WithContinuation(wrappedContinuation)); + WrappedTarget?.WriteAsyncLogEvent(logEvent.LogEvent.WithContinuation(wrappedContinuation)); } else { _pendingManualFlushList.BeginOperation(); - WrappedTarget.WriteAsyncLogEvent(logEvent); + WrappedTarget?.WriteAsyncLogEvent(logEvent); FlushWrappedTarget(_flushCompletedContinuation); } } else { - WrappedTarget.WriteAsyncLogEvent(logEvent); + WrappedTarget?.WriteAsyncLogEvent(logEvent); } } @@ -199,7 +200,7 @@ protected override void FlushAsync(AsyncContinuation asyncContinuation) private void FlushWrappedTarget(AsyncContinuation asyncContinuation) { - WrappedTarget.Flush(asyncContinuation); + WrappedTarget?.Flush(asyncContinuation); } /// diff --git a/src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs b/src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs index a6668271e3..fa69dc6180 100644 --- a/src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs +++ b/src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs @@ -49,16 +49,17 @@ namespace NLog.Targets.Wrappers [Target("BufferingWrapper", IsWrapper = true)] public class BufferingTargetWrapper : WrapperTargetBase { - private AsyncRequestQueue _buffer; - private Timer _flushTimer; + private AsyncRequestQueue _buffer = new AsyncRequestQueue(100, AsyncTargetWrapperOverflowAction.Discard); + private Timer? _flushTimer; private readonly object _lockObject = new object(); /// /// Initializes a new instance of the class. /// public BufferingTargetWrapper() - : this(null) { + BufferSize = 100; + FlushTimeout = -1; } /// @@ -111,7 +112,7 @@ public BufferingTargetWrapper(Target wrappedTarget, int bufferSize, int flushTim /// The action to take when the buffer overflows. public BufferingTargetWrapper(Target wrappedTarget, int bufferSize, int flushTimeout, BufferingTargetWrapperOverflowAction overflowAction) { - Name = string.IsNullOrEmpty(wrappedTarget?.Name) ? Name : (wrappedTarget.Name + "_wrapper"); + Name = (wrappedTarget is null || string.IsNullOrEmpty(wrappedTarget.Name)) ? Name : (wrappedTarget.Name + "_wrapper"); WrappedTarget = wrappedTarget; BufferSize = bufferSize; FlushTimeout = flushTimeout; @@ -121,6 +122,7 @@ public BufferingTargetWrapper(Target wrappedTarget, int bufferSize, int flushTim /// /// Gets or sets the number of log events to be buffered. /// + /// Default: /// public Layout BufferSize { get; set; } @@ -128,6 +130,7 @@ public BufferingTargetWrapper(Target wrappedTarget, int bufferSize, int flushTim /// Gets or sets the timeout (in milliseconds) after which the contents of buffer will be flushed /// if there's no write in the specified period of time. Use -1 to disable timed flushes. /// + /// Default: . Zero or Negative means disabled. /// public Layout FlushTimeout { get; set; } @@ -135,8 +138,9 @@ public BufferingTargetWrapper(Target wrappedTarget, int bufferSize, int flushTim /// Gets or sets a value indicating whether to use sliding timeout. /// /// - /// This value determines how the inactivity period is determined. If sliding timeout is enabled, - /// the inactivity timer is reset after each write, if it is disabled - inactivity timer will + /// Default: . + /// This value determines how the inactivity period is determined. When + /// the inactivity timer is reset after each write, if - inactivity timer will /// count from the first event written to the buffer. /// /// @@ -146,10 +150,9 @@ public BufferingTargetWrapper(Target wrappedTarget, int bufferSize, int flushTim /// Gets or sets the action to take if the buffer overflows. /// /// - /// Setting to will replace the - /// oldest event with new events without sending events down to the wrapped target, and - /// setting to will flush the - /// entire buffer to the wrapped target. + /// Default: . Setting to + /// will flush the entire buffer to the wrapped target. Setting to + /// will replace the oldest event with new events without sending events down to the wrapped target. /// /// public BufferingTargetWrapperOverflowAction OverflowAction { get; set; } @@ -160,8 +163,22 @@ public BufferingTargetWrapper(Target wrappedTarget, int bufferSize, int flushTim /// The asynchronous continuation. protected override void FlushAsync(AsyncContinuation asyncContinuation) { - WriteEventsInBuffer("Flush Async"); - base.FlushAsync(asyncContinuation); + if (Monitor.TryEnter(_lockObject, 1500)) + { + try + { + WriteEventsInBuffer("Flush Async"); + base.FlushAsync(asyncContinuation); + } + finally + { + Monitor.Exit(_lockObject); + } + } + else + { + asyncContinuation(new NLogRuntimeException($"Target {this} failed to flush after lock timeout.")); + } } /// @@ -195,7 +212,22 @@ protected override void CloseTarget() } else { - WriteEventsInBuffer("Closing Target"); + var lockTimeoutMs = OverflowAction == BufferingTargetWrapperOverflowAction.Discard ? 500 : 1500; + if (Monitor.TryEnter(_lockObject, lockTimeoutMs)) + { + try + { + WriteEventsInBuffer("Closing Target"); + } + finally + { + Monitor.Exit(_lockObject); + } + } + else + { + InternalLogger.Debug("{0}: Failed to flush after lock timeout", this); + } } } } @@ -213,13 +245,16 @@ protected override void Write(AsyncLogEventInfo logEvent) PrecalculateVolatileLayouts(logEvent.LogEvent); var firstEventInQueue = _buffer.Enqueue(logEvent); - if (_buffer.RequestCount >= _buffer.RequestLimit) + if (_buffer.RequestCount >= _buffer.QueueLimit) { // If the OverflowAction action is set to "Discard", the buffer will automatically // roll over the oldest item. if (OverflowAction == BufferingTargetWrapperOverflowAction.Flush) { - WriteEventsInBuffer("Exceeding BufferSize"); + lock (_lockObject) + { + WriteEventsInBuffer("Exceeding BufferSize"); + } } } else @@ -230,7 +265,7 @@ protected override void Write(AsyncLogEventInfo logEvent) if (flushTimeout > 0) { // reset the timer on first item added to the buffer or whenever SlidingTimeout is set to true - _flushTimer.Change(flushTimeout, -1); + _flushTimer?.Change(flushTimeout, -1); } } } @@ -250,7 +285,7 @@ private void FlushCallback(object state) if (_flushTimer is null) return; - WriteEventsInBuffer(null); + WriteEventsInBuffer(string.Empty); } else { @@ -285,15 +320,12 @@ private void WriteEventsInBuffer(string reason) return; } - lock (_lockObject) + AsyncLogEventInfo[] logEvents = _buffer.DequeueBatch(int.MaxValue); + if (logEvents.Length > 0) { - AsyncLogEventInfo[] logEvents = _buffer.DequeueBatch(int.MaxValue); - if (logEvents.Length > 0) - { - if (reason != null) - InternalLogger.Trace("{0}: Writing {1} events ({2})", this, logEvents.Length, reason); - WrappedTarget.WriteAsyncLogEvents(logEvents); - } + if (!string.IsNullOrEmpty(reason)) + InternalLogger.Trace("{0}: Writing {1} events ({2})", this, logEvents.Length, reason); + WrappedTarget.WriteAsyncLogEvents(logEvents); } } } diff --git a/src/NLog/Targets/Wrappers/ConcurrentRequestQueue.cs b/src/NLog/Targets/Wrappers/ConcurrentRequestQueue.cs index d7b1b245a5..24f62f786f 100644 --- a/src/NLog/Targets/Wrappers/ConcurrentRequestQueue.cs +++ b/src/NLog/Targets/Wrappers/ConcurrentRequestQueue.cs @@ -51,11 +51,11 @@ internal sealed class ConcurrentRequestQueue : AsyncRequestQueueBase /// /// Initializes a new instance of the AsyncRequestQueue class. /// - /// Request limit. + /// Queue max size. /// The overflow action. - public ConcurrentRequestQueue(int requestLimit, AsyncTargetWrapperOverflowAction overflowAction) + public ConcurrentRequestQueue(int queueLimit, AsyncTargetWrapperOverflowAction overflowAction) { - RequestLimit = requestLimit; + QueueLimit = queueLimit; OnOverflow = overflowAction; } @@ -80,7 +80,7 @@ public override bool Enqueue(AsyncLogEventInfo logEventInfo) { long currentCount = Interlocked.Increment(ref _count); bool queueWasEmpty = currentCount == 1; // Inserted first item in empty queue - if (currentCount > RequestLimit) + if (currentCount > QueueLimit) { switch (OnOverflow) { @@ -97,8 +97,12 @@ public override bool Enqueue(AsyncLogEventInfo logEventInfo) case AsyncTargetWrapperOverflowAction.Grow: { InternalLogger.Debug("AsyncQueue - Growing the size of queue, because queue is full"); + var currentQueueLimit = QueueLimit; + if (currentCount > currentQueueLimit) + { + QueueLimit = currentQueueLimit * 2; + } OnLogEventQueueGrows(currentCount); - RequestLimit *= 2; } break; } @@ -123,7 +127,7 @@ private bool DequeueUntilBelowRequestLimit() } currentCount = Interlocked.Read(ref _count); queueWasEmpty = true; - } while (currentCount > RequestLimit); + } while (currentCount > QueueLimit); return queueWasEmpty; } @@ -134,14 +138,14 @@ private bool WaitForBelowRequestLimit() long currentCount = TrySpinWaitForLowerCount(); // If yield did not help, then wait on a lock - if (currentCount > RequestLimit) + if (currentCount > QueueLimit) { InternalLogger.Debug("AsyncQueue - Blocking until ready, because queue is full"); lock (_logEventInfoQueue) { InternalLogger.Trace("AsyncQueue - Entered critical section."); currentCount = Interlocked.Read(ref _count); - while (currentCount > RequestLimit) + while (currentCount > QueueLimit) { Interlocked.Decrement(ref _count); Monitor.Wait(_logEventInfoQueue); @@ -171,7 +175,7 @@ private long TrySpinWaitForLowerCount() spinWait.SpinOnce(); currentCount = Interlocked.Read(ref _count); - if (currentCount <= RequestLimit) + if (currentCount <= QueueLimit) break; } diff --git a/src/NLog/Targets/Wrappers/FallbackGroupTarget.cs b/src/NLog/Targets/Wrappers/FallbackGroupTarget.cs index e3a27e6cfa..858397a166 100644 --- a/src/NLog/Targets/Wrappers/FallbackGroupTarget.cs +++ b/src/NLog/Targets/Wrappers/FallbackGroupTarget.cs @@ -93,12 +93,14 @@ public FallbackGroupTarget(params Target[] targets) /// /// Gets or sets a value indicating whether to return to the first target after any successful write. /// + /// Default: /// public bool ReturnToFirstOnSuccess { get; set; } /// /// Gets or sets whether to enable batching, but fallback will be handled individually /// + /// Default: /// public bool EnableBatchWrite { get; set; } = true; @@ -158,7 +160,7 @@ private AsyncLogEventInfo WrapWithFallback(AsyncLogEventInfo logEvent, int targe } } - AsyncContinuation continuation = null; + AsyncContinuation? continuation = null; int tryCounter = 0; continuation = ex => { @@ -183,7 +185,7 @@ private AsyncLogEventInfo WrapWithFallback(AsyncLogEventInfo logEvent, int targe { InternalLogger.Warn(ex, "{0}: Target '{1}' failed. Fallback to next: `{2}`", this, Targets[targetToInvoke], Targets[nextTarget]); targetToInvoke = nextTarget; - Targets[targetToInvoke].WriteAsyncLogEvent(logEvent.LogEvent.WithContinuation(continuation)); + Targets[targetToInvoke].WriteAsyncLogEvent(continuation is null ? logEvent : logEvent.LogEvent.WithContinuation(continuation)); } else { diff --git a/src/NLog/Targets/Wrappers/FilteringRule.cs b/src/NLog/Targets/Wrappers/FilteringRule.cs index 07fa97be5e..ac4e7b82a4 100644 --- a/src/NLog/Targets/Wrappers/FilteringRule.cs +++ b/src/NLog/Targets/Wrappers/FilteringRule.cs @@ -33,8 +33,8 @@ namespace NLog.Targets.Wrappers { - using Conditions; - using Config; + using NLog.Conditions; + using NLog.Config; /// /// Filtering rule for . @@ -46,7 +46,6 @@ public class FilteringRule /// Initializes a new instance of the FilteringRule class. /// public FilteringRule() - : this(null, null) { } @@ -65,14 +64,12 @@ public FilteringRule(ConditionExpression whenExistsExpression, ConditionExpressi /// Gets or sets the condition to be tested. /// /// - [RequiredParameter] - public ConditionExpression Exists { get; set; } + public ConditionExpression? Exists { get; set; } /// /// Gets or sets the resulting filter to be applied when the condition matches. /// /// - [RequiredParameter] - public ConditionExpression Filter { get; set; } + public ConditionExpression? Filter { get; set; } } } diff --git a/src/NLog/Targets/Wrappers/FilteringTargetWrapper.cs b/src/NLog/Targets/Wrappers/FilteringTargetWrapper.cs index 8df7c142f8..5f8eaf56bc 100644 --- a/src/NLog/Targets/Wrappers/FilteringTargetWrapper.cs +++ b/src/NLog/Targets/Wrappers/FilteringTargetWrapper.cs @@ -36,7 +36,6 @@ namespace NLog.Targets.Wrappers using System.Collections.Generic; using NLog.Common; using NLog.Conditions; - using NLog.Config; using NLog.Filters; using NLog.Internal; @@ -66,8 +65,8 @@ public class FilteringTargetWrapper : WrapperTargetBase /// Initializes a new instance of the class. /// public FilteringTargetWrapper() - : this(null, null) { + Filter = ConditionBasedFilter.Empty; } /// @@ -89,25 +88,35 @@ public FilteringTargetWrapper(string name, Target wrappedTarget, ConditionExpres /// The condition. public FilteringTargetWrapper(Target wrappedTarget, ConditionExpression condition) { - Name = string.IsNullOrEmpty(wrappedTarget?.Name) ? Name : (wrappedTarget.Name + "_wrapper"); + Name = (wrappedTarget is null || string.IsNullOrEmpty(wrappedTarget.Name)) ? Name : (wrappedTarget.Name + "_wrapper"); WrappedTarget = wrappedTarget; - Condition = condition; + Filter = CreateFilter(condition); } /// /// Gets or sets the condition expression. Log events who meet this condition will be forwarded /// to the wrapped target. /// + /// Default: /// - public ConditionExpression Condition { get => (Filter as ConditionBasedFilter)?.Condition; set => Filter = CreateFilter(value); } + public ConditionExpression? Condition { get => (Filter as ConditionBasedFilter)?.Condition; set => Filter = CreateFilter(value); } /// /// Gets or sets the filter. Log events who evaluates to will be discarded /// + /// [Required] Default: /// - [RequiredParameter] public Filter Filter { get; set; } + /// + protected override void InitializeTarget() + { + if (Filter is null || ReferenceEquals(Filter, ConditionBasedFilter.Empty)) + throw new NLogConfigurationException($"FilteringTargetWrapper Filter-property must be assigned. Filter LogEvents using blank Filter not supported."); + + base.InitializeTarget(); + } + /// /// Checks the condition against the passed log event. /// If the condition is met, the log event is forwarded to @@ -118,7 +127,7 @@ protected override void Write(AsyncLogEventInfo logEvent) { if (ShouldLogEvent(logEvent, Filter)) { - WrappedTarget.WriteAsyncLogEvent(logEvent); + WrappedTarget?.WriteAsyncLogEvent(logEvent); } } @@ -128,7 +137,7 @@ protected override void Write(IList logEvents) var filterLogEvents = logEvents.Filter(Filter, (logEvent, filter) => ShouldLogEvent(logEvent, filter)); if (filterLogEvents.Count > 0) { - WrappedTarget.WriteAsyncLogEvents(filterLogEvents); + WrappedTarget?.WriteAsyncLogEvents(filterLogEvents); } } @@ -146,12 +155,11 @@ private static bool ShouldLogEvent(AsyncLogEventInfo logEvent, Filter filter) } } - private static ConditionBasedFilter CreateFilter(ConditionExpression value) + private static ConditionBasedFilter CreateFilter(ConditionExpression? value) { if (value is null) - { - return null; - } + return ConditionBasedFilter.Empty; + return new ConditionBasedFilter { Condition = value, FilterDefaultAction = FilterResult.Ignore }; } } diff --git a/src/NLog/Targets/Wrappers/GroupByTargetWrapper.cs b/src/NLog/Targets/Wrappers/GroupByTargetWrapper.cs index 9dcddce0e7..96c2a153f9 100644 --- a/src/NLog/Targets/Wrappers/GroupByTargetWrapper.cs +++ b/src/NLog/Targets/Wrappers/GroupByTargetWrapper.cs @@ -35,7 +35,6 @@ namespace NLog.Targets.Wrappers { using System.Collections.Generic; using NLog.Common; - using NLog.Config; using NLog.Internal; using NLog.Layouts; @@ -49,19 +48,20 @@ namespace NLog.Targets.Wrappers [Target("GroupByWrapper", IsWrapper = true)] public class GroupByTargetWrapper : WrapperTargetBase { - SortHelpers.KeySelector _buildKeyStringDelegate; + private readonly SortHelpers.KeySelector _buildKeyStringDelegate; /// /// Identifier to perform group-by /// - [RequiredParameter] - public Layout Key { get; set; } + /// [Required] Default: + public Layout Key { get; set; } = Layout.Empty; /// /// Initializes a new instance of the class. /// - public GroupByTargetWrapper() : this(null) + public GroupByTargetWrapper() { + _buildKeyStringDelegate = logEvent => RenderLogEvent(Key, logEvent.LogEvent); } /// @@ -69,7 +69,7 @@ public GroupByTargetWrapper() : this(null) /// /// The wrapped target. public GroupByTargetWrapper(Target wrappedTarget) - : this(string.IsNullOrEmpty(wrappedTarget?.Name) ? null : (wrappedTarget.Name + "_wrapper"), wrappedTarget) + : this(string.Empty, wrappedTarget) { } @@ -79,7 +79,7 @@ public GroupByTargetWrapper(Target wrappedTarget) /// The name of the target. /// The wrapped target. public GroupByTargetWrapper(string name, Target wrappedTarget) - : this(name, wrappedTarget, string.Empty) + : this(name, wrappedTarget, Layout.Empty) { } @@ -90,28 +90,35 @@ public GroupByTargetWrapper(string name, Target wrappedTarget) /// The wrapped target. /// Group by identifier. public GroupByTargetWrapper(string name, Target wrappedTarget, Layout key) + : this() { - Name = name; + Name = (!string.IsNullOrEmpty(name) || wrappedTarget is null || string.IsNullOrEmpty(wrappedTarget.Name)) ? name : (wrappedTarget.Name + "_wrapper"); WrappedTarget = wrappedTarget; Key = key; } + /// + protected override void InitializeTarget() + { + if (Key is null || ReferenceEquals(Key, Layout.Empty)) + throw new NLogConfigurationException($"GroupByTargetWrapper Key-property must be assigned. Group LogEvents using blank Key not supported."); + + base.InitializeTarget(); + } + /// protected override void Write(AsyncLogEventInfo logEvent) { - WrappedTarget.WriteAsyncLogEvent(logEvent); + WrappedTarget?.WriteAsyncLogEvent(logEvent); } /// protected override void Write(IList logEvents) { - if (_buildKeyStringDelegate is null) - _buildKeyStringDelegate = logEvent => RenderLogEvent(Key, logEvent.LogEvent); - var buckets = logEvents.BucketSort(_buildKeyStringDelegate); foreach (var bucket in buckets) { - WrappedTarget.WriteAsyncLogEvents(bucket.Value); + WrappedTarget?.WriteAsyncLogEvents(bucket.Value); } } } diff --git a/src/NLog/Targets/Wrappers/LimitingTargetWrapper.cs b/src/NLog/Targets/Wrappers/LimitingTargetWrapper.cs index 12908ac918..4a21295ec1 100644 --- a/src/NLog/Targets/Wrappers/LimitingTargetWrapper.cs +++ b/src/NLog/Targets/Wrappers/LimitingTargetWrapper.cs @@ -52,11 +52,12 @@ public class LimitingTargetWrapper : WrapperTargetBase /// /// Initializes a new instance of the class. /// - public LimitingTargetWrapper() : this(null) + public LimitingTargetWrapper() { + MessageLimit = 1000; + Interval = TimeSpan.FromHours(1); } - /// /// Initializes a new instance of the class. /// @@ -85,7 +86,7 @@ public LimitingTargetWrapper(Target wrappedTarget) /// Interval in which the maximum number of messages can be written. public LimitingTargetWrapper(Target wrappedTarget, int messageLimit, TimeSpan interval) { - Name = string.IsNullOrEmpty(wrappedTarget?.Name) ? Name : (wrappedTarget.Name + "_wrapper"); + Name = (wrappedTarget is null || string.IsNullOrEmpty(wrappedTarget.Name)) ? Name : (wrappedTarget.Name + "_wrapper"); WrappedTarget = wrappedTarget; MessageLimit = messageLimit; Interval = interval; @@ -94,18 +95,14 @@ public LimitingTargetWrapper(Target wrappedTarget, int messageLimit, TimeSpan in /// /// Gets or sets the maximum allowed number of messages written per . /// - /// - /// Messages received after has been reached in the current will be discarded. - /// + /// Default: . Messages received after has been reached in the current will be discarded. /// public Layout MessageLimit { get; set; } /// /// Gets or sets the interval in which messages will be written up to the number of messages. /// - /// - /// Messages received after has been reached in the current will be discarded. - /// + /// Default: hour. Messages received after has been reached in the current will be discarded. /// public Layout Interval { get; set; } @@ -149,7 +146,7 @@ protected override void Write(AsyncLogEventInfo logEvent) if (messageLimit <= 0 || MessagesWrittenCount < messageLimit) { - WrappedTarget.WriteAsyncLogEvent(logEvent); + WrappedTarget?.WriteAsyncLogEvent(logEvent); MessagesWrittenCount++; } else diff --git a/src/NLog/Targets/Wrappers/LogEventDroppedEventArgs.cs b/src/NLog/Targets/Wrappers/LogEventDroppedEventArgs.cs index f914c5fd28..9312402ef7 100644 --- a/src/NLog/Targets/Wrappers/LogEventDroppedEventArgs.cs +++ b/src/NLog/Targets/Wrappers/LogEventDroppedEventArgs.cs @@ -31,10 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; - namespace NLog.Targets.Wrappers { + using System; + /// /// Arguments for events. /// diff --git a/src/NLog/Targets/Wrappers/PostFilteringTargetWrapper.cs b/src/NLog/Targets/Wrappers/PostFilteringTargetWrapper.cs index 4395a0627c..3ee07ec2cc 100644 --- a/src/NLog/Targets/Wrappers/PostFilteringTargetWrapper.cs +++ b/src/NLog/Targets/Wrappers/PostFilteringTargetWrapper.cs @@ -69,7 +69,6 @@ public class PostFilteringTargetWrapper : WrapperTargetBase /// Initializes a new instance of the class. /// public PostFilteringTargetWrapper() - : this(null) { } @@ -77,7 +76,7 @@ public PostFilteringTargetWrapper() /// Initializes a new instance of the class. /// public PostFilteringTargetWrapper(Target wrappedTarget) - : this(string.IsNullOrEmpty(wrappedTarget?.Name) ? null : (wrappedTarget.Name + "_wrapper"), wrappedTarget) + : this(string.Empty, wrappedTarget) { } @@ -88,15 +87,16 @@ public PostFilteringTargetWrapper(Target wrappedTarget) /// The wrapped target. public PostFilteringTargetWrapper(string name, Target wrappedTarget) { - Name = name ?? Name; + Name = (!string.IsNullOrEmpty(name) || wrappedTarget is null || string.IsNullOrEmpty(wrappedTarget.Name)) ? name : (wrappedTarget.Name + "_wrapper"); WrappedTarget = wrappedTarget; } /// /// Gets or sets the default filter to be applied when no specific rule matches. /// + /// Default: /// - public ConditionExpression DefaultFilter { get; set; } + public ConditionExpression? DefaultFilter { get; set; } /// /// Gets the collection of filtering rules. The rules are processed top-down @@ -107,6 +107,21 @@ public PostFilteringTargetWrapper(string name, Target wrappedTarget) [ArrayParameter(typeof(FilteringRule), "when")] public IList Rules { get; } = new List(); + /// + protected override void InitializeTarget() + { + base.InitializeTarget(); + + foreach (var rule in Rules) + { + if (rule.Exists is null) + throw new NLogConfigurationException("PostFilteringTargetWrapper When-Rules with unassigned Exists-property."); + + if (rule.Filter is null) + throw new NLogConfigurationException("PostFilteringTargetWrapper When-Rules with unassigned Filter-property."); + } + } + /// protected override void Write(AsyncLogEventInfo logEvent) { @@ -124,10 +139,10 @@ protected override void Write(IList logEvents) { InternalLogger.Trace("{0}: Running on {1} events", this, logEvents.Count); - var resultFilter = EvaluateAllRules(logEvents) ?? DefaultFilter; + var resultFilter = EvaluateAllRules(logEvents); if (resultFilter is null) { - WrappedTarget.WriteAsyncLogEvents(logEvents); + WrappedTarget?.WriteAsyncLogEvents(logEvents); } else { @@ -137,14 +152,14 @@ protected override void Write(IList logEvents) if (resultBuffer.Count > 0) { InternalLogger.Trace("{0}: Sending to {1}", this, WrappedTarget); - WrappedTarget.WriteAsyncLogEvents(resultBuffer); + WrappedTarget?.WriteAsyncLogEvents(resultBuffer); } } } private static bool ShouldLogEvent(AsyncLogEventInfo logEvent, ConditionExpression resultFilter) { - object v = resultFilter.Evaluate(logEvent.LogEvent); + var v = resultFilter.Evaluate(logEvent.LogEvent); if (ConditionExpression.BoxedTrue.Equals(v)) { return true; @@ -161,17 +176,17 @@ private static bool ShouldLogEvent(AsyncLogEventInfo logEvent, ConditionExpressi /// /// /// - private ConditionExpression EvaluateAllRules(IList logEvents) + private ConditionExpression? EvaluateAllRules(IList logEvents) { if (Rules.Count == 0) - return null; + return DefaultFilter; for (int i = 0; i < logEvents.Count; ++i) { for (int j = 0; j < Rules.Count; ++j) { var rule = Rules[j]; - object v = rule.Exists.Evaluate(logEvents[i].LogEvent); + var v = rule.Exists?.Evaluate(logEvents[i].LogEvent); if (ConditionExpression.BoxedTrue.Equals(v)) { InternalLogger.Trace("{0}: Rule matched: {1}", this, rule.Exists); @@ -180,7 +195,7 @@ private ConditionExpression EvaluateAllRules(IList logEvents) } } - return null; + return DefaultFilter; } } } diff --git a/src/NLog/Targets/Wrappers/RepeatingTargetWrapper.cs b/src/NLog/Targets/Wrappers/RepeatingTargetWrapper.cs index 8da51aa6bb..c1962f4904 100644 --- a/src/NLog/Targets/Wrappers/RepeatingTargetWrapper.cs +++ b/src/NLog/Targets/Wrappers/RepeatingTargetWrapper.cs @@ -61,7 +61,6 @@ public class RepeatingTargetWrapper : WrapperTargetBase /// Initializes a new instance of the class. /// public RepeatingTargetWrapper() - : this(null, 3) { } @@ -84,6 +83,7 @@ public RepeatingTargetWrapper(string name, Target wrappedTarget, int repeatCount /// The repeat count. public RepeatingTargetWrapper(Target wrappedTarget, int repeatCount) { + Name = (wrappedTarget is null || string.IsNullOrEmpty(wrappedTarget.Name)) ? Name : (wrappedTarget.Name + "_wrapper"); WrappedTarget = wrappedTarget; RepeatCount = repeatCount; } @@ -91,8 +91,9 @@ public RepeatingTargetWrapper(Target wrappedTarget, int repeatCount) /// /// Gets or sets the number of times to repeat each log message. /// + /// Default: /// - public int RepeatCount { get; set; } + public int RepeatCount { get; set; } = 3; /// /// Forwards the log message to the by calling the method times. @@ -100,7 +101,7 @@ public RepeatingTargetWrapper(Target wrappedTarget, int repeatCount) /// The log event. protected override void Write(AsyncLogEventInfo logEvent) { - AsyncHelpers.Repeat(RepeatCount, logEvent.Continuation, cont => WrappedTarget.WriteAsyncLogEvent(logEvent.LogEvent.WithContinuation(cont))); + AsyncHelpers.Repeat(RepeatCount, logEvent.Continuation, cont => WrappedTarget?.WriteAsyncLogEvent(logEvent.LogEvent.WithContinuation(cont))); } } } diff --git a/src/NLog/Targets/Wrappers/RetryingTargetWrapper.cs b/src/NLog/Targets/Wrappers/RetryingTargetWrapper.cs index f8dcfae821..53834b4d54 100644 --- a/src/NLog/Targets/Wrappers/RetryingTargetWrapper.cs +++ b/src/NLog/Targets/Wrappers/RetryingTargetWrapper.cs @@ -66,8 +66,9 @@ public class RetryingTargetWrapper : WrapperTargetBase /// Initializes a new instance of the class. /// public RetryingTargetWrapper() - : this(null, 3, 100) { + RetryCount = 3; + RetryDelayMilliseconds = 100; } /// @@ -91,7 +92,7 @@ public RetryingTargetWrapper(string name, Target wrappedTarget, int retryCount, /// The retry delay milliseconds. public RetryingTargetWrapper(Target wrappedTarget, int retryCount, int retryDelayMilliseconds) { - Name = string.IsNullOrEmpty(wrappedTarget?.Name) ? Name : (wrappedTarget.Name + "_wrapper"); + Name = (wrappedTarget is null || string.IsNullOrEmpty(wrappedTarget.Name)) ? Name : (wrappedTarget.Name + "_wrapper"); WrappedTarget = wrappedTarget; RetryCount = retryCount; RetryDelayMilliseconds = retryDelayMilliseconds; @@ -100,18 +101,21 @@ public RetryingTargetWrapper(Target wrappedTarget, int retryCount, int retryDela /// /// Gets or sets the number of retries that should be attempted on the wrapped target in case of a failure. /// + /// Default: /// public Layout RetryCount { get; set; } /// /// Gets or sets the time to wait between retries in milliseconds. /// + /// Default: ms /// public Layout RetryDelayMilliseconds { get; set; } /// /// Gets or sets whether to enable batching, and only apply single delay when a whole batch fails /// + /// Default: /// public bool EnableBatchWrite { get; set; } = true; @@ -141,7 +145,7 @@ protected override void WriteAsyncThreadSafe(IList logEvents) lock (_retrySyncObject) { - WrappedTarget.WriteAsyncLogEvents(logEvents); + WrappedTarget?.WriteAsyncLogEvents(logEvents); } } else @@ -175,12 +179,12 @@ protected override void WriteAsyncThreadSafe(AsyncLogEventInfo logEvent) /// The log event. protected override void Write(AsyncLogEventInfo logEvent) { - WrappedTarget.WriteAsyncLogEvent(WrapWithRetry(logEvent, (retryNumber) => true)); + WrappedTarget?.WriteAsyncLogEvent(WrapWithRetry(logEvent, (retryNumber) => true)); } private AsyncLogEventInfo WrapWithRetry(AsyncLogEventInfo logEvent, Func sleepBeforeRetry) { - AsyncContinuation continuation = null; + AsyncContinuation? continuation = null; int counter = 0; continuation = ex => @@ -221,7 +225,7 @@ private AsyncLogEventInfo WrapWithRetry(AsyncLogEventInfo logEvent, Func /// Base class for targets wrap other (single) targets. @@ -45,9 +44,9 @@ public abstract class WrapperTargetBase : Target /// /// Gets or sets the target that is wrapped by this target. /// + /// [Required] Default: /// - [RequiredParameter] - public Target WrappedTarget + public Target? WrappedTarget { get => _wrappedTarget; set @@ -56,7 +55,7 @@ public Target WrappedTarget _tostring = null; } } - private Target _wrappedTarget; + private Target? _wrappedTarget; /// public override string ToString() @@ -69,9 +68,9 @@ private string GenerateTargetToString() if (WrappedTarget is null) return GenerateTargetToString(true); else if (string.IsNullOrEmpty(Name)) - return $"{GenerateTargetToString(true, "")}_{WrappedTarget}"; + return $"{GenerateTargetToString(true, string.Empty)}_{WrappedTarget}"; else - return $"{GenerateTargetToString(true, "")}_{WrappedTarget.GenerateTargetToString(false, Name)}"; + return $"{GenerateTargetToString(true, string.Empty)}_{WrappedTarget.GenerateTargetToString(false, Name)}"; } /// @@ -83,6 +82,15 @@ protected override void FlushAsync(AsyncContinuation asyncContinuation) WrappedTarget.Flush(asyncContinuation); } + /// + protected override void InitializeTarget() + { + if (WrappedTarget is null) + throw new NLogConfigurationException($"{GetType().Name}(Name={Name}): No wrapped Target configured."); + + base.InitializeTarget(); + } + /// /// Writes logging event to the log target. Must be overridden in inheriting /// classes. diff --git a/src/NLog/Time/AccurateLocalTimeSource.cs b/src/NLog/Time/AccurateLocalTimeSource.cs index 434444b44d..9a74fb8659 100644 --- a/src/NLog/Time/AccurateLocalTimeSource.cs +++ b/src/NLog/Time/AccurateLocalTimeSource.cs @@ -38,8 +38,12 @@ namespace NLog.Time /// /// Current local time retrieved directly from DateTime.Now. /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki [TimeSource("AccurateLocal")] - public class AccurateLocalTimeSource : TimeSource + public sealed class AccurateLocalTimeSource : TimeSource { /// /// Gets current local time directly from DateTime.Now. diff --git a/src/NLog/Time/AccurateUtcTimeSource.cs b/src/NLog/Time/AccurateUtcTimeSource.cs index 40abb67135..161cbfe628 100644 --- a/src/NLog/Time/AccurateUtcTimeSource.cs +++ b/src/NLog/Time/AccurateUtcTimeSource.cs @@ -38,8 +38,12 @@ namespace NLog.Time /// /// Current UTC time retrieved directly from DateTime.UtcNow. /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki [TimeSource("AccurateUTC")] - public class AccurateUtcTimeSource : TimeSource + public sealed class AccurateUtcTimeSource : TimeSource { /// /// Gets current UTC time directly from DateTime.UtcNow. diff --git a/src/NLog/Time/CachedTimeSource.cs b/src/NLog/Time/CachedTimeSource.cs index c0f397fafe..6b2e35423e 100644 --- a/src/NLog/Time/CachedTimeSource.cs +++ b/src/NLog/Time/CachedTimeSource.cs @@ -38,6 +38,10 @@ namespace NLog.Time /// /// Fast time source that updates current time only once per tick (15.6 milliseconds). /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki public abstract class CachedTimeSource : TimeSource { private int _lastTicks = -1; @@ -64,9 +68,7 @@ private DateTime RetrieveFreshTime(int tickCount) { var freshTime = FreshTime; _lastTime = freshTime; // Assignment of 64 bit value is safe, also when 32bit Intel x86 that uses FPU-registers without tearing -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 System.Threading.Thread.MemoryBarrier(); // Make sure that the value written to _lastTime is visible to other threads -#endif _lastTicks = tickCount; return freshTime; } diff --git a/src/NLog/Time/FastLocalTimeSource.cs b/src/NLog/Time/FastLocalTimeSource.cs index 16fe590a9a..0e44cf4be4 100644 --- a/src/NLog/Time/FastLocalTimeSource.cs +++ b/src/NLog/Time/FastLocalTimeSource.cs @@ -38,8 +38,12 @@ namespace NLog.Time /// /// Fast local time source that is updated once per tick (15.6 milliseconds). /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki [TimeSource("FastLocal")] - public class FastLocalTimeSource : CachedTimeSource + public sealed class FastLocalTimeSource : CachedTimeSource { /// /// Gets uncached local time directly from DateTime.Now. diff --git a/src/NLog/Time/FastUtcTimeSource.cs b/src/NLog/Time/FastUtcTimeSource.cs index 4dc04626fe..deabaae891 100644 --- a/src/NLog/Time/FastUtcTimeSource.cs +++ b/src/NLog/Time/FastUtcTimeSource.cs @@ -38,8 +38,12 @@ namespace NLog.Time /// /// Fast UTC time source that is updated once per tick (15.6 milliseconds). /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki [TimeSource("FastUTC")] - public class FastUtcTimeSource : CachedTimeSource + public sealed class FastUtcTimeSource : CachedTimeSource { /// /// Gets uncached UTC time directly from DateTime.UtcNow. diff --git a/src/NLog/Time/TimeSource.cs b/src/NLog/Time/TimeSource.cs index de54719fbc..87ff248585 100644 --- a/src/NLog/Time/TimeSource.cs +++ b/src/NLog/Time/TimeSource.cs @@ -34,12 +34,16 @@ namespace NLog.Time { using System; - using Config; - using Internal; + using NLog.Config; + using NLog.Internal; /// /// Defines source of current time. /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki [NLogConfigurationItem] public abstract class TimeSource { diff --git a/src/NLog/Time/TimeSourceAttribute.cs b/src/NLog/Time/TimeSourceAttribute.cs index ca9d19ffda..c4ac5901f5 100644 --- a/src/NLog/Time/TimeSourceAttribute.cs +++ b/src/NLog/Time/TimeSourceAttribute.cs @@ -34,7 +34,7 @@ namespace NLog.Time { using System; - using Config; + using NLog.Config; /// /// Marks class as a time source and assigns a name to it. diff --git a/src/NuGet/NLog.Config/content/NLog.config b/src/NuGet/NLog.Config/content/NLog.config index 7a0f3aa130..b635084d60 100644 --- a/src/NuGet/NLog.Config/content/NLog.config +++ b/src/NuGet/NLog.Config/content/NLog.config @@ -1,6 +1,5 @@ - NLog.Schema - NLog Schema for Intellisense(TM) $BuildVersion$ Kim Christensen, Julian Verdurmen Kim Christenen, Julian Verdurmen false - Enables Intellisense(TM) when editing NLog.config. - XSD file to enables Intellisense(TM) when editing NLog configuration files, for example in Visual Studio. + NLog Schema for Intellisense + Enables Intellisense when editing NLog.config XML file + NLog Schema XSD-file for enabling Intellisense, when editing NLog.config XML file in Visual Studio. + +The nuget-package will try to copy the XSD-file into the project-folder, so one can reference NLog.xsd from NLog.config: + + <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"> + </nlog> -Notes: -- Extensions could report XSD errors. Those errors could be ignored safely. -- Unfortunately this package won't work well when using <PackageReference>. You could download the lastest XSD from here: https://nlog-project.org/schemas/NLog.xsd +If this nuget-package fails to place the XSD-file into the project-folder then one can download latest here: https://nlog-project.org/schemas/NLog.xsd + +Alternative enable the Visual Studio option "Automatically download DTDs and schemas" and specify the complete URL: + + <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd http://www.nlog-project.org/schemas/NLog.xsd"> + </nlog> NLog $BuildVersion$ en-US @@ -27,6 +39,7 @@ Notes: + diff --git a/src/NuGet/NLog.Schema/NLog.Schema.targets b/src/NuGet/NLog.Schema/NLog.Schema.targets new file mode 100644 index 0000000000..364205594a --- /dev/null +++ b/src/NuGet/NLog.Schema/NLog.Schema.targets @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/tests/NLog.Database.Tests/DatabaseTargetTests.cs b/tests/NLog.Database.Tests/DatabaseTargetTests.cs index 44b6bb8b46..31007f087c 100644 --- a/tests/NLog.Database.Tests/DatabaseTargetTests.cs +++ b/tests/NLog.Database.Tests/DatabaseTargetTests.cs @@ -36,13 +36,14 @@ namespace NLog.Database.Tests using System; using System.Collections; using System.Collections.Generic; -#if !NETSTANDARD +#if NETFRAMEWORK using System.Configuration; #endif using System.Data; using System.Data.Common; using System.Globalization; using System.IO; + using System.Linq; using NLog.Config; using NLog.Targets; using Xunit; @@ -50,12 +51,12 @@ namespace NLog.Database.Tests #if MONO using Mono.Data.Sqlite; using System.Data.SqlClient; -#elif NETSTANDARD - using Microsoft.Data.SqlClient; - using Microsoft.Data.Sqlite; -#else +#elif NETFRAMEWORK using System.Data.SqlClient; using System.Data.SQLite; +#else + using Microsoft.Data.SqlClient; + using Microsoft.Data.Sqlite; #endif public class DatabaseTargetTests @@ -65,7 +66,7 @@ public DatabaseTargetTests() LogManager.ThrowExceptions = true; } -#if !NETSTANDARD +#if NETFRAMEWORK static DatabaseTargetTests() { var data = (DataSet)ConfigurationManager.GetSection("system.data"); @@ -538,10 +539,9 @@ public void LevelParameterTest(DbType? dbType, bool noRawValue, string expectedV string lvlLayout = noRawValue ? "${level:format=Ordinal:norawvalue=true}" : "${level:format=Ordinal}"; MockDbConnection.ClearLog(); - DatabaseTarget dt = new DatabaseTarget() + DatabaseTarget dt = new DatabaseTarget(() => new MockDbConnection("A")) { CommandText = "INSERT INTO FooBar VALUES(@lvl, @msg)", - DBProvider = typeof(MockDbConnection).AssemblyQualifiedName, KeepConnection = true, Parameters = { @@ -552,8 +552,6 @@ public void LevelParameterTest(DbType? dbType, bool noRawValue, string expectedV var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.Configuration.AddRuleForAllLevels(dt)).LogFactory; - Assert.Same(typeof(MockDbConnection), dt.ConnectionType); - List exceptions = new List(); var events = new[] { @@ -566,7 +564,7 @@ public void LevelParameterTest(DbType? dbType, bool noRawValue, string expectedV Assert.Null(ex); } - string expectedLog = string.Format(@"Open('Server=.;Trusted_Connection=SSPI;'). + string expectedLog = string.Format(@"Open('A'). CreateParameter(0) Parameter #0 Direction=Input Parameter #0 Name=lvl{0} @@ -924,34 +922,38 @@ Add Parameter Parameter #2 [Fact] public void ConnectionStringBuilderTest1() { - DatabaseTarget dt; + var dbProvider = typeof(MockDbConnection).AssemblyQualifiedName; + Assert.NotNull(dbProvider); - dt = new DatabaseTarget(); + DatabaseTarget dt = CreateTargetWithProvider(); Assert.Equal("Server=.;Trusted_Connection=SSPI;", GetConnectionString(dt)); - dt = new DatabaseTarget(); + dt = CreateTargetWithProvider(); dt.DBHost = "${logger}"; Assert.Equal("Server=Logger1;Trusted_Connection=SSPI;", GetConnectionString(dt)); - dt = new DatabaseTarget(); + dt = CreateTargetWithProvider(); dt.DBHost = "HOST1"; dt.DBDatabase = "${logger}"; Assert.Equal("Server=HOST1;Trusted_Connection=SSPI;Database=Logger1", GetConnectionString(dt)); - dt = new DatabaseTarget(); + dt = CreateTargetWithProvider(); dt.DBHost = "HOST1"; dt.DBDatabase = "${logger}"; dt.DBUserName = "user1"; dt.DBPassword = "password1"; Assert.Equal("Server=HOST1;User id=user1;Password=password1;Database=Logger1", GetConnectionString(dt)); - dt = new DatabaseTarget(); + dt = CreateTargetWithProvider(); dt.ConnectionString = "customConnectionString42"; dt.DBHost = "HOST1"; dt.DBDatabase = "${logger}"; dt.DBUserName = "user1"; dt.DBPassword = "password1"; Assert.Equal("customConnectionString42", GetConnectionString(dt)); + + // local function to create a target with the provider set + DatabaseTarget CreateTargetWithProvider() => new DatabaseTarget { DBProvider = dbProvider }; } [Fact] @@ -1191,7 +1193,7 @@ public void DatabaseExceptionTest3() AssertLog(expectedLog); } -#if !MONO && !NETSTANDARD +#if !MONO && NETFRAMEWORK [Fact] public void ConnectionStringNameInitTest() { @@ -1339,7 +1341,7 @@ public void SqlServerShorthandNotationTest() } } -#if !NETSTANDARD +#if NETFRAMEWORK [Fact] public void OleDbShorthandNotationTest() { @@ -1440,10 +1442,10 @@ private static string GetSQLiteDbProvider() { #if MONO return "Mono.Data.Sqlite.SqliteConnection, Mono.Data.Sqlite"; -#elif NETSTANDARD - return "Microsoft.Data.Sqlite.SqliteConnection, Microsoft.Data.Sqlite"; -#else +#elif NETFRAMEWORK return "System.Data.SQLite.SQLiteConnection, System.Data.SQLite"; +#else + return "Microsoft.Data.Sqlite.SqliteConnection, Microsoft.Data.Sqlite"; #endif } @@ -1638,10 +1640,10 @@ private static LogFactory SetupSqliteConfigWithInvalidInstallCommand(string data // Use an in memory SQLite database // See https://www.sqlite.org/inmemorydb.html -#if NETSTANDARD - var connectionString = "Data Source=:memory:"; -#else +#if NETFRAMEWORK var connectionString = "Uri=file::memory:;Version=3"; +#else + var connectionString = "Data Source=:memory:"; #endif return new LogFactory().Setup().SetupExtensions(ext => ext.RegisterAssembly(typeof(DatabaseTarget).Assembly)).LoadConfigurationFromXml(String.Format(nlogXmlConfig, GetSQLiteDbProvider(), connectionString)).LogFactory; @@ -1685,10 +1687,10 @@ public void RethrowingInstallExceptions() Assert.True(context.ThrowExceptions); // Sanity check -#if MONO || NETSTANDARD - Assert.Throws(() => logFactory.Configuration.Install(context)); -#else +#if NETFRAMEWORK && !MONO Assert.Throws(() => logFactory.Configuration.Install(context)); +#else + Assert.Throws(() => logFactory.Configuration.Install(context)); #endif } finally @@ -1707,14 +1709,13 @@ public void SqlServer_NoTargetInstallException() } bool isAppVeyor = IsAppVeyor(); - SqlServerTest.TryDropDatabase(isAppVeyor); try { - SqlServerTest.CreateDatabase(isAppVeyor); - var connectionString = SqlServerTest.GetConnectionString(isAppVeyor); + CreateDatabaseWithRetry(isAppVeyor); + DatabaseTarget testTarget = new DatabaseTarget("TestDbTarget"); testTarget.ConnectionString = connectionString; @@ -1759,13 +1760,12 @@ public void SqlServer_InstallAndLogMessage() } bool isAppVeyor = IsAppVeyor(); - SqlServerTest.TryDropDatabase(isAppVeyor); try { - SqlServerTest.CreateDatabase(isAppVeyor); + var connectionString = SqlServerTest.GetConnectionString(isAppVeyor); - var connectionString = SqlServerTest.GetConnectionString(IsAppVeyor()); + CreateDatabaseWithRetry(isAppVeyor); var logFactory = new LogFactory().Setup().SetupExtensions(ext => ext.RegisterAssembly(typeof(DatabaseTarget).Assembly)).LoadConfigurationFromXml(@" 1) + { + SqlServerTest.TryDropDatabase(isAppVeyor); + System.Threading.Thread.Sleep(1000); + } + SqlServerTest.CreateDatabase(isAppVeyor); + break; + } + catch + { + if (i >= 3) + throw; + System.Threading.Thread.Sleep(i * 5000); + } + } + } + +#if NETFRAMEWORK [Fact] public void GetProviderNameFromAppConfig() { @@ -1962,15 +1985,133 @@ public void DatabaseConnectionStringViaVariableTest(string password, string expe Assert.Equal(expected, result); } +#if NET8_0_OR_GREATER + public sealed class DbTypeTestData() : TheoryData(Enum.GetValues()); +#else + public sealed class DbTypeTestData : TheoryData + { + public DbTypeTestData() => AddRange(Enum.GetValues(typeof(DbType)).Cast().ToArray()); + } +#endif + [ClassData(typeof(DbTypeTestData))] + [Theory] + public void DbTypeEnumSetterTest(DbType type) + { + // Clear log + MockDbConnection.ClearLog(); + + // Arrange + var con = new MockDbConnection(); + var dt = new DatabaseTarget(() => con) + { + Parameters = + { + new DatabaseParameterInfo("Test", "AOT") { DbTypeEnum = type }, + } + }; + dt.CreateDbCommand(new LogEventInfo(), con); + + // Assert + var log = MockDbConnection.Log; + Assert.Contains($"DbType={type}", log); + } + + [ClassData(typeof(DbTypeTestData))] + [Theory] + public void DbTypeSetterTest(DbType type) + { + // Clear log + MockDbConnection.ClearLog(); + + // Arrange + var con = new MockDbConnection(); + var dt = new DatabaseTarget(() => con) + { + Parameters = + { + new DatabaseParameterInfo("Test", "AOT", p => p.DbType = type) + } + }; + dt.CreateDbCommand(new LogEventInfo(), con); + + // Assert + var log = MockDbConnection.Log; + Assert.Contains($"DbType={type}", log); + } + + + [Fact] + public void DbTypeSetterExceptionTest() + { + // Arrange + var expectedException = new Exception("Test exception"); + var con = new MockDbConnection(); + var dt = new DatabaseTarget(() => con) + { + Name = "db", + Parameters = + { + new DatabaseParameterInfo("Test", "AOT", (p) => throw expectedException) + } + }; + var actualException = Assert.Throws(() => dt.CreateDbCommand(new LogEventInfo(), con)); + + // Assert + Assert.Equal(expectedException, actualException); + } + + + [Fact] + public void DbTypeSetterNullTest() + { + Assert.Throws(() => new DatabaseParameterInfo("Test", "AOT", null)); + } + + [Theory] + [InlineData("A", "A")] + [InlineData("", "")] + [InlineData(null, null)] + public void DbFactoryConnectionStringTest1(string csInConn, string csExpected) + { + // Arrange + var dt = new DatabaseTarget(() => new MockDbConnection(csInConn)); + var cs = GetConnectionString(dt); + + // Assert + Assert.Equal(csExpected, cs); + } + + [Theory] + [InlineData("A", "", "Server=.;Trusted_Connection=SSPI;")] + [InlineData(null, "", "Server=.;Trusted_Connection=SSPI;")] + [InlineData("", "", "Server=.;Trusted_Connection=SSPI;")] + [InlineData("A", null, "A")] + [InlineData("", null, "")] + [InlineData("A", "B", "B")] + [InlineData("", "B", "B")] + [InlineData(null, "B", "B")] + public void DbFactoryConnectionStringTest2(string csInConn, string csInDbTarget, string csExpected) + { + // Arrange + var dt = new DatabaseTarget(() => new MockDbConnection(csInConn)) + { + ConnectionString = csInDbTarget + }; + var cs = GetConnectionString(dt); + + // Assert + Assert.Equal(csExpected, cs); + } + private static void AssertLog(string expectedLog) { Assert.Equal(expectedLog.Replace("\r", ""), MockDbConnection.Log.Replace("\r", "")); } - private string GetConnectionString(DatabaseTarget dt) + private static string GetConnectionString(DatabaseTarget dt) { MockDbConnection.ClearLog(); - dt.DBProvider = typeof(MockDbConnection).AssemblyQualifiedName; + dt.CommandText = "NotImportant"; var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.Configuration.AddRuleForAllLevels(dt)).LogFactory; @@ -2049,6 +2190,7 @@ public void Dispose() public static void ClearLog() { Log = string.Empty; + LastConnectionString = null; } public void AddToLog(string message, params object[] args) @@ -2437,10 +2579,10 @@ private sealed class SQLiteTest public SQLiteTest(string dbName) { this.dbName = dbName; -#if NETSTANDARD - connectionString = "Data Source=" + this.dbName; -#else +#if NETFRAMEWORK connectionString = "Data Source=" + this.dbName + ";Version=3;"; +#else + connectionString = "Data Source=" + this.dbName; #endif } @@ -2508,32 +2650,32 @@ private static class SQLiteHandler { public static void CreateDatabase(string dbName) { -#if NETSTANDARD - // Using ConnectionString Mode=ReadWriteCreate -#elif MONO +#if MONO SqliteConnection.CreateFile(dbName); -#else +#elif NETFRAMEWORK SQLiteConnection.CreateFile(dbName); +#else + // Using ConnectionString Mode=ReadWriteCreate #endif } public static DbConnection GetConnection(string connectionString) { -#if NETSTANDARD - return new SqliteConnection(connectionString + ";Mode=ReadWriteCreate;"); -#elif MONO +#if MONO return new SqliteConnection(connectionString); -#else +#elif NETFRAMEWORK return new SQLiteConnection(connectionString); +#else + return new SqliteConnection(connectionString + ";Mode=ReadWriteCreate;"); #endif } public static DbCommand CreateCommand(string commandString, DbConnection connection) { -#if MONO || NETSTANDARD - return new SqliteCommand(commandString, (SqliteConnection)connection); -#else +#if NETFRAMEWORK && !MONO return new SQLiteCommand(commandString, (SQLiteConnection)connection); +#else + return new SqliteCommand(commandString, (SqliteConnection)connection); #endif } } @@ -2547,7 +2689,7 @@ static SqlServerTest() public static string GetConnectionString(bool isAppVeyor) { string connectionString = string.Empty; -#if !NETSTANDARD +#if NETFRAMEWORK connectionString = ConfigurationManager.AppSettings["SqlServerTestConnectionString"]; #endif if (String.IsNullOrWhiteSpace(connectionString)) @@ -2578,25 +2720,16 @@ public static void CreateDatabase(bool isAppVeyor) IssueCommand(IsAppVeyor(), "CREATE DATABASE NLogTest", connectionString); } - public static bool NLogTestDatabaseExists(bool isAppVeyor) - { - var connectionString = GetMasterConnectionString(isAppVeyor); - var dbId = IssueScalarQuery(IsAppVeyor(), "select db_id('NLogTest')", connectionString); - return dbId != null && dbId != DBNull.Value; - } - private static string GetMasterConnectionString(bool isAppVeyor) { return isAppVeyor ? AppVeyorConnectionStringMaster : LocalConnectionStringMaster; } - public static void IssueCommand(bool isAppVeyor, string commandString, string connectionString = null) + public static void IssueCommand(bool isAppVeyor, string commandString, string connectionString) { using (var connection = new SqlConnection(connectionString ?? GetConnectionString(isAppVeyor))) { connection.Open(); - if (connectionString is null) - connection.ChangeDatabase("NLogTest"); using (var command = new SqlCommand(commandString, connection)) { command.ExecuteNonQuery(); @@ -2626,15 +2759,11 @@ public static bool TryDropDatabase(bool isAppVeyor) { try { - if (NLogTestDatabaseExists(isAppVeyor)) - { - var connectionString = GetMasterConnectionString(isAppVeyor); - IssueCommand(isAppVeyor, - "ALTER DATABASE [NLogTest] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE NLogTest;", - connectionString); - return true; - } - return false; + var connectionString = GetMasterConnectionString(isAppVeyor); + IssueCommand(isAppVeyor, + "ALTER DATABASE [NLogTest] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE NLogTest;", + connectionString); + return true; } catch (Exception) { diff --git a/tests/NLog.Database.Tests/NLog.Database.Tests.csproj b/tests/NLog.Database.Tests/NLog.Database.Tests.csproj index 888b88ceca..973edce5b2 100644 --- a/tests/NLog.Database.Tests/NLog.Database.Tests.csproj +++ b/tests/NLog.Database.Tests/NLog.Database.Tests.csproj @@ -3,7 +3,7 @@ 17.0 net462 - net462;net6.0 + net462;net8.0 false @@ -15,10 +15,6 @@ Full - - $(DefineConstants);NETSTANDARD - - $(DefineConstants);MONO @@ -28,8 +24,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -44,7 +40,7 @@ - + diff --git a/tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs b/tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs new file mode 100644 index 0000000000..e8e6d1a40c --- /dev/null +++ b/tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs @@ -0,0 +1,190 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.RegEx.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using NLog.Targets; + using Xunit; + + public class ColoredConsoleTargetTests + { + public ColoredConsoleTargetTests() + { + LogManager.ThrowExceptions = true; + LogManager.Setup().SetupExtensions(ext => ext.RegisterAssembly(typeof(Conditions.RegexConditionMethods).Assembly)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WordHighlightingTextIgnoreCase(bool compileRegex) + { + var target = new ColoredConsoleTarget { Layout = "${logger} ${message}" }; + target.WordHighlightingRules.Add( + new ConsoleWordHighlightingRuleRegex + { + ForegroundColor = ConsoleOutputColor.Red, + Text = "at", + IgnoreCase = true, + CompileRegex = compileRegex + }); + + AssertOutput(target, "The Cat Sat At The Bar.", + new string[] { "The C", "at", " S", "at", " ", "At", " The Bar." }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WordHighlightingTextWholeWords(bool compileRegex) + { + var target = new ColoredConsoleTarget { Layout = "${logger} ${message}" }; + target.WordHighlightingRules.Add( + new ConsoleWordHighlightingRuleRegex + { + ForegroundColor = ConsoleOutputColor.Red, + Text = "at", + WholeWords = true, + CompileRegex = compileRegex + }); + + AssertOutput(target, "The cat sat at the bar.", + new string[] { "The cat sat ", "at", " the bar." }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WordHighlightingRegex(bool compileRegex) + { + var target = new ColoredConsoleTarget { Layout = "${logger} ${message}" }; + target.WordHighlightingRules.Add( + new ConsoleWordHighlightingRuleRegex + { + ForegroundColor = ConsoleOutputColor.Red, + Regex = "\\wat", + CompileRegex = compileRegex + }); + + AssertOutput(target, "The cat sat at the bar.", + new string[] { "The ", "cat", " ", "sat", " at the bar." }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void DonRemoveIfRegexIsEmpty(bool compileRegex) + { + var target = new ColoredConsoleTarget { Layout = "${logger} ${message}" }; + target.WordHighlightingRules.Add( + new ConsoleWordHighlightingRuleRegex + { + ForegroundColor = ConsoleOutputColor.Red, + Text = null, + IgnoreCase = true, + CompileRegex = compileRegex + }); + + AssertOutput(target, "The Cat Sat At The Bar.", + new string[] { "The Cat Sat At The Bar." }); + } + + private static void AssertOutput(Target target, string message, string[] expectedParts, string loggerName = "Logger ") + { + var consoleOutWriter = new PartsWriter(); + TextWriter oldConsoleOutWriter = Console.Out; + Console.SetOut(consoleOutWriter); + + try + { + var exceptions = new List(); + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; + target.WriteAsyncLogEvent(new LogEventInfo(LogLevel.Info, loggerName.Trim(), message).WithContinuation(exceptions.Add)); + logFactory.Shutdown(); + + Assert.Single(exceptions); + Assert.True(exceptions.TrueForAll(e => e is null)); + } + finally + { + Console.SetOut(oldConsoleOutWriter); + } + + var expected = Enumerable.Repeat(loggerName + expectedParts[0], 1).Concat(expectedParts.Skip(1)); + Assert.Equal(expected, consoleOutWriter.Values); + Assert.True(consoleOutWriter.SingleWriteLine); + } + + private sealed class PartsWriter : StringWriter + { + public PartsWriter() + { + Values = new List(); + } + + public List Values { get; private set; } + public bool SingleWriteLine { get; private set; } + + public override void Write(string value) + { + Values.Add(value); + } + + public override void WriteLine(string value) + { + if (SingleWriteLine) + { + Values.Clear(); + throw new InvalidOperationException("Single WriteLine only"); + } + SingleWriteLine = true; + if (!string.IsNullOrEmpty(value)) + Values.Add(value); + } + + public override void WriteLine() + { + if (SingleWriteLine) + { + Values.Clear(); + throw new InvalidOperationException("Single WriteLine only"); + } + SingleWriteLine = true; + } + } + } +} diff --git a/tests/NLog.RegEx.Tests/ConditionEvaluatorTests.cs b/tests/NLog.RegEx.Tests/ConditionEvaluatorTests.cs new file mode 100644 index 0000000000..8c156355d5 --- /dev/null +++ b/tests/NLog.RegEx.Tests/ConditionEvaluatorTests.cs @@ -0,0 +1,85 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.RegEx.Tests +{ + using NLog.Conditions; + using Xunit; + + public class ConditionEvaluatorTests + { + public ConditionEvaluatorTests() + { + LogManager.ThrowExceptions = true; + LogManager.Setup().SetupExtensions(ext => ext.RegisterAssembly(typeof(Conditions.RegexConditionMethods).Assembly)); + } + + [Fact] + public void ConditionMethodsTest() + { + AssertEvaluationResult(true, "regex-matches('foo', '^foo$')"); + AssertEvaluationResult(false, "regex-matches('foo', '^bar$')"); + + //Check that calling with empty string is equivalent with not passing the parameter + AssertEvaluationResult(true, "regex-matches('foo', '^foo$', '')"); + AssertEvaluationResult(false, "regex-matches('foo', '^bar$', '')"); + + //Check that options are parsed correctly + AssertEvaluationResult(true, "regex-matches('Foo', '^foo$', 'ignorecase')"); + AssertEvaluationResult(false, "regex-matches('Foo', '^foo$')"); + AssertEvaluationResult(true, "regex-matches('foo\nbar', '^Foo$', 'ignorecase,multiline')"); + AssertEvaluationResult(false, "regex-matches('foo\nbar', '^Foo$')"); + Assert.Throws(() => AssertEvaluationResult(true, "regex-matches('foo\nbar', '^Foo$', 'ignorecase,nonexistent')")); + } + + private static void AssertEvaluationResult(object expectedResult, string conditionText) + { + ConditionExpression condition = ConditionParser.ParseExpression(conditionText); + LogEventInfo context = CreateWellKnownContext(); + object actualResult = condition.Evaluate(context); + Assert.Equal(expectedResult, actualResult); + } + + private static LogEventInfo CreateWellKnownContext() + { + var context = new LogEventInfo + { + Level = LogLevel.Warn, + Message = "some message", + LoggerName = "MyCompany.Product.Class" + }; + + return context; + } + } +} diff --git a/tests/NLog.RegEx.Tests/NLog.RegEx.Tests.csproj b/tests/NLog.RegEx.Tests/NLog.RegEx.Tests.csproj new file mode 100644 index 0000000000..995d2603ba --- /dev/null +++ b/tests/NLog.RegEx.Tests/NLog.RegEx.Tests.csproj @@ -0,0 +1,26 @@ + + + + 17.0 + net462 + net462;net8.0 + + false + + Full + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/tests/NLog.RegEx.Tests/RegexReplaceTests.cs b/tests/NLog.RegEx.Tests/RegexReplaceTests.cs new file mode 100644 index 0000000000..c46667cee0 --- /dev/null +++ b/tests/NLog.RegEx.Tests/RegexReplaceTests.cs @@ -0,0 +1,181 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.RegEx.Tests +{ + using System; + using System.Collections.Generic; + using NLog; + using NLog.Config; + using NLog.LayoutRenderers.Wrappers; + using NLog.Layouts; + using NLog.Targets; + using Xunit; + + public class RegexReplaceTests + { + public RegexReplaceTests() + { + LogManager.ThrowExceptions = true; + LogManager.Setup().SetupExtensions(ext => ext.RegisterAssembly(typeof(Conditions.RegexConditionMethods).Assembly)); + } + + [Fact] + public void ReplaceTestWithoutRegEx() + { + // Arrange + SimpleLayout layout = @"${regex-replace:inner=${message}:searchFor=foo:replaceWith=BAR}"; + + // Act + var result = layout.Render(new LogEventInfo(LogLevel.Info, "Test", " foo bar bar foo bar FOO")); + + // Assert + Assert.Equal(" BAR bar bar BAR bar FOO", result); + } + + [Fact] + public void ReplaceTestIgnoreCaseWithoutRegEx() + { + // Arrange + SimpleLayout layout = @"${regex-replace:inner=${message}:searchFor=foo:replaceWith=BAR:ignorecase=true}"; + + // Act + var result = layout.Render(new LogEventInfo(LogLevel.Info, "Test", " foo bar bar foo bar FOO")); + + // Assert + Assert.Equal(" BAR bar bar BAR bar BAR", result); + } + + [Fact] + public void ReplaceTestWholeWordsWithoutRegEx() + { + // Arrange + SimpleLayout layout = @"${regex-replace:inner=${message}:searchFor=foo:replaceWith=BAR:ignorecase=true:WholeWords=true}"; + + // Act + var result = layout.Render(new LogEventInfo(LogLevel.Info, "Test", " foo bar bar foobar bar FOO")); + + // Assert + Assert.Equal(" BAR bar bar foobar bar BAR", result); + } + + [Fact] + public void ReplaceTestWithSimpleRegExFromConfig() + { + var configuration = XmlLoggingConfiguration.CreateFromXmlString(@" + + + + + + + +"); + + var d1 = configuration.FindTargetByName("d1") as DebugTarget; + Assert.NotNull(d1); + var layout = d1.Layout as SimpleLayout; + Assert.NotNull(layout); + + var result = layout.Render(new LogEventInfo(LogLevel.Info, "Test", "\r\nfoo\rbar\nbar\tbar bar \n bar")); + Assert.Equal(" foo bar bar bar bar bar", result); + } + + [Fact] + public void ReplaceTestWithSimpleRegExFromConfig2() + { + var configuration = XmlLoggingConfiguration.CreateFromXmlString(@" + + + + + + + + + +"); + + var d1 = configuration.FindTargetByName("d1") as DebugTarget; + Assert.NotNull(d1); + var layout = d1.Layout as SimpleLayout; + Assert.NotNull(layout); + + var result = layout.Render(new LogEventInfo(LogLevel.Info, "Test", "\r\nfoo\rbar\nbar\tbar bar \n bar")); + Assert.Equal(" foo bar bar bar bar bar", result); + } + + [Fact] + public void ReplaceTestWithComplexRegEx() + { + var logFactory = new LogFactory().Setup().LoadConfigurationFromXml(@" + + + + + + + + + + + + +").LogFactory; + + var d1 = logFactory.Configuration.FindTargetByName("d1"); + Assert.NotNull(d1); + var layout = d1.Layout as SimpleLayout; + Assert.NotNull(layout); + + var testCases = new List> + { + Tuple.Create("1234", "1234"), + Tuple.Create("1234-5678-1234-5678", "XXXX-XXXX-XXXX-5678"), + Tuple.Create("1234 5678 1234 5678", "XXXX XXXX XXXX 5678"), + Tuple.Create("1234567812345678", "XXXXXXXXXXXX5678"), + Tuple.Create("ABCD-1234-5678-1234-5678", "ABCD-XXXX-XXXX-XXXX-5678"), + Tuple.Create("1234-5678-1234-5678-ABCD", "XXXX-XXXX-XXXX-5678-ABCD"), + Tuple.Create("ABCD-1234-5678-1234-5678-ABCD", "ABCD-XXXX-XXXX-XXXX-5678-ABCD"), + }; + + foreach (var testCase in testCases) + { + var result = layout.Render(new LogEventInfo(LogLevel.Info, "Test", testCase.Item1)); + Assert.Equal(testCase.Item2, result); + } + } + } +} diff --git a/tests/NLog.Targets.AtomicFile.Tests/AtomicProcessRunner.cs b/tests/NLog.Targets.AtomicFile.Tests/AtomicProcessRunner.cs new file mode 100644 index 0000000000..f5df651510 --- /dev/null +++ b/tests/NLog.Targets.AtomicFile.Tests/AtomicProcessRunner.cs @@ -0,0 +1,161 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.AtomicFile.Tests +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Text; + using Xunit; + + static class AtomicProcessRunner + { + static AtomicProcessRunner() + { + string sourceCode = @" +using System; +using System.Reflection; + +public static class C1 +{ + public static int Main(string[] args) + { + try + { + Console.WriteLine(""Starting...""); + if (args.Length < 3) + throw new Exception(""Usage: Runner.exe \""AssemblyName\"" \""ClassName\"" \""MethodName\"" \""Parameter1...N\""""); + + string assemblyName = args[0]; + string className = args[1]; + string methodName = args[2]; + object[] arguments = new object[args.Length - 3]; + for (int i = 0; i < arguments.Length; ++i) + arguments[i] = args[3 + i]; + + Assembly assembly = Assembly.Load(assemblyName); + Type type = assembly.GetType(className); + if (type == null) + throw new Exception(className + "" not found in "" + assemblyName); + MethodInfo method = type.GetMethod(methodName); + if (method == null) + throw new Exception(methodName + "" not found in "" + type); + object targetObject = null; + if (!method.IsStatic) + targetObject = Activator.CreateInstance(type); + method.Invoke(targetObject, arguments); + return 0; + } + catch (Exception ex) + { + Console.WriteLine(ex); + return 1; + } + } +}"; + +#if NETFRAMEWORK + var provider = new Microsoft.CSharp.CSharpCodeProvider(); + var options = new System.CodeDom.Compiler.CompilerParameters(); + options.OutputAssembly = "Runner.exe"; + options.GenerateExecutable = true; + options.IncludeDebugInformation = true; + // To allow debugging the generated Runner.exe we need to keep files. + // See https://stackoverflow.com/questions/875723/how-to-debug-break-in-codedom-compiled-code + options.TempFiles = new System.CodeDom.Compiler.TempFileCollection(Environment.GetEnvironmentVariable("TEMP"), true); + var results = provider.CompileAssemblyFromSource(options, sourceCode); +#else + var compilation = Microsoft.CodeAnalysis.CSharp.CSharpCompilation + .Create( + "Runner.dll", + new[] { Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(sourceCode) }, + references: Basic.Reference.Assemblies.ReferenceAssemblies.NetStandard20); + + var outputAssembly = Path.Combine(Path.GetDirectoryName(typeof(AtomicProcessRunner).Assembly.Location), "Runner.dll"); + if (System.IO.File.Exists(outputAssembly)) + System.IO.File.Delete(outputAssembly); + using (var stream = new FileStream(outputAssembly, FileMode.CreateNew)) + //using (var stream = new MemoryStream()) + { + var result = compilation.Emit(stream); + var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error).ToList(); + Assert.True(failures.Count == 0, string.Join(Environment.NewLine, failures)); + Assert.True(result.Success); + } + var outputRuntimeConfig = Path.Combine(Path.GetDirectoryName(typeof(AtomicProcessRunner).Assembly.Location), "Runner.runtimeconfig.json"); + if (System.IO.File.Exists(outputRuntimeConfig)) + System.IO.File.Delete(outputRuntimeConfig); + using (var streamReader = new StreamReader(Path.Combine(Path.GetDirectoryName(typeof(AtomicProcessRunner).Assembly.Location), typeof(AtomicProcessRunner).Assembly.GetName().Name) + ".runtimeconfig.json")) + using (var streamWriter = new StreamWriter(outputRuntimeConfig)) + { + streamWriter.Write(streamReader.ReadToEnd()); + } +#endif + } + + public static Process SpawnMethod(Type type, string methodName, params string[] p) + { + string assemblyName = type.Assembly.FullName; + string typename = type.FullName; + StringBuilder sb = new StringBuilder(); +#if !NETFRAMEWORK + sb.AppendFormat("exec \"{0}\" ", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Runner.dll")); +#endif + sb.AppendFormat("\"{0}\" \"{1}\" \"{2}\"", assemblyName, typename, methodName); + foreach (string s in p) + { + sb.Append(" "); + sb.Append("\""); + sb.Append(s); + sb.Append("\""); + } + + Process proc = new Process(); + proc.StartInfo.Arguments = sb.ToString(); +#if NETFRAMEWORK + proc.StartInfo.FileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Runner.exe"); +#else + proc.StartInfo.FileName = "dotnet"; +#endif + proc.StartInfo.UseShellExecute = false; + proc.StartInfo.CreateNoWindow = true; + // Hint: + // In case we wanna redirect stdout we should drain the redirected pipe continuously. + // Otherwise Runner.exe's console buffer is full rather fast, leading to a lock within Console.Write(Line). + proc.Start(); + return proc; + } + } +} diff --git a/tests/NLog.Targets.AtomicFile.Tests/AtomicWritesMultiProcessTests.cs b/tests/NLog.Targets.AtomicFile.Tests/AtomicWritesMultiProcessTests.cs new file mode 100644 index 0000000000..2cb57207b2 --- /dev/null +++ b/tests/NLog.Targets.AtomicFile.Tests/AtomicWritesMultiProcessTests.cs @@ -0,0 +1,405 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +#define DISABLE_FILE_INTERNAL_LOGGING + +namespace NLog.Targets.AtomicFile.Tests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Threading; + using NLog.Common; + using NLog.Targets; + using NLog.Targets.Wrappers; + using Xunit; + + public class AtomicWritesMultiProcessTests + { + private static readonly Logger _logger = LogManager.GetLogger(nameof(AtomicWritesMultiProcessTests)); + + public AtomicWritesMultiProcessTests() + { + InternalLogger.Reset(); + LogManager.Configuration = null; + LogManager.ThrowExceptions = true; + } + + private static void ConfigureSharedFile(string mode, string fileName) + { + var modes = mode.Split('|'); + + AtomicFileTarget ft = new AtomicFileTarget(); + ft.FileName = fileName; + ft.Layout = "${message}"; + ft.ConcurrentWrites = true; + ft.OpenFileCacheTimeout = 10; + ft.OpenFileCacheSize = 1; + ft.LineEnding = LineEndingMode.LF; + ft.KeepFileOpen = Array.IndexOf(modes, "retry") >= 0 ? false : true; + ft.ArchiveAboveSize = Array.IndexOf(modes, "archive") >= 0 ? 50 : -1; + if (ft.ArchiveAboveSize > 0) + { + ft.ArchiveSuffixFormat = "_{0:0000}"; + ft.MaxArchiveFiles = 10000; + } + + var name = "ConfigureSharedFile_" + mode.Replace('|', '_') + "-wrapper"; + + LogManager.Setup().SetupExtensions(ext => ext.RegisterTarget("file")); + + switch (modes[0]) + { + case "async": + var asyncTarget = new AsyncTargetWrapper(ft, 100, AsyncTargetWrapperOverflowAction.Grow) { Name = name, TimeToSleepBetweenBatches = 10 }; + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(asyncTarget)); + break; + + case "buffered": + var bufferTarget = new BufferingTargetWrapper(ft, 100) { Name = name }; + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(bufferTarget)); + break; + + case "buffered_timed_flush": + var bufferFlushTarget = new BufferingTargetWrapper(ft, 100, 10) { Name = name }; + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(bufferFlushTarget)); + break; + + default: + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(ft)); + break; + } + } + +#pragma warning disable xUnit1013 // Needed for test + public static void MultiProcessExecutor(string processIndex, string fileName, string numLogsString, string mode) +#pragma warning restore xUnit1013 + { + Thread.CurrentThread.Name = processIndex; + + int numLogs = Convert.ToInt32(numLogsString); + int idxProcess = Convert.ToInt32(processIndex); + + ConfigureSharedFile(mode, fileName); + + string format = processIndex + " {0}"; + + // Having the internal logger enabled would just slow things down, reducing the + // likelyhood for uncovering racing conditions. Uncomment #define DISABLE_FILE_INTERNAL_LOGGING + +#if !DISABLE_FILE_INTERNAL_LOGGING + var logWriter = new StringWriter { NewLine = Environment.NewLine }; + NLog.Common.InternalLogger.LogLevel = LogLevel.Debug; + NLog.Common.InternalLogger.LogFile = Path.Combine(Path.GetDirectoryName(fileName), string.Format("Internal_{0}.txt", processIndex)); + NLog.Common.InternalLogger.LogWriter = logWriter; + NLog.Common.InternalLogger.LogToConsole = true; + + try +#endif + { + LogManager.ThrowExceptions = false; + + Thread.Sleep(Math.Max((10 - idxProcess), 1) * 5); // Delay to wait for the other processes + + for (int i = 0; i < numLogs; ++i) + { + _logger.Debug(format, i); + } + + LogManager.Configuration = null; // Flush + Close + LogManager.ThrowExceptions = true; + } +#if !DISABLE_FILE_INTERNAL_LOGGING + catch (Exception ex) + { + using (var textWriter = File.AppendText(Path.Combine(Path.GetDirectoryName(fileName), string.Format("Internal_{0}.txt", processIndex)))) + { + textWriter.WriteLine(ex.ToString()); + textWriter.WriteLine(logWriter.GetStringBuilder().ToString()); + } + throw; + } + finally + { + LogManager.ThrowExceptions = true; + } + + using (var textWriter = File.AppendText(Path.Combine(Path.GetDirectoryName(fileName), string.Format("Internal_{0}.txt", processIndex)))) + { + textWriter.WriteLine(logWriter.GetStringBuilder().ToString()); + } +#endif + } + + private string MakeFileName(int numProcesses, int numLogs, string mode) + { + // Having separate file names for the various tests makes debugging easier. + return $"test_{numProcesses}_{numLogs}_{mode.Replace('|', '_')}.txt"; + } + + private void DoConcurrentTest(int numProcesses, int numLogs, string mode) + { + string tempPath = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + string archivePath = Path.Combine(tempPath, "Archive"); + + try + { + Directory.CreateDirectory(tempPath); + Directory.CreateDirectory(archivePath); + + string logFile = Path.Combine(tempPath, MakeFileName(numProcesses, numLogs, mode)); + if (File.Exists(logFile)) + { + throw new Exception($"file '{logFile}' already exists"); + } + + Process[] processes = new Process[numProcesses]; + + for (int i = 0; i < numProcesses; ++i) + { + processes[i] = AtomicProcessRunner.SpawnMethod( + GetType(), + nameof(MultiProcessExecutor), + i.ToString(), + logFile, + numLogs.ToString(), + mode); + } + + // In case we'd like to capture stdout, we would need to drain it continuously. + // StandardOutput.ReadToEnd() wont work, since the other processes console only has limited buffer. + for (int i = 0; i < numProcesses; ++i) + { + processes[i].WaitForExit(); + Assert.Equal(0, processes[i].ExitCode); + processes[i].Dispose(); + processes[i] = null; + } + + var files = new System.Collections.Generic.List(new DirectoryInfo(tempPath).GetFiles("test_*.txt").Select(f => f.FullName)); + if (files.Count == 0) + files.Add(logFile); + + bool verifyFileSize = files.Count > 1; + + var receivedNumbersSet = new List[numProcesses]; + for (int i = 0; i < numProcesses; i++) + { + var receivedNumbers = new List(numLogs); + receivedNumbersSet[i] = receivedNumbers; + } + + //Console.WriteLine("Verifying output file {0}", logFile); + foreach (var file in files) + { + using (StreamReader sr = File.OpenText(file)) + { + string line; + while ((line = sr.ReadLine()) != null) + { + string[] tokens = line.Split(' '); + Assert.Equal(2, tokens.Length); + + int thread = Convert.ToInt32(tokens[0]); + int number = Convert.ToInt32(tokens[1]); + Assert.True(thread >= 0); + Assert.True(thread < numProcesses); + receivedNumbersSet[thread].Add(number); + } + + if (verifyFileSize) + { + if (sr.BaseStream.Length > 120) + throw new InvalidOperationException( + $"Error when reading file {file}, size {sr.BaseStream.Length} is too large"); + else if (sr.BaseStream.Length < 35 && files[files.Count - 1] != file) + throw new InvalidOperationException( + $"Error when reading file {file}, size {sr.BaseStream.Length} is too small"); + } + } + } + + var expected = Enumerable.Range(0, numLogs).ToList(); + + int currentProcess = 0; + bool? equalsWhenReorderd = null; + try + { + for (; currentProcess < numProcesses; currentProcess++) + { + var receivedNumbers = receivedNumbersSet[currentProcess]; + + var equalLength = expected.Count == receivedNumbers.Count; + + var fastCheck = equalLength && expected.SequenceEqual(receivedNumbers); + + if (!fastCheck) + //assert equals on two long lists in xUnit is lame. Not showing the difference. + { + if (equalLength) + { + var reordered = receivedNumbers.OrderBy(i => i); + + equalsWhenReorderd = expected.SequenceEqual(reordered); + } + + Assert.Equal(string.Join(",", expected), string.Join(",", receivedNumbers)); + } + } + } + catch (Exception ex) + { + var reoderProblem = equalsWhenReorderd is null ? "Dunno" : (equalsWhenReorderd == true ? "Yes" : "No"); + throw new InvalidOperationException($"Error when comparing path {tempPath} for process {currentProcess}. Is this a reordering problem? {reoderProblem}", ex); + } + } +#if !DISABLE_FILE_INTERNAL_LOGGING + catch (Exception ex) + { + Console.WriteLine(ex); + Console.WriteLine(); + + StringBuilder sb = new StringBuilder(); + sb.Append(ex.Message); + + for (int i = 0; i < numProcesses; i++) + { + var internalLogFile = Path.Combine(tempPath, string.Format("Internal_{0}.txt", i)); + if (File.Exists(internalLogFile)) + { + sb.AppendFormat(" MultiProcessId={0}", numProcesses); + sb.AppendLine(); + foreach (var line in File.ReadAllLines(internalLogFile)) + sb.AppendLine(line); + sb.AppendLine(); + } + } + + if (sb.Length > ex.Message.Length) + throw new InvalidOperationException(sb.ToString(), ex); + else + throw; + } +#endif + finally + { + try + { + if (Directory.Exists(archivePath)) + Directory.Delete(archivePath, true); + if (Directory.Exists(tempPath)) + Directory.Delete(tempPath, true); + } + catch + { + } + } + } + + [Theory] + [InlineData(2, 10000, "none")] + [InlineData(5, 4000, "none")] + [InlineData(10, 2000, "none")] +#if !MONO + // Linux doesn't work well with concurrent archive operations + [InlineData(2, 500, "none|archive")] +#endif + public void SimpleConcurrentTest(int numProcesses, int numLogs, string mode) + { + if (mode.Contains("archive") && IsLinux()) + { + Console.WriteLine("[SKIP] ConcurrentWritesMultiProcessTests.SimpleConcurrentTest Concurrent archive not supported on Linux"); + return; + } + + for (int i = 1; i <= 3; ++i) + { + try + { + DoConcurrentTest(numProcesses, numLogs, mode); + return; + } + catch + { + if (i == 3) + throw; + } + } + } + + [Theory] + [InlineData("async")] + public void AsyncConcurrentTest(string mode) + { + // Before 2 processes are running into concurrent writes, + // the first process typically already has written couple thousand events. + // Thus to have a meaningful test, at least 10K events are required. + // Due to the buffering it makes no big difference in runtime, whether we + // have 2 process writing 10K events each or couple more processes with even more events. + // Runtime is mostly defined by Runner.exe compilation and JITing the first. + DoConcurrentTest(5, 1000, mode); + } + + [Theory] + [InlineData("buffered")] + public void BufferedConcurrentTest(string mode) + { + DoConcurrentTest(5, 1000, mode); + } + + [Theory] + [InlineData("buffered_timed_flush")] + public void BufferedTimedFlushConcurrentTest(string mode) + { + DoConcurrentTest(5, 1000, mode); + } + + private static void ChildLoggerProcess(string[] args) + { + string processIndex = args[0]; + string fileName = args[1]; + string numLogsString = args[2]; + string mode = args[3]; + + MultiProcessExecutor(processIndex, fileName, numLogsString, mode); + } + + protected static bool IsLinux() + { + var val = Environment.GetEnvironmentVariable("WINDIR"); + return string.IsNullOrEmpty(val); + } + } +} diff --git a/tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj b/tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj new file mode 100644 index 0000000000..61f19c133d --- /dev/null +++ b/tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj @@ -0,0 +1,36 @@ + + + + 17.0 + net462 + net462;net8.0 + + false + + NLogTests.snk + false + true + true + + Full + true + true + en + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/tests/NLog.Targets.AtomicFile.Tests/NLogTests.snk b/tests/NLog.Targets.AtomicFile.Tests/NLogTests.snk new file mode 100644 index 0000000000..f168e4dbec Binary files /dev/null and b/tests/NLog.Targets.AtomicFile.Tests/NLogTests.snk differ diff --git a/src/NLog/Internal/EncodingHelpers.cs b/tests/NLog.Targets.AtomicFile.Tests/Properties/AssemblyInfo.cs similarity index 87% rename from src/NLog/Internal/EncodingHelpers.cs rename to tests/NLog.Targets.AtomicFile.Tests/Properties/AssemblyInfo.cs index efab1e9469..23035655fa 100644 --- a/src/NLog/Internal/EncodingHelpers.cs +++ b/tests/NLog.Targets.AtomicFile.Tests/Properties/AssemblyInfo.cs @@ -31,13 +31,4 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.Internal -{ - internal static class EncodingHelpers - { - /// - /// UTF-8 BOM 239, 187, 191 - /// - internal static readonly byte[] Utf8BOM = { 0xEF, 0xBB, 0xBF }; - } -} +[assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/NLog.Targets.ConcurrentFile.Tests/ConcurrentFileTargetTests.cs b/tests/NLog.Targets.ConcurrentFile.Tests/ConcurrentFileTargetTests.cs new file mode 100644 index 0000000000..39d3b11d98 --- /dev/null +++ b/tests/NLog.Targets.ConcurrentFile.Tests/ConcurrentFileTargetTests.cs @@ -0,0 +1,4740 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.ConcurrentFile.Tests +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.IO.Compression; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; +#if NETFRAMEWORK + using Ionic.Zip; +#endif + using NLog.Common; + using NLog.Config; + using NLog.Layouts; + using NLog.Targets; + using NLog.Targets.Wrappers; + using NLog.Time; + using NSubstitute; + using Xunit; + + public class ConcurrentFileTargetTests + { + private readonly Logger logger = LogManager.GetLogger("NLog.UnitTests.Targets.FileTargetTests"); + + public ConcurrentFileTargetTests() + { + InternalLogger.Reset(); + LogManager.Configuration = null; + LogManager.ThrowExceptions = true; + LogManager.Setup().SetupExtensions(ext => ext.RegisterTarget("file")); + } + + public static IEnumerable SimpleFileTest_TestParameters + { + get + { + var booleanValues = new[] { true, false }; + return + from concurrentWrites in booleanValues + from keepFileOpen in booleanValues + from forceMutexConcurrentWrites in booleanValues + where UniqueBaseAppender(concurrentWrites, keepFileOpen, forceMutexConcurrentWrites) + from forceManaged in booleanValues + select new object[] { concurrentWrites, keepFileOpen, forceManaged, forceMutexConcurrentWrites }; + } + } + + private static bool UniqueBaseAppender(bool concurrentWrites, bool keepFileOpen, bool forceMutexConcurrentWrites) + { + if (!keepFileOpen && !concurrentWrites && !forceMutexConcurrentWrites) + return true; + if (!keepFileOpen && concurrentWrites && !forceMutexConcurrentWrites) + return true; + if (keepFileOpen && !forceMutexConcurrentWrites) + return true; + return false; + } + + [Theory] + [MemberData(nameof(SimpleFileTest_TestParameters))] + public void SimpleFileTest(bool concurrentWrites, bool keepFileOpen, bool forceManaged, bool forceMutexConcurrentWrites) + { + var logFile = Path.GetTempFileName(); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + OpenFileCacheTimeout = 0, + ConcurrentWrites = concurrentWrites, + KeepFileOpen = keepFileOpen, + ForceManaged = forceManaged, + ForceMutexConcurrentWrites = forceMutexConcurrentWrites, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + + LogManager.Configuration = null; // Flush + + AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + } + } + + [Theory] + [MemberData(nameof(SimpleFileTest_TestParameters))] + public void SimpleFileDeleteTest(bool concurrentWrites, bool keepFileOpen, bool forceManaged, bool forceMutexConcurrentWrites) + { + bool isSimpleKeepFileOpen = keepFileOpen && !concurrentWrites +#if NETFRAMEWORK && !MONO + && IsLinux() +#endif + ; + +#if MONO + if (IsLinux() && concurrentWrites && keepFileOpen) + { + Console.WriteLine("[SKIP] FileTargetTests.SimpleFileDeleteTest Not supported on MONO on Travis, because of FileSystemWatcher not working"); + return; + } +#endif + foreach (var archiveSameFolder in new[] { true, false }) + { + for (int i = 1; i <= 3; ++i) + { + var logPath = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString(), "Archive"); + var logFile = Path.GetFullPath(Path.Combine(logPath, "..", "nlogA.txt")); + //var arhiveFile = archiveSameFolder ? Path.GetFullPath(Path.Combine(logPath, "..", "nlogB.txt")) : Path.GetFullPath(Path.Combine(logPath, "nlogB.txt")); + var arhiveFile = Path.GetFullPath(Path.Combine(logPath, archiveSameFolder ? ".." : ".", "nlogB.txt")); + + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = Layout.FromLiteral(logFile), + ArchiveFileName = Layout.FromLiteral(arhiveFile), + ArchiveEvery = FileArchiveEveryPeriod.Year, + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + OpenFileCacheTimeout = 0, + EnableFileDelete = true, + ConcurrentWrites = concurrentWrites, + KeepFileOpen = keepFileOpen, + ForceManaged = forceManaged, + ForceMutexConcurrentWrites = forceMutexConcurrentWrites, + ArchiveAboveSize = archiveSameFolder ? 1000000 : 0, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + + LogManager.Flush(); + + Directory.CreateDirectory(Path.GetDirectoryName(arhiveFile)); + File.Move(logFile, arhiveFile); + + if (isSimpleKeepFileOpen) + Thread.Sleep(1500); // Ensure EnableFileDeleteSimpleMonitor will trigger + else if (keepFileOpen) + Thread.Sleep(150); // Allow AutoClose-Timer-Thread to react (FileWatcher schedules timer after 50 msec) + + logger.Info("bbb"); + + LogManager.Configuration = null; + + AssertFileContents(logFile, "Info bbb\n", Encoding.UTF8); + } + catch + { + if (i == 3) + throw; + + System.Threading.Thread.Sleep(1000 * i); + } + finally + { + if (File.Exists(arhiveFile)) + { + File.Delete(arhiveFile); + } + + if (File.Exists(logFile)) + { + File.Delete(logFile); + } + + if (Directory.Exists(Path.GetDirectoryName(arhiveFile))) + { + Directory.Delete(Path.GetDirectoryName(arhiveFile)); + } + + if (Directory.Exists(Path.GetDirectoryName(logFile))) + { + Directory.Delete(Path.GetDirectoryName(logFile)); + } + } + } + } + } + + /// + /// There was a bug when creating the file in the root. + /// + /// Please note that this test can fail because the unit test doesn't have write access in the root. + /// + [Fact] + public void SimpleFileTestInRoot() + { + if (NLog.Internal.PlatformDetector.IsWin32) + { + var dirPath = "C:\\"; + var directoryInfo = new DirectoryInfo(dirPath); + + if (directoryInfo.Exists) + { + return; + } + + var logFile = dirPath + "nlog-test.log"; + SimpleFileWriteLogTest(logFile); + } + } + + + [Fact] + public void SimpleFileWithSpecialCharsTest() + { + var logFile = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString() + "!@#$%^&()_-=+ .log"); + SimpleFileWriteLogTest(logFile); + } + + private void SimpleFileWriteLogTest(string logFile) + { + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + + LogManager.Configuration = null; // Flush + + AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + } + } + + [Fact] + public void SimpleFileTestWriteBom() + { + var logFile = Path.GetTempFileName(); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Encoding = Encoding.UTF8, + WriteBom = true, + Layout = "${level} ${message}", + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + + LogManager.Configuration = null; // Flush + + AssertFileContents(logFile, "Debug aaa\n", Encoding.UTF8, true); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + } + } + +#if !MONO + /// + /// If a drive doesn't existing, before repeatably creating a dir was tried. This test was taking +60 seconds + /// + [Theory] + [MemberData(nameof(SimpleFileTest_TestParameters))] + public void NonExistingDriveShouldNotDelayMuch(bool concurrentWrites, bool keepFileOpen, bool forceManaged, bool forceMutexConcurrentWrites) + { + var nonExistingDrive = GetFirstNonExistingDriveWindows(); + + var logFile = nonExistingDrive + "://dont-extist/no-timeout.log"; + + DateTime start = DateTime.UtcNow; + + try + { + LogManager.ThrowExceptions = false; + + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + Layout = "${level} ${message}", + ConcurrentWrites = concurrentWrites, + KeepFileOpen = keepFileOpen, + ForceManaged = forceManaged, + ForceMutexConcurrentWrites = forceMutexConcurrentWrites, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + for (int i = 0; i < 300; i++) + { + logger.Debug("aaa"); + } + + LogManager.Configuration = null; // Flush + + Assert.True(DateTime.UtcNow - start < TimeSpan.FromSeconds(5)); + } + finally + { + LogManager.ThrowExceptions = true; + + //should not be necessary + if (File.Exists(logFile)) + File.Delete(logFile); + } + } + + /// + /// Get first drive letter of non-existing drive + /// + /// + private static char GetFirstNonExistingDriveWindows() + { + var existingDrives = new HashSet(Environment.GetLogicalDrives().Select(d => d[0].ToString()), + StringComparer.OrdinalIgnoreCase); + var nonExistingDrive = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToList().First(driveLetter => !existingDrives.Contains(driveLetter.ToString())); + return nonExistingDrive; + } + +#endif + + [Fact] + public void RollingArchiveEveryMonth() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var defaultTimeSource = TimeSource.Current; + + try + { + var timeSource = new ShiftedTimeSource(DateTimeKind.Local); + if (timeSource.Time.Minute == 59) + { + // Avoid double-archive due to overflow of the hour. + timeSource.AddToLocalTime(TimeSpan.FromMinutes(1)); + timeSource.AddToSystemTime(TimeSpan.FromMinutes(1)); + } + TimeSource.Current = timeSource; + + var fileTarget = new ConcurrentFileTarget + { + FileName = Path.Combine(tempDir, "${date:format=dd}_AppName.log"), + LineEnding = LineEndingMode.LF, + Layout = "${message}", + ArchiveNumbering = ArchiveNumberingMode.Rolling, + ArchiveEvery = FileArchiveEveryPeriod.Month, + MaxArchiveFiles = 1, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + for (int i = 0; i < 12; ++i) + { + for (int j = 0; j < 31; ++j) + { + logger.Debug("aaa"); + timeSource.AddToLocalTime(TimeSpan.FromDays(1)); + timeSource.AddToSystemTime(TimeSpan.FromDays(1)); + } + } + + var files = Directory.GetFiles(tempDir); + // Cleanup doesn't work, as all file names has the same timestamp + if (files.Length < 28 || files.Length > 31) + Assert.Equal(30, files.Length); + + foreach (var file in files) + { + Assert.Equal(14, Path.GetFileName(file).Length); + } + + LogManager.Configuration = null; // Flush and close + } + finally + { + TimeSource.Current = defaultTimeSource; + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + +#if !MONO + [Theory] +#else + [Theory(Skip="Not supported on MONO on Travis, because of File birthtime not working")] +#endif + [InlineData(false, false, ArchiveNumberingMode.DateAndSequence)] + [InlineData(false, true, ArchiveNumberingMode.DateAndSequence)] + [InlineData(false, false, ArchiveNumberingMode.Sequence)] + [InlineData(false, true, ArchiveNumberingMode.Sequence)] + [InlineData(true, false, ArchiveNumberingMode.DateAndSequence)] + [InlineData(true, true, ArchiveNumberingMode.DateAndSequence)] + [InlineData(true, false, ArchiveNumberingMode.Sequence)] + [InlineData(true, true, ArchiveNumberingMode.Sequence)] + public void DatedArchiveEveryMonth(bool archiveSubFolder, bool maxArchiveDays, ArchiveNumberingMode archiveNumberingMode) + { + if (IsLinux()) + { + Console.WriteLine("[SKIP] FileTargetTests.DatedArchiveEveryMonth because SetCreationTime is not working on Travis"); + return; + } + + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "AppName.log"); + var archiveDir = archiveSubFolder ? Path.Combine(tempDir, "Archive") : tempDir; + + var defaultTimeSource = TimeSource.Current; + + try + { + var timeSource = new ShiftedTimeSource(DateTimeKind.Local); + if (timeSource.Time.Minute == 59) + { + // Avoid double-archive due to overflow of the hour. + timeSource.AddToLocalTime(TimeSpan.FromMinutes(1)); + timeSource.AddToSystemTime(TimeSpan.FromMinutes(1)); + } + TimeSource.Current = timeSource; + + // Generate 4 files with the following contents + // 000 - 6 Months Old (Deleted) + // 111 - 4 Months Old + // 222 - 2 Months Old + // 333 - Current + List createdFiles = new List(); + List currentFiles = new List(); + for (int i = 0; i < 4; ++i) + { + if (i != 0) + { + // Make the files 2 months older, and lets try again + for (int x = 0; x < createdFiles.Count; ++x) + { + var existingFile = createdFiles[x]; + var monthsOld = x == 0 ? 2 : ((i - x) * 2 + 2); + File.SetCreationTime(existingFile, DateTime.Now.AddDays(-(32 * monthsOld))); + } + + timeSource.AddToLocalTime(TimeSpan.FromDays(32 * 3)); + timeSource.AddToSystemTime(TimeSpan.FromDays(32 * 3)); + } + + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + LineEnding = LineEndingMode.LF, + Encoding = Encoding.ASCII, + Layout = "${message}", + KeepFileOpen = i % 2 != 0, + ArchiveFileName = archiveSubFolder ? Path.Combine(archiveDir, "AppName.{#}.log") : (Layout)null, + ArchiveNumbering = archiveNumberingMode, + ArchiveEvery = FileArchiveEveryPeriod.Month, + ArchiveDateFormat = "yyyyMMdd", + MaxArchiveFiles = maxArchiveDays ? 0 : 2, + MaxArchiveDays = maxArchiveDays ? 5 * 30 : 0 + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + logger.Debug($"{i.ToString()}{i.ToString()}{i.ToString()}"); + LogManager.Configuration = null; // Flush + + currentFiles = Directory.GetFiles(tempDir).ToList(); + if (archiveSubFolder && Directory.Exists(archiveDir)) + currentFiles.AddRange(Directory.GetFiles(archiveDir)); + + string newFile = string.Empty; + foreach (var fileName in currentFiles) + { + if (!createdFiles.Contains(fileName)) + { + Assert.Empty(newFile); + newFile = fileName; + + if (archiveNumberingMode == ArchiveNumberingMode.DateAndSequence && createdFiles.Count > 1) + { + // Verify it used the last-modified-time (And not file-creation-time) + string dateName = string.Empty; + dateName = Path.GetFileName(fileName); + dateName = dateName.Replace("AppName.", ""); + dateName = dateName.Replace(".0.log", ""); + dateName = dateName.Replace("log", ""); + Assert.NotEmpty(dateName); + Assert.Equal(timeSource.Time.Month, DateTime.ParseExact(dateName, "yyyyMMdd", null).Month); + } + } + } + + Assert.False(string.IsNullOrEmpty(newFile), $"Missing new file. OldFileCount={createdFiles.Count}, NewFileCount={currentFiles.Count}"); + createdFiles.Add(newFile); + } + + Assert.Equal(3, currentFiles.Count); + AssertFileContents(logFile, "333\n", Encoding.ASCII); + } + finally + { + TimeSource.Current = defaultTimeSource; + + if (Directory.Exists(archiveDir)) + Directory.Delete(archiveDir, true); + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void CsvHeaderTest() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "log.log"); + if (Path.DirectorySeparatorChar == '\\') + logFile = logFile.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + try + { + for (var i = 0; i < 2; i++) + { + var layout = new CsvLayout + { + Delimiter = CsvColumnDelimiterMode.Semicolon, + WithHeader = true, + Columns = + { + new CsvColumn("name", "${logger}"), + new CsvColumn("level", "${level}"), + new CsvColumn("message", "${message}"), + } + }; + + var fileTarget = new ConcurrentFileTarget + { + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = layout, + OpenFileCacheTimeout = 0, + ArchiveAboveSize = 120, // Only 2 LogEvents per file + MaxArchiveFiles = 1, + }; + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + if (i == 0) + { + for (int j = 0; j < 3; j++) + logger.Debug("aaa"); + + LogManager.Configuration = null; // Flush + + // See that the 3rd LogEvent was placed in its own file + AssertFileContents(logFile, "name;level;message\nNLog.UnitTests.Targets.FileTargetTests;Debug;aaa\n", Encoding.UTF8); + } + else + { + logger.Debug("aaa"); + } + } + + // See that opening closing + AssertFileContents(logFile, "name;level;message\nNLog.UnitTests.Targets.FileTargetTests;Debug;aaa\nNLog.UnitTests.Targets.FileTargetTests;Debug;aaa\n", Encoding.UTF8); + + Assert.NotEqual(3, Directory.GetFiles(tempDir).Length); // See that archive cleanup worked + + LogManager.Configuration = null; // Close + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void DeleteFileOnStartTest() + { + var logFile = Path.GetTempFileName(); + try + { + var fileTarget = new ConcurrentFileTarget + { + DeleteOldFileOnStartup = false, + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}" + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + + LogManager.Configuration = null; + + AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + + // configure again, without + // DeleteOldFileOnStartup + + fileTarget = new ConcurrentFileTarget + { + DeleteOldFileOnStartup = false, + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}" + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + + LogManager.Configuration = null; + AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\nDebug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + + // configure again, this time with + // DeleteOldFileOnStartup + + fileTarget = new ConcurrentFileTarget + { + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + DeleteOldFileOnStartup = true + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + + LogManager.Configuration = null; // Flush + + AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + } + } + + /// + /// todo not needed to execute twice. + /// + [Fact] + public void DeleteFileOnStartTest_noExceptionWhenMissing() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "log.log"); + + try + { + LogManager.Setup().LoadConfigurationFromXml($@" + + + + + + + +"); + + Assert.False(File.Exists(logFile)); + + var logger = LogManager.GetCurrentClassLogger(); + logger.Trace("running test"); + + Assert.NotNull(LogManager.Configuration); + + LogManager.Configuration = null; // Flush + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + +#if NETFRAMEWORK + public static IEnumerable ArchiveFileOnStartTests_TestParameters + { + get + { + var booleanValues = new[] { true, false }; + return + from enableCompression in booleanValues + from customFileCompressor in booleanValues + select new object[] { enableCompression, customFileCompressor }; + } + } +#else + public static IEnumerable ArchiveFileOnStartTests_TestParameters + { + get + { + var booleanValues = new[] { true, false }; + return + from enableCompression in booleanValues + select new object[] { enableCompression, false }; + } + } +#endif + + [Theory] + [MemberData(nameof(ArchiveFileOnStartTests_TestParameters))] + public void ArchiveFileOnStartTests(bool enableCompression, bool customFileCompressor) + { + var logFile = Path.GetTempFileName() + ".txt"; + var tempArchiveFolder = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString(), "Archive"); + var archiveExtension = enableCompression ? "zip" : "txt"; + IFileCompressor fileCompressor = null; + try + { + if (customFileCompressor) + { + fileCompressor = ConcurrentFileTarget.FileCompressor; + ConcurrentFileTarget.FileCompressor = new CustomFileCompressor(); + } + + // Configure first time with ArchiveOldFileOnStartup = false. + var fileTarget = new ConcurrentFileTarget + { + ArchiveOldFileOnStartup = false, + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}" + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + + LogManager.Configuration = null; + + AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + + // Configure second time with ArchiveOldFileOnStartup = false again. + // Expected behavior: Extra content to be appended to the file. + fileTarget = new ConcurrentFileTarget + { + ArchiveOldFileOnStartup = false, + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}" + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + + LogManager.Configuration = null; // Flush + AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\nDebug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + + + // Configure third time with ArchiveOldFileOnStartup = true again. + // Expected behavior: Extra content will be stored in a new file; the + // old content should be moved into a new location. + + var archiveTempName = Path.Combine(tempArchiveFolder, "archive." + archiveExtension); + + ConcurrentFileTarget ft; + fileTarget = ft = new ConcurrentFileTarget + { + EnableArchiveFileCompression = enableCompression, + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + ArchiveOldFileOnStartup = true, + ArchiveFileName = archiveTempName, + ArchiveNumbering = ArchiveNumberingMode.Sequence, + MaxArchiveFiles = 1 + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + logger.Debug("ddd"); + logger.Info("eee"); + logger.Warn("fff"); + + LogManager.Configuration = null; // Flush + + AssertFileContents(logFile, "Debug ddd\nInfo eee\nWarn fff\n", Encoding.UTF8); + Assert.True(File.Exists(archiveTempName)); + + Action assertFileContents = (f1, f2, content, encoding) => AssertFileContents(f1, content, encoding, false); + if (fileTarget.EnableArchiveFileCompression) + assertFileContents = AssertZipFileContents; + +#if !NET35 + string expectedEntryName = Path.GetFileNameWithoutExtension(archiveTempName) + ".txt"; +#else + string expectedEntryName = Path.GetFileName(logFile); +#endif + assertFileContents(archiveTempName, expectedEntryName, "Debug aaa\nInfo bbb\nWarn ccc\nDebug aaa\nInfo bbb\nWarn ccc\n", + Encoding.UTF8); + } + finally + { + if (customFileCompressor) + ConcurrentFileTarget.FileCompressor = fileCompressor; + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempArchiveFolder)) + Directory.Delete(tempArchiveFolder, true); + } + } + + [Fact] + public void ArchiveOldFileOnStartupAboveSize() + { + var logFile = Path.GetTempFileName(); + var tempArchiveFolder = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString(), "Archive"); + var archiveTempName = Path.Combine(tempArchiveFolder, "archive_size_threshold.txt"); + ConcurrentFileTarget CreateTestTarget(long threshold) + { + return new ConcurrentFileTarget + { + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + ArchiveOldFileOnStartupAboveSize = threshold, + ArchiveFileName = archiveTempName, + ArchiveNumbering = ArchiveNumberingMode.Sequence, + MaxArchiveFiles = 1 + }; + } + try + { + // No archive on startup (ignoring threshold) + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(CreateTestTarget(1000))); + logger.Info("aaa"); + LogManager.Shutdown(); + AssertFileContents(logFile, "Info aaa\n", Encoding.UTF8); + Assert.False(File.Exists(archiveTempName)); + + // Archive on startup with small threshold -> Must be archived + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(CreateTestTarget(3))); + logger.Info("ccc"); + LogManager.Flush(); + AssertFileContents(logFile, "Info ccc\n", Encoding.UTF8); + Assert.True(File.Exists(archiveTempName)); + AssertFileContents(archiveTempName, "Info aaa\n", Encoding.UTF8); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempArchiveFolder)) + Directory.Delete(tempArchiveFolder, true); + } + } + + [Fact] + public void ArchiveOldFileOnStartupAboveSizeWhenFileLocked() + { + var logFile = Path.GetTempFileName(); + var tempArchiveFolder = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString(), "Archive"); + var archiveTempName = Path.Combine(tempArchiveFolder, "archive_size_threshold.zip"); + + ConcurrentFileTarget CreateTestTarget(long threshold) + { + return new ConcurrentFileTarget + { + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + ArchiveOldFileOnStartupAboveSize = threshold, + ArchiveFileName = archiveTempName, + ArchiveNumbering = ArchiveNumberingMode.Sequence, + EnableArchiveFileCompression = true, + MaxArchiveFiles = 1 + }; + } + + try + { + // No archive on startup (ignoring threshold) + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(CreateTestTarget(1000))); + logger.Info("aaa"); + LogManager.Shutdown(); + AssertFileContents(logFile, "Info aaa\n", Encoding.UTF8); + Assert.False(File.Exists(archiveTempName)); + + NLog.LogManager.ThrowExceptions = false; + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(CreateTestTarget(3))); + + using (var fileStream = new FileStream(logFile, FileMode.Open, FileAccess.Write, FileShare.None)) + { + // Archive on startup with small threshold -> Must be archived + logger.Info("ccc"); + LogManager.Flush(); + fileStream.Close(); + AssertFileContents(logFile, "Info aaa\n", Encoding.UTF8); + Assert.False(File.Exists(archiveTempName)); + } + } + finally + { + NLog.LogManager.ThrowExceptions = true; + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempArchiveFolder)) + Directory.Delete(tempArchiveFolder, true); + } + } + + [Fact] + public void RetryFileOpenWhenFileLocked() + { + var logFile = Path.GetTempFileName(); + + var fileTarget = new ConcurrentFileTarget("file") + { + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + KeepFileOpen = false, + ConcurrentWriteAttempts = 100, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + try + { + var fileStream = new FileStream(logFile, FileMode.Open, FileAccess.Write, FileShare.None); + var task = Task.Run(() => logger.Info("aaa")); + Assert.False(task.Wait(TimeSpan.FromMilliseconds(50))); + fileStream.Dispose(); + Assert.True(task.Wait(TimeSpan.FromSeconds(60))); + + AssertFileContents(logFile, "Info aaa\n", Encoding.UTF8); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + } + } + + public static IEnumerable ReplaceFileContentsOnEachWriteTest_TestParameters + { + get + { + bool[] boolValues = new[] { false, true }; + return + from useHeader in boolValues + from useFooter in boolValues + select new object[] { useHeader, useFooter }; + } + } + + [Theory] + [MemberData(nameof(ReplaceFileContentsOnEachWriteTest_TestParameters))] + public void ReplaceFileContentsOnEachWriteTest(bool useHeader, bool useFooter) + { + const string header = "Headerline", footer = "Footerline"; + + var logFile = Path.GetTempFileName(); + try + { + var fileTarget = new ConcurrentFileTarget + { + DeleteOldFileOnStartup = false, + FileName = Layout.FromLiteral(logFile), + ReplaceFileContentsOnEachWrite = true, + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}" + }; + if (useHeader) + fileTarget.Header = header; + if (useFooter) + fileTarget.Footer = footer; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + string headerPart = useHeader ? header + LineEndingMode.LF.NewLineCharacters : string.Empty; + string footerPart = useFooter ? footer + LineEndingMode.LF.NewLineCharacters : string.Empty; + + logger.Debug("aaa"); + LogManager.Flush(); + AssertFileContents(logFile, headerPart + "Debug aaa\n" + footerPart, Encoding.UTF8); + + logger.Info("bbb"); + LogManager.Flush(); + AssertFileContents(logFile, headerPart + "Info bbb\n" + footerPart, Encoding.UTF8); + + logger.Warn("ccc"); + LogManager.Flush(); + AssertFileContents(logFile, headerPart + "Warn ccc\n" + footerPart, Encoding.UTF8); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + } + } + + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ReplaceFileContentsOnEachWrite_CreateDirs(bool createDirs) + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logfile = Path.Combine(tempDir, "log.log"); + + try + { + LogManager.ThrowExceptions = false; + + var target = new ConcurrentFileTarget + { + FileName = logfile, + ReplaceFileContentsOnEachWrite = true, + CreateDirs = createDirs + }; + var config = new LoggingConfiguration(); + + config.AddTarget("logfile", target); + + config.AddRuleForAllLevels(target); + + LogManager.Configuration = config; + + var logger = LogManager.GetLogger("A"); + logger.Info("a"); + + Assert.Equal(createDirs, Directory.Exists(tempDir)); + } + finally + { + LogManager.ThrowExceptions = true; + + if (File.Exists(logfile)) + File.Delete(logfile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void CreateDirsTest() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}" + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + + LogManager.Configuration = null; // Flush + AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + } + finally + { + LogManager.Configuration = null; + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Theory] + [InlineData(true, true, 0)] + [InlineData(false, true, 0)] + [InlineData(false, true, 1)] + [InlineData(true, false, 0)] + [InlineData(false, false, 0)] + [InlineData(false, false, 1)] + public void AutoFlushTest(bool autoFlush, bool keepFileOpen, int autoFlushTimeout) + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + KeepFileOpen = keepFileOpen, + ConcurrentWrites = false, + OpenFileFlushTimeout = autoFlushTimeout, + }; + if (!autoFlush) + fileTarget.AutoFlush = autoFlush; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + + if (autoFlush || !keepFileOpen) + { + AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + } + else + { + AssertFileContents(logFile, string.Empty, Encoding.UTF8); + if (autoFlushTimeout > 0) + { + for (int i = 0; i < 3; ++i) + { + Thread.Sleep(TimeSpan.FromSeconds(autoFlushTimeout * 1.5)); + var fileInfo = new FileInfo(logFile); + if (fileInfo.Exists && fileInfo.Length > 0) + break; + } + AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + } + } + + LogManager.Configuration = null; // Flush + AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + } + finally + { + LogManager.Configuration = null; + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void SequentialArchiveTest() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveAboveSize = 100, + LineEnding = LineEndingMode.LF, + Layout = "${message}", + MaxArchiveFiles = 3, + ArchiveNumbering = ArchiveNumberingMode.Sequence + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + // we emit 5 * 25 *(3 x aaa + \n) bytes + // so that we should get a full file + 3 archives + Generate100BytesLog('a'); + Generate100BytesLog('b'); + Generate100BytesLog('c'); + Generate100BytesLog('d'); + Generate100BytesLog('e'); + + LogManager.Configuration = null; // Flush + + var times = 25; + AssertFileContents(logFile, + StringRepeat(times, "eee\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0001.txt"), + StringRepeat(times, "bbb\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0002.txt"), + StringRepeat(times, "ccc\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0003.txt"), + StringRepeat(times, "ddd\n"), + Encoding.UTF8); + + //0000 should not exists because of MaxArchiveFiles=3 + Assert.False(File.Exists(Path.Combine(archiveFolder, "0000.txt"))); + Assert.False(File.Exists(Path.Combine(archiveFolder, "0004.txt"))); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void SequentialArchiveTest_MaxArchiveFiles_0() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveAboveSize = 100, + LineEnding = LineEndingMode.LF, + ArchiveNumbering = ArchiveNumberingMode.Sequence, + Layout = "${message}", + MaxArchiveFiles = 0 + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + // we emit 5 * 25 *(3 x aaa + \n) bytes + // so that we should get a full file + 4 archives + Generate100BytesLog('a'); + Generate100BytesLog('b'); + Generate100BytesLog('c'); + Generate100BytesLog('d'); + Generate100BytesLog('e'); + + LogManager.Configuration = null; // Flush + + AssertFileContents(logFile, + StringRepeat(25, "eee\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0000.txt"), + StringRepeat(25, "aaa\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0001.txt"), + StringRepeat(25, "bbb\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0002.txt"), + StringRepeat(25, "ccc\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0003.txt"), + StringRepeat(25, "ddd\n"), + Encoding.UTF8); + + Assert.False(File.Exists(Path.Combine(archiveFolder, "0004.txt"))); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void ArchiveAboveSizeWithArchiveNumberingModeDate_maxfiles_o() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveAboveSize = 100, + LineEnding = LineEndingMode.LF, + Layout = "${message}", + ArchiveNumbering = ArchiveNumberingMode.Date + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + //e.g. 20150804 + var archiveFileName = DateTime.Now.ToString("yyyyMMdd"); + + // we emit 5 * 25 *(3 x aaa + \n) bytes + // so that we should get a full file + 3 archives + var times = 25; + for (var i = 0; i < times; ++i) + { + logger.Debug("aaa"); + } + + for (var i = 0; i < times; ++i) + { + logger.Debug("bbb"); + } + for (var i = 0; i < times; ++i) + { + logger.Debug("ccc"); + } + for (var i = 0; i < times; ++i) + { + logger.Debug("ddd"); + } + for (var i = 0; i < times; ++i) + { + logger.Debug("eee"); + } + + LogManager.Configuration = null; // Flush + + //we expect only eee and all other in the archive + AssertFileContents(logFile, + StringRepeat(times, "eee\n"), + Encoding.UTF8); + + //DUNNO what to expected! + //try (which fails) + AssertFileContents( + Path.Combine(archiveFolder, $"{archiveFileName}.txt"), + StringRepeat(times, "aaa\n") + StringRepeat(times, "bbb\n") + StringRepeat(times, "ccc\n") + StringRepeat(times, "ddd\n"), + Encoding.UTF8); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void DeleteArchiveFilesByDate() + { + const int maxArchiveFiles = 3; + + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), + ArchiveAboveSize = 50, + LineEnding = LineEndingMode.LF, + ArchiveNumbering = ArchiveNumberingMode.Date, + ArchiveDateFormat = "yyyyMMddHHmmssfff", //make sure the milliseconds are set in the filename + Layout = "${message}", + MaxArchiveFiles = maxArchiveFiles + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + //writing 19 times 10 bytes (9 char + linefeed) will result in 3 archive files and 1 current file + for (var i = 0; i < 19; ++i) + { + logger.Debug("123456789"); + //build in a small sleep to make sure the current time is reflected in the filename + //do this every 5 entries + if (i % 5 == 0) + Thread.Sleep(50); + } + + //Setting the Configuration to [null] will result in a 'Dump' of the current log entries + LogManager.Configuration = null; // Flush + + var files = Directory.GetFiles(archiveFolder).OrderBy(s => s); + //the amount of archived files may not exceed the set 'MaxArchiveFiles' + Assert.Equal(maxArchiveFiles, files.Count()); + + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + //writing just one line of 11 bytes will trigger the cleanup of old archived files + //as stated by the MaxArchiveFiles property, but will only delete the oldest file + logger.Debug("1234567890"); + + LogManager.Configuration = null; // Flush + + var files2 = Directory.GetFiles(archiveFolder).OrderBy(s => s); + Assert.Equal(maxArchiveFiles, files2.Count()); + + //the oldest file should be deleted + Assert.DoesNotContain(files.ElementAt(0), files2); + //two files should still be there + Assert.Equal(files.ElementAt(1), files2.ElementAt(0)); + Assert.Equal(files.ElementAt(2), files2.ElementAt(1)); + //one new archive file should be created + Assert.DoesNotContain(files2.ElementAt(2), files); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void DeleteArchiveFilesByDateWithDateName() + { + const int maxArchiveFiles = 3; + LogManager.ThrowExceptions = true; + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + try + { + var logFile = Path.Combine(tempDir, "${date:format=yyyyMMddHHmmssfff}.txt"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(tempDir, "{#}.txt"), + ArchiveEvery = FileArchiveEveryPeriod.Year, + LineEnding = LineEndingMode.LF, + ArchiveNumbering = ArchiveNumberingMode.Date, + ArchiveDateFormat = "yyyyMMddHHmmssfff", //make sure the milliseconds are set in the filename + Layout = "${message}", + MaxArchiveFiles = maxArchiveFiles + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + for (var i = 0; i < 4; ++i) + { + logger.Debug("123456789"); + //build in a sleep to make sure the current time is reflected in the filename + Thread.Sleep(50); + } + //Setting the Configuration to [null] will result in a 'Dump' of the current log entries + LogManager.Configuration = null; // Flush + + var files = Directory.GetFiles(tempDir).OrderBy(s => s); + //we expect 3 archive files, plus one current file + Assert.Equal(maxArchiveFiles + 1, files.Count()); + + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + //writing 50ms later will trigger the cleanup of old archived files + //as stated by the MaxArchiveFiles property, but will only delete the oldest file + Thread.Sleep(50); + logger.Debug("123456789"); + LogManager.Configuration = null; // Flush + + var files2 = Directory.GetFiles(tempDir).OrderBy(s => s); + Assert.Equal(maxArchiveFiles + 1, files2.Count()); + + //the oldest file should be deleted + Assert.DoesNotContain(files.ElementAt(0), files2); + //two files should still be there + Assert.Equal(files.ElementAt(1), files2.ElementAt(0)); + Assert.Equal(files.ElementAt(2), files2.ElementAt(1)); + Assert.Equal(files.ElementAt(3), files2.ElementAt(2)); + //one new file should be created + Assert.DoesNotContain(files2.ElementAt(3), files); + } + finally + { + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + public static IEnumerable DateArchive_UsesDateFromCurrentTimeSource_TestParameters + { + get + { + var maxArchiveDays = false; + var booleanValues = new[] { true, false }; + var timeKindValues = new[] { DateTimeKind.Utc, DateTimeKind.Local }; + return + from timeKind in timeKindValues + from includeDateInLogFilePath in booleanValues + from concurrentWrites in booleanValues + from keepFileOpen in booleanValues + from forceMutexConcurrentWrites in booleanValues + where UniqueBaseAppender(concurrentWrites, keepFileOpen, forceMutexConcurrentWrites) + from includeSequenceInArchive in booleanValues + from forceManaged in booleanValues + select new object[] { timeKind, includeDateInLogFilePath, concurrentWrites, keepFileOpen, includeSequenceInArchive, forceManaged, forceMutexConcurrentWrites, maxArchiveDays }; + } + } + + [Theory] + [MemberData(nameof(DateArchive_UsesDateFromCurrentTimeSource_TestParameters))] + public void DateArchive_UsesDateFromCurrentTimeSource(DateTimeKind timeKind, bool includeDateInLogFilePath, bool concurrentWrites, bool keepFileOpen, bool includeSequenceInArchive, bool forceManaged, bool forceMutexConcurrentWrites, bool maxArhiveDays) + { +#if !NETFRAMEWORK || MONO + if (IsLinux()) + { + Console.WriteLine("[SKIP] FileTargetTests.DateArchive_UsesDateFromCurrentTimeSource because SetLastWriteTime is not working on Travis"); + return; + } +#endif + + const string archiveDateFormat = "yyyyMMdd"; + const int maxArchiveFiles = 3; + + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, includeDateInLogFilePath ? "file_${shortdate}.txt" : "file.txt"); + var defaultTimeSource = TimeSource.Current; + try + { + var timeSource = new ShiftedTimeSource(timeKind); + + TimeSource.Current = timeSource; + + string archiveFolder = Path.Combine(tempDir, "archive"); + string archiveFileNameTemplate = Path.Combine(archiveFolder, "{#}.txt"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = archiveFileNameTemplate, + LineEnding = LineEndingMode.LF, + ArchiveNumbering = includeSequenceInArchive ? ArchiveNumberingMode.DateAndSequence : ArchiveNumberingMode.Date, + ArchiveEvery = FileArchiveEveryPeriod.Day, + ArchiveDateFormat = archiveDateFormat, + Layout = "${date:format=O}|${message}", + MaxArchiveFiles = maxArhiveDays ? 0 : maxArchiveFiles, + MaxArchiveDays = maxArhiveDays ? maxArchiveFiles : 0, + ConcurrentWrites = concurrentWrites, + KeepFileOpen = keepFileOpen, + ForceManaged = forceManaged, + ForceMutexConcurrentWrites = forceMutexConcurrentWrites, + Header = "header", + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("123456789"); + DateTime previousWriteTime = timeSource.Time; + + const int daysToTestLogging = 3; + const int intervalsPerDay = 24; + var loggingInterval = TimeSpan.FromHours(1); + for (var i = 0; i < daysToTestLogging * intervalsPerDay; ++i) + { + timeSource.AddToLocalTime(loggingInterval); + + if (timeSource.Time.Date != previousWriteTime.Date) + { + string currentLogFile = includeDateInLogFilePath + ? logFile.Replace("${shortdate}", timeSource.Time.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)) + : logFile; + // Simulate that previous file write began in previous day and ended on current day. + try + { + File.SetLastWriteTime(currentLogFile, timeSource.SystemTime); + } + catch { } + } + + var eventInfo = new LogEventInfo(LogLevel.Debug, logger.Name, "123456789"); + logger.Log(eventInfo); + LogManager.Flush(); + + var dayIsChanged = eventInfo.TimeStamp.Date != previousWriteTime.Date; + // ensure new archive is created only when the day part of time is changed + var archiveFileName = archiveFileNameTemplate.Replace("{#}", previousWriteTime.ToString(archiveDateFormat) + (includeSequenceInArchive ? ".0" : string.Empty)); + var archiveExists = File.Exists(archiveFileName); + if (dayIsChanged) + Assert.True(archiveExists, + $"new archive should be created when the day part of {timeKind} time is changed"); + else + Assert.False(archiveExists, + $"new archive should not be create when day part of {timeKind} time is unchanged"); + + previousWriteTime = eventInfo.TimeStamp.Date; + if (dayIsChanged) + timeSource.AddToSystemTime(TimeSpan.FromDays(1)); + } + //Setting the Configuration to [null] will result in a 'Dump' of the current log entries + LogManager.Configuration = null; // Flush + + var files = Directory.GetFiles(archiveFolder); + //the amount of archived files may not exceed the set 'MaxArchiveFiles' + Assert.Equal(maxArchiveFiles, files.Length); + + foreach (var file in files) + AssertFileContentsStartsWith(file, "header", Encoding.UTF8); + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + //writing one line on a new day will trigger the cleanup of old archived files + //as stated by the MaxArchiveFiles property, but will only delete the oldest file + timeSource.AddToLocalTime(TimeSpan.FromDays(1)); + logger.Debug("1234567890"); + + LogManager.Configuration = null; // Flush + + var files2 = Directory.GetFiles(archiveFolder); + Assert.Equal(maxArchiveFiles, files2.Length); + + //the oldest file should be deleted + Assert.DoesNotContain(files[0], files2); + //two files should still be there + Assert.Equal(files[1], files2[0]); + Assert.Equal(files[2], files2[1]); + //one new archive file should be created + Assert.DoesNotContain(files2[2], files); + } + finally + { + TimeSource.Current = defaultTimeSource; // restore default time source + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Theory] + [InlineData(DateTimeKind.Utc, false, false)] + [InlineData(DateTimeKind.Local, false, false)] + [InlineData(DateTimeKind.Utc, true, false)] + [InlineData(DateTimeKind.Local, true, false)] + [InlineData(DateTimeKind.Utc, false, true)] + [InlineData(DateTimeKind.Local, false, true)] + [InlineData(DateTimeKind.Utc, true, true)] + [InlineData(DateTimeKind.Local, true, true)] + public void DateArchive_UsesDateFromCurrentTimeSource_MaxArchiveDays(DateTimeKind timeKind, bool includeDateInLogFilePath, bool includeSequenceInArchive) + { + const bool MaxArchiveDays = true; + DateArchive_UsesDateFromCurrentTimeSource(timeKind, includeDateInLogFilePath, false, false, includeSequenceInArchive, false, false, MaxArchiveDays); + } + + public static IEnumerable DateArchive_ArchiveOnceOnly_TestParameters + { + get + { + var booleanValues = new[] { true, false }; + return + from concurrentWrites in booleanValues + from keepFileOpen in booleanValues + from forceMutexConcurrentWrites in booleanValues + where UniqueBaseAppender(concurrentWrites, keepFileOpen, forceMutexConcurrentWrites) + from includeDateInLogFilePath in booleanValues + from includeSequenceInArchive in booleanValues + from forceManaged in booleanValues + select new object[] { concurrentWrites, keepFileOpen, includeDateInLogFilePath, includeSequenceInArchive, forceManaged, forceMutexConcurrentWrites }; + } + } + + [Theory] + [MemberData(nameof(DateArchive_ArchiveOnceOnly_TestParameters))] + public void DateArchive_ArchiveOnceOnly(bool concurrentWrites, bool keepFileOpen, bool dateInLogFilePath, bool includeSequenceInArchive, bool forceManaged, bool forceMutexConcurrentWrites) + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, dateInLogFilePath ? "file_${shortdate}.txt" : "file.txt"); + + var defaultTimeSource = TimeSource.Current; + + try + { + var timeSource = new ShiftedTimeSource(DateTimeKind.Local); + if (timeSource.Time.Minute == 59) + { + // Avoid double-archive due to overflow of the hour. + timeSource.AddToLocalTime(TimeSpan.FromMinutes(1)); + timeSource.AddToSystemTime(TimeSpan.FromMinutes(1)); + } + TimeSource.Current = timeSource; + + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), + LineEnding = LineEndingMode.LF, + ArchiveNumbering = includeSequenceInArchive ? ArchiveNumberingMode.DateAndSequence : ArchiveNumberingMode.Date, + ArchiveEvery = FileArchiveEveryPeriod.Day, + ArchiveDateFormat = "yyyyMMdd", + Layout = "${message}", + ConcurrentWrites = concurrentWrites, + KeepFileOpen = keepFileOpen, + ForceManaged = forceManaged, + ForceMutexConcurrentWrites = forceMutexConcurrentWrites, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("123456789"); + LogManager.Configuration = null; + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + timeSource.AddToLocalTime(TimeSpan.FromDays(1)); + if (!dateInLogFilePath) + File.SetCreationTimeUtc(logFile, timeSource.Time.ToUniversalTime()); + timeSource.AddToLocalTime(TimeSpan.FromDays(1)); + + // This should archive the log before logging. + logger.Debug("123456789"); + + LogManager.Configuration = null; + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + timeSource.AddToSystemTime(TimeSpan.FromDays(2)); // Archive only once + + // This must not archive. + logger.Debug("123456789"); + + LogManager.Configuration = null; // Flush + + Assert.Single(Directory.GetFiles(archiveFolder)); + var prevLogFile = Directory.GetFiles(archiveFolder)[0]; + AssertFileContents(prevLogFile, StringRepeat(1, "123456789\n"), Encoding.UTF8); + + var currentLogFile = Directory.GetFiles(tempDir)[0]; + AssertFileContents(currentLogFile, StringRepeat(2, "123456789\n"), Encoding.UTF8); + } + finally + { + TimeSource.Current = defaultTimeSource; // restore default time source + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + public static IEnumerable DateArchive_SkipPeriod_TestParameters + { + get + { + var timeKindValues = new[] { DateTimeKind.Utc, DateTimeKind.Local }; + var archivePeriodValues = new[] { FileArchiveEveryPeriod.Day, FileArchiveEveryPeriod.Hour }; + var booleanValues = new[] { true, false }; + return + from timeKind in timeKindValues + from archivePeriod in archivePeriodValues + from includeDateInLogFilePath in booleanValues + from includeSequenceInArchive in booleanValues + select new object[] { timeKind, archivePeriod, includeDateInLogFilePath, includeSequenceInArchive }; + } + } + + [Theory] + [MemberData(nameof(DateArchive_SkipPeriod_TestParameters))] + public void DateArchive_SkipPeriod(DateTimeKind timeKind, FileArchiveEveryPeriod archivePeriod, bool includeDateInLogFilePath, bool includeSequenceInArchive) + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, includeDateInLogFilePath ? "file_${date:format=yyyyMMddHHmm}.txt" : "file.txt"); + var defaultTimeSource = TimeSource.Current; + try + { + // Avoid inconsistency in file's last-write-time due to overflow of the minute during test run. + while (DateTime.Now.Second > 55) + Thread.Sleep(1000); + + var timeSource = new ShiftedTimeSource(timeKind); + if (timeSource.Time.Minute == 59) + { + // Avoid double-archive due to overflow of the hour. + timeSource.AddToLocalTime(TimeSpan.FromMinutes(1)); + timeSource.AddToSystemTime(TimeSpan.FromMinutes(1)); + } + TimeSource.Current = timeSource; + + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(tempDir, "archive", "{#}.txt"), + LineEnding = LineEndingMode.LF, + ArchiveNumbering = includeSequenceInArchive ? ArchiveNumberingMode.DateAndSequence : ArchiveNumberingMode.Date, + ArchiveEvery = archivePeriod, + ArchiveDateFormat = "yyyyMMddHHmm", + Layout = "${date:format=O}|${message}", + }; + string archiveDateFormat = fileTarget.ArchiveDateFormat; + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("1234567890"); + timeSource.AddToLocalTime(TimeSpan.FromMinutes(1)); + logger.Debug("1234567890"); + // The archive file name must be based on the last time the file was written. + string archiveFileName = + $"{timeSource.Time.ToString(archiveDateFormat) + (includeSequenceInArchive ? ".0" : string.Empty)}.txt"; + // Effectively update the file's last-write-time. + timeSource.AddToSystemTime(TimeSpan.FromMinutes(1)); + + timeSource.AddToLocalTime(TimeSpan.FromDays(2)); + logger.Debug("1234567890"); + + LogManager.Configuration = null; // Flush + + string archivePath = Path.Combine(tempDir, "archive"); + var archiveFiles = Directory.GetFiles(archivePath); + Assert.Single(archiveFiles); + Assert.Equal(archiveFileName, Path.GetFileName(archiveFiles[0])); + } + finally + { + TimeSource.Current = defaultTimeSource; // restore default time source + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + public static IEnumerable DateArchive_AllLoggersTransferToCurrentLogFile_TestParameters + { + get + { + var booleanValues = new[] { true, false }; + return + from concurrentWrites in booleanValues + from keepFileOpen in booleanValues + from forceMutexConcurrentWrites in booleanValues + where UniqueBaseAppender(concurrentWrites, keepFileOpen, forceMutexConcurrentWrites) + from includeDateInLogFilePath in booleanValues + from includeSequenceInArchive in booleanValues + from enableArchiveCompression in booleanValues + from forceManaged in booleanValues + select new object[] { concurrentWrites, keepFileOpen, includeDateInLogFilePath, includeSequenceInArchive, enableArchiveCompression, forceManaged, forceMutexConcurrentWrites }; + } + } + + [Theory] + [MemberData(nameof(DateArchive_AllLoggersTransferToCurrentLogFile_TestParameters))] + public void DateArchive_AllLoggersTransferToCurrentLogFile(bool concurrentWrites, bool keepFileOpen, bool includeDateInLogFilePath, bool includeSequenceInArchive, bool enableArchiveCompression, bool forceManaged, bool forceMutexConcurrentWrites) + { + if (keepFileOpen && !concurrentWrites) + return; // This combination do not support two local FileTargets to the same file + +#if NET35 || NET40 + if (enableArchiveCompression) + return; // No need to test with compression +#endif + + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logfile = Path.Combine(tempDir, includeDateInLogFilePath ? "file_${shortdate}.txt" : "file.txt"); + var defaultTimeSource = TimeSource.Current; + +#if NET35 || NET40 + IFileCompressor fileCompressor = null; +#endif + + try + { + var timeSource = new ShiftedTimeSource(DateTimeKind.Local); + if (timeSource.Time.Minute == 59) + { + // Avoid double-archive due to overflow of the hour. + timeSource.AddToLocalTime(TimeSpan.FromMinutes(1)); + timeSource.AddToSystemTime(TimeSpan.FromMinutes(1)); + } + TimeSource.Current = timeSource; + + var config = new LoggingConfiguration(); + +#if NET35 || NET40 + if (enableArchiveCompression) + { + fileCompressor = FileTarget.FileCompressor; + FileTarget.FileCompressor = new CustomFileCompressor(); + } +#endif + + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget1 = new ConcurrentFileTarget + { + FileName = logfile, + ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), + LineEnding = LineEndingMode.LF, + ArchiveNumbering = includeSequenceInArchive ? ArchiveNumberingMode.DateAndSequence : ArchiveNumberingMode.Date, + ArchiveEvery = FileArchiveEveryPeriod.Day, + ArchiveDateFormat = "yyyyMMdd", + EnableArchiveFileCompression = enableArchiveCompression, + Layout = "${message}", + ConcurrentWrites = concurrentWrites, + KeepFileOpen = keepFileOpen, + ForceManaged = forceManaged, + ForceMutexConcurrentWrites = forceMutexConcurrentWrites, + }; + var logger1Rule = new LoggingRule("logger1", LogLevel.Debug, fileTarget1); + config.LoggingRules.Add(logger1Rule); + + var fileTarget2 = new ConcurrentFileTarget + { + FileName = logfile, + ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), + LineEnding = LineEndingMode.LF, + ArchiveNumbering = includeSequenceInArchive ? ArchiveNumberingMode.DateAndSequence : ArchiveNumberingMode.Date, + ArchiveEvery = FileArchiveEveryPeriod.Day, + ArchiveDateFormat = "yyyyMMdd", + EnableArchiveFileCompression = enableArchiveCompression, + Layout = "${message}", + ConcurrentWrites = concurrentWrites, + KeepFileOpen = keepFileOpen, + ForceManaged = forceManaged, + ForceMutexConcurrentWrites = forceMutexConcurrentWrites, + }; + var logger2Rule = new LoggingRule("logger2", LogLevel.Debug, fileTarget2); + config.LoggingRules.Add(logger2Rule); + + LogManager.Configuration = config; + + var logger1 = LogManager.GetLogger("logger1"); + var logger2 = LogManager.GetLogger("logger2"); + + logger1.Debug("123456789"); + logger2.Debug("123456789"); + LogManager.Flush(); + + timeSource.AddToLocalTime(TimeSpan.FromDays(1)); + + // This should archive the log before logging. + logger1.Debug("123456789"); + + timeSource.AddToSystemTime(TimeSpan.FromDays(1)); // Archive only once + + Thread.Sleep(10); + logger2.Debug("123456789"); + + LogManager.Configuration = null; // Flush + + var files = Directory.GetFiles(archiveFolder); + Assert.Single(files); + if (!enableArchiveCompression) + { + string prevLogFile = Directory.GetFiles(archiveFolder)[0]; + AssertFileContents(prevLogFile, StringRepeat(2, "123456789\n"), Encoding.UTF8); + } + string currentLogFile = Directory.GetFiles(tempDir)[0]; + AssertFileContents(currentLogFile, StringRepeat(2, "123456789\n"), Encoding.UTF8); + } + finally + { + TimeSource.Current = defaultTimeSource; // restore default time source + +#if NET35 || NET40 + if (enableArchiveCompression) + { + FileTarget.FileCompressor = fileCompressor; + } +#endif + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void DeleteArchiveFilesByDate_MaxArchiveFiles_0() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), + ArchiveAboveSize = 50, + LineEnding = LineEndingMode.LF, + ArchiveNumbering = ArchiveNumberingMode.Date, + ArchiveDateFormat = "yyyyMMddHHmmssfff", //make sure the milliseconds are set in the filename + Layout = "${message}", + MaxArchiveFiles = 0 + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + //writing 19 times 10 bytes (9 char + linefeed) will result in 3 archive files and 1 current file + for (var i = 0; i < 19; ++i) + { + logger.Debug("123456789"); + //build in a small sleep to make sure the current time is reflected in the filename + //do this every 5 entries + if (i % 5 == 0) + { + Thread.Sleep(50); + } + } + + //Setting the Configuration to [null] will result in a 'Dump' of the current log entries + LogManager.Configuration = null; // Flush + + var fileCount = Directory.EnumerateFiles(archiveFolder).Count(); + + Assert.Equal(3, fileCount); + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + //create 1 new file for archive + logger.Debug("1234567890"); + LogManager.Configuration = null; + + var fileCount2 = Directory.EnumerateFiles(archiveFolder).Count(); + //there should be 1 more file + Assert.Equal(4, fileCount2); + } + finally + { + if (File.Exists(logFile)) + { + File.Delete(logFile); + } + + if (Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, true); + } + } + } + + [Fact] + public void DeleteArchiveFilesByDate_AlteredMaxArchive() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), + ArchiveAboveSize = 50, + LineEnding = LineEndingMode.LF, + ArchiveNumbering = ArchiveNumberingMode.Date, + ArchiveDateFormat = "yyyyMMddHHmmssfff", //make sure the milliseconds are set in the filename + Layout = "${message}", + MaxArchiveFiles = 5 + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + //writing 29 times 10 bytes (9 char + linefeed) will result in 3 archive files and 1 current file + for (var i = 0; i < 29; ++i) + { + logger.Debug("123456789"); + //build in a small sleep to make sure the current time is reflected in the filename + //do this every 5 entries + if (i % 5 == 0) + Thread.Sleep(50); + } + //Setting the Configuration to [null] will result in a 'Dump' of the current log entries + LogManager.Configuration = null; + + var files = Directory.GetFiles(archiveFolder).OrderBy(s => s); + //the amount of archived files may not exceed the set 'MaxArchiveFiles' + Assert.Equal(fileTarget.MaxArchiveFiles, files.Count()); + + //alter the MaxArchivedFiles + fileTarget.MaxArchiveFiles = 2; + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + //writing just one line of 11 bytes will trigger the cleanup of old archived files + //as stated by the MaxArchiveFiles property, but will only delete the oldest files + logger.Debug("1234567890"); + LogManager.Configuration = null; // Flush + + var files2 = Directory.GetFiles(archiveFolder).OrderBy(s => s); + Assert.Equal(fileTarget.MaxArchiveFiles, files2.Count()); + + //the oldest files should be deleted + Assert.DoesNotContain(files.ElementAt(0), files2); + Assert.DoesNotContain(files.ElementAt(1), files2); + Assert.DoesNotContain(files.ElementAt(2), files2); + Assert.DoesNotContain(files.ElementAt(3), files2); + //one files should still be there + Assert.Equal(files.ElementAt(4), files2.ElementAt(0)); + //one new archive file should be created + Assert.DoesNotContain(files2.ElementAt(1), files); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void RepeatingHeaderTest() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + const string header = "Headerline"; + + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveAboveSize = 51, + LineEnding = LineEndingMode.LF, + ArchiveNumbering = ArchiveNumberingMode.Sequence, + Layout = "${message}", + Header = header, + MaxArchiveFiles = 2, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + // Writing 16 times 10 bytes = 160 bytes = 3 files + for (var i = 0; i < 16; ++i) + { + logger.Debug("123456789"); + } + + LogManager.Configuration = null; // Flush + + AssertFileContentsStartsWith(logFile, header, Encoding.UTF8); + + AssertFileContentsStartsWith(Path.Combine(archiveFolder, "0002.txt"), header, Encoding.UTF8); + + AssertFileContentsStartsWith(Path.Combine(archiveFolder, "0001.txt"), header, Encoding.UTF8); + + Assert.False(File.Exists(Path.Combine(archiveFolder, "0000.txt"))); // MaxArchiveFiles = 2 (Removes the first file) + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RepeatingFooterTest(bool writeFooterOnArchivingOnly) + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + const string footer = "Footerline"; + + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveAboveSize = 51, + LineEnding = LineEndingMode.LF, + ArchiveNumbering = ArchiveNumberingMode.Sequence, + Layout = "${message}", + Footer = footer, + MaxArchiveFiles = 2, + WriteFooterOnArchivingOnly = writeFooterOnArchivingOnly + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + // Writing 16 times 10 bytes = 160 bytes = 3 files + for (var i = 0; i < 16; ++i) + { + logger.Debug("123456789"); + } + + LogManager.Configuration = null; // Flush + + string expectedEnding = footer + fileTarget.LineEnding.NewLineCharacters; + if (writeFooterOnArchivingOnly) + Assert.False(File.ReadAllText(logFile).EndsWith(expectedEnding), "Footer was unexpectedly written to log file."); + else + AssertFileContentsEndsWith(logFile, expectedEnding, Encoding.UTF8); + AssertFileContentsEndsWith(Path.Combine(archiveFolder, "0002.txt"), expectedEnding, Encoding.UTF8); + AssertFileContentsEndsWith(Path.Combine(archiveFolder, "0001.txt"), expectedEnding, Encoding.UTF8); + Assert.False(File.Exists(Path.Combine(archiveFolder, "0000.txt"))); // MaxArchiveFiles = 2 (Removes the first file) + } + finally + { + LogManager.Configuration = null; + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WriteHeaderOnStartupTest(bool writeHeaderWhenInitialFileNotEmpty) + { + var logFile = Path.GetTempFileName() + ".txt"; + try + { + const string header = "Headerline"; + + // Configure first time + var fileTarget = new ConcurrentFileTarget + { + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${message}", + Header = header, + WriteBom = true, + WriteHeaderWhenInitialFileNotEmpty = true + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + + LogManager.Configuration = null; + + string headerPart = header + LineEndingMode.LF.NewLineCharacters; + string logPart = "aaa\nbbb\nccc\n"; + AssertFileContents(logFile, headerPart + logPart, Encoding.UTF8, addBom: true); + + // Configure second time + fileTarget = new ConcurrentFileTarget + { + FileName = Layout.FromLiteral(logFile), + LineEnding = LineEndingMode.LF, + Layout = "${message}", + Header = header, + WriteBom = true, + WriteHeaderWhenInitialFileNotEmpty = writeHeaderWhenInitialFileNotEmpty + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + + LogManager.Configuration = null; // Flush + + if (writeHeaderWhenInitialFileNotEmpty) + AssertFileContents(logFile, headerPart + logPart + headerPart + logPart, Encoding.UTF8, addBom: true); + else + AssertFileContents(logFile, headerPart + logPart + logPart, Encoding.UTF8, addBom: true); + } + finally + { + LogManager.Configuration = null; + if (File.Exists(logFile)) + File.Delete(logFile); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RollingArchiveTest(bool specifyArchiveFileName) + { + RollingArchiveTests(enableCompression: false, specifyArchiveFileName: specifyArchiveFileName); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RollingArchiveCompressionTest(bool specifyArchiveFileName) + { + RollingArchiveTests(enableCompression: true, specifyArchiveFileName: specifyArchiveFileName); + } + + private void RollingArchiveTests(bool enableCompression, bool specifyArchiveFileName) + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + var archiveExtension = enableCompression ? "zip" : "txt"; + +#if NET35 || NET40 + IFileCompressor fileCompressor = null; +#endif + + try + { + var fileTarget = new ConcurrentFileTarget + { + EnableArchiveFileCompression = enableCompression, + FileName = logFile, + ArchiveAboveSize = 100, + LineEnding = LineEndingMode.LF, + ArchiveNumbering = ArchiveNumberingMode.Rolling, + Layout = "${message}", + MaxArchiveFiles = 3 + }; + +#if NET35 || NET40 + if (enableCompression) + { + fileCompressor = FileTarget.FileCompressor; + FileTarget.FileCompressor = new CustomFileCompressor(); + } +#endif + + if (specifyArchiveFileName) + fileTarget.ArchiveFileName = Path.Combine(tempDir, "archive", "{####}." + archiveExtension); + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + // we emit 5 * 25 * (3 x aaa + \n) bytes + // so that we should get a full file + 3 archives + Generate100BytesLog('a'); + Generate100BytesLog('b'); + Generate100BytesLog('c'); + Generate100BytesLog('d'); + Generate100BytesLog('e'); + + LogManager.Configuration = null; // Flush + + Action assertFileContents = (f1, f2, content, encoding) => AssertFileContents(f1, content, encoding, false); + if (enableCompression) + assertFileContents = AssertZipFileContents; + + var times = 25; + AssertFileContents(logFile, + StringRepeat(times, "eee\n"), + Encoding.UTF8); + + string archiveFileNameFormat = specifyArchiveFileName + ? Path.Combine("archive", "000{0}." + archiveExtension) + : "file.{0}." + archiveExtension; + + assertFileContents( + Path.Combine(tempDir, string.Format(archiveFileNameFormat, 0)), + "file.txt", + StringRepeat(times, "ddd\n"), + Encoding.UTF8); + + assertFileContents( + Path.Combine(tempDir, string.Format(archiveFileNameFormat, 1)), + "file.txt", + StringRepeat(times, "ccc\n"), + Encoding.UTF8); + + assertFileContents( + Path.Combine(tempDir, string.Format(archiveFileNameFormat, 2)), + "file.txt", + StringRepeat(times, "bbb\n"), + Encoding.UTF8); + + Assert.False(File.Exists(Path.Combine(tempDir, string.Format(archiveFileNameFormat, 3)))); + } + finally + { +#if NET35 || NET40 + if (enableCompression) + { + FileTarget.FileCompressor = fileCompressor; + } +#endif + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [InlineData("/")] + [InlineData("\\")] + [Theory] + public void RollingArchiveTest_MaxArchiveFiles_0(string slash) + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(tempDir, "archive" + slash + "{####}.txt"), + ArchiveAboveSize = 100, + LineEnding = LineEndingMode.LF, + ArchiveNumbering = ArchiveNumberingMode.Rolling, + Layout = "${message}", + MaxArchiveFiles = 0 + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + // we emit 5 * 25 * (3 x aaa + \n) bytes + // so that we should get a full file + 4 archives + Generate100BytesLog('a'); + Generate100BytesLog('b'); + Generate100BytesLog('c'); + Generate100BytesLog('d'); + Generate100BytesLog('e'); + + LogManager.Configuration = null; // Flush + + var times = 25; + AssertFileContents(logFile, + StringRepeat(times, "eee\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(tempDir, "archive" + slash + "0000.txt"), + StringRepeat(times, "ddd\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(tempDir, "archive" + slash + "0001.txt"), + StringRepeat(times, "ccc\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(tempDir, "archive" + slash + "0002.txt"), + StringRepeat(times, "bbb\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(tempDir, "archive" + slash + "0003.txt"), + StringRepeat(times, "aaa\n"), + Encoding.UTF8); + } + finally + { + + if (File.Exists(logFile)) + { + File.Delete(logFile); + } + + if (Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, true); + } + } + } + + [Fact] + public void MultiFileWrite() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = Path.Combine(tempDir, "${level}.txt"), + LineEnding = LineEndingMode.LF, + Layout = "${message}" + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger(LogLevel.Debug).WriteTo(fileTarget)); + + var times = 25; + for (var i = 0; i < times; ++i) + { + logger.Trace("@@@"); + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + logger.Error("ddd"); + logger.Fatal("eee"); + } + + LogManager.Configuration = null; // Flush + + Assert.False(File.Exists(Path.Combine(tempDir, "Trace.txt"))); + + AssertFileContents(Path.Combine(tempDir, "Debug.txt"), + StringRepeat(times, "aaa\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Info.txt"), + StringRepeat(times, "bbb\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Warn.txt"), + StringRepeat(times, "ccc\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Error.txt"), + StringRepeat(times, "ddd\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Fatal.txt"), + StringRepeat(times, "eee\n"), Encoding.UTF8); + } + finally + { + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void BufferedMultiFileWrite() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = Path.Combine(tempDir, "${level}.txt"), + LineEnding = LineEndingMode.LF, + Layout = "${message}" + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger(LogLevel.Debug).WriteTo(new BufferingTargetWrapper(fileTarget, 10))); + + var times = 25; + for (var i = 0; i < times; ++i) + { + logger.Trace("@@@"); + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + logger.Error("ddd"); + logger.Fatal("eee"); + } + + LogManager.Configuration = null; // Flush + + Assert.False(File.Exists(Path.Combine(tempDir, "Trace.txt"))); + + AssertFileContents(Path.Combine(tempDir, "Debug.txt"), + StringRepeat(times, "aaa\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Info.txt"), + StringRepeat(times, "bbb\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Warn.txt"), + StringRepeat(times, "ccc\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Error.txt"), + StringRepeat(times, "ddd\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Fatal.txt"), + StringRepeat(times, "eee\n"), Encoding.UTF8); + } + finally + { + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void AsyncMultiFileWrite() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = Path.Combine(tempDir, "${level}.txt"), + LineEnding = LineEndingMode.LF, + Layout = "${message} ${threadid}" + }; + + // this also checks that thread-volatile layouts + // such as ${threadid} are properly cached and not recalculated + // in logging threads. + var threadID = Thread.CurrentThread.ManagedThreadId.ToString(); + + LogManager.Setup().LoadConfiguration(c => c.ForLogger(LogLevel.Debug).WriteTo(fileTarget).WithAsync()); + + var times = 25; + for (var i = 0; i < times; ++i) + { + logger.Trace("@@@"); + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + logger.Error("ddd"); + logger.Fatal("eee"); + } + + LogManager.Configuration = null; // Flush + + Assert.False(File.Exists(Path.Combine(tempDir, "Trace.txt"))); + + AssertFileContents(Path.Combine(tempDir, "Debug.txt"), + StringRepeat(times, "aaa " + threadID + "\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Info.txt"), + StringRepeat(times, "bbb " + threadID + "\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Warn.txt"), + StringRepeat(times, "ccc " + threadID + "\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Error.txt"), + StringRepeat(times, "ddd " + threadID + "\n"), Encoding.UTF8); + + AssertFileContents(Path.Combine(tempDir, "Fatal.txt"), + StringRepeat(times, "eee " + threadID + "\n"), Encoding.UTF8); + } + finally + { + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void DisposingFileTarget_WhenNotIntialized_ShouldNotThrow() + { + var exceptionThrown = false; + var fileTarget = new ConcurrentFileTarget(); + + try + { + fileTarget.Dispose(); + } + catch + { + exceptionThrown = true; + } + + Assert.False(exceptionThrown); + } + + [Fact] + public void FileTarget_ArchiveNumbering_DateAndSequence() + { + FileTarget_ArchiveNumbering_DateAndSequenceTests(enableCompression: false, fileTxt: "file.txt", archiveFileName: Path.Combine("archive", "{#}.txt")); + } + + [Fact] + public void FileTarget_ArchiveNumbering_DateAndSequence_archive_same_as_log_name() + { + FileTarget_ArchiveNumbering_DateAndSequenceTests(enableCompression: false, fileTxt: "file-${date:format=yyyy-MM-dd}.txt", archiveFileName: "file-{#}.txt"); + } + + [Fact] + public void FileTarget_ArchiveNumbering_DateAndSequence_WithCompression() + { + FileTarget_ArchiveNumbering_DateAndSequenceTests(enableCompression: true, fileTxt: "file.txt", archiveFileName: Path.Combine("archive", "{#}.zip")); + } + + private void FileTarget_ArchiveNumbering_DateAndSequenceTests(bool enableCompression, string fileTxt, string archiveFileName) + { + const string archiveDateFormat = "yyyy-MM-dd"; + const int archiveAboveSize = 100; + + var tempDir = ArchiveFileNameHelper.GenerateTempPath(); + Layout logFile = Path.Combine(tempDir, fileTxt); + var logFileName = logFile.Render(LogEventInfo.CreateNullEvent()); + +#if NET35 || NET40 + IFileCompressor fileCompressor = null; +#endif + + try + { + var fileTarget = new ConcurrentFileTarget + { + EnableArchiveFileCompression = enableCompression, + FileName = logFile, + ArchiveFileName = Path.Combine(tempDir, archiveFileName), + ArchiveDateFormat = archiveDateFormat, + ArchiveAboveSize = archiveAboveSize, + LineEnding = LineEndingMode.LF, + Layout = "${message}", + MaxArchiveFiles = 3, + ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, + ArchiveEvery = FileArchiveEveryPeriod.Day + }; + + +#if NET35 || NET40 + if (enableCompression) + { + fileCompressor = FileTarget.FileCompressor; + FileTarget.FileCompressor = new CustomFileCompressor(); + } +#endif + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + // we emit 5 * 25 *(3 x aaa + \n) bytes + // so that we should get a full file + 3 archives + Generate100BytesLog('a'); + Generate100BytesLog('b'); + Generate100BytesLog('c'); + Generate100BytesLog('d'); + Generate100BytesLog('e'); + + string renderedArchiveFileName = archiveFileName.Replace("{#}", DateTime.Now.ToString(archiveDateFormat)); + + LogManager.Configuration = null; + + Action assertFileContents = (f1, f2, content, encoding) => AssertFileContents(f1, content, encoding, false); + if (enableCompression) + assertFileContents = AssertZipFileContents; + + var extension = Path.GetExtension(renderedArchiveFileName); + var fileNameWithoutExt = renderedArchiveFileName.Substring(0, renderedArchiveFileName.Length - extension.Length); + ArchiveFileNameHelper helper = new ArchiveFileNameHelper(tempDir, fileNameWithoutExt, extension); + + var times = 25; + AssertFileContents(logFileName, + StringRepeat(times, "eee\n"), + Encoding.UTF8); + +#if !NET35 + string expectedEntry1Name = Path.GetFileNameWithoutExtension(helper.GetFullPath(1)) + ".txt"; + string expectedEntry2Name = Path.GetFileNameWithoutExtension(helper.GetFullPath(2)) + ".txt"; + string expectedEntry3Name = Path.GetFileNameWithoutExtension(helper.GetFullPath(3)) + ".txt"; +#else + string expectedEntry1Name = fileTxt; + string expectedEntry2Name = fileTxt; + string expectedEntry3Name = fileTxt; +#endif + assertFileContents(helper.GetFullPath(1), expectedEntry1Name, StringRepeat(times, "bbb\n"), Encoding.UTF8); + assertFileContents(helper.GetFullPath(2), expectedEntry2Name, StringRepeat(times, "ccc\n"), Encoding.UTF8); + assertFileContents(helper.GetFullPath(3), expectedEntry3Name, StringRepeat(times, "ddd\n"), Encoding.UTF8); + + Assert.False(helper.Exists(0), "First archive should have been deleted due to max archive count."); + Assert.False(helper.Exists(4), "Fifth archive must not have been created yet."); + } + finally + { +#if NET35 || NET40 + if (enableCompression) + { + FileTarget.FileCompressor = fileCompressor; + } +#endif + + if (File.Exists(logFileName)) + File.Delete(logFileName); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Theory] + [InlineData("archive/test.log.{####}", "archive/test.log.0000", ArchiveNumberingMode.Sequence)] + [InlineData("archive\\test.log.{####}", "archive\\test.log.0000", ArchiveNumberingMode.Sequence)] + [InlineData("file-${date:format=yyyyMMdd}.txt", "file-${date:format=yyyyMMdd}.txt", ArchiveNumberingMode.Sequence)] + [InlineData("file-{#}.txt", "file-${date:format=yyyyMMdd}.txt", ArchiveNumberingMode.Date)] + public void FileTargetArchiveFileNameTest(string archiveFileName, string expectedArchiveFileName, ArchiveNumberingMode archiveNumbering) + { + var subPath = "nlog_" + Guid.NewGuid().ToString(); + var tempDir = Path.Combine(Path.GetTempPath(), subPath); + var logFile = Path.Combine(tempDir, "file-${date:format=yyyyMMdd}.txt"); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(tempDir, "..", subPath, archiveFileName), + ArchiveNumbering = archiveNumbering, + ArchiveAboveSize = 1000, + MaxArchiveFiles = 1000, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + for (var i = 0; i < 25; ++i) + { + logger.Debug("a"); + } + + LogManager.Configuration = null; + + logFile = new SimpleLayout(logFile).Render(LogEventInfo.CreateNullEvent()); + expectedArchiveFileName = new SimpleLayout(expectedArchiveFileName).Render(LogEventInfo.CreateNullEvent()); + + Assert.True(File.Exists(logFile)); + Assert.True(File.Exists(Path.Combine(tempDir, expectedArchiveFileName))); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void FileTarget_InvalidFileNameCorrection() + { + var tempFile = Path.GetTempFileName(); + var invalidLogFileName = tempFile + Path.GetInvalidFileNameChars()[0]; + var expectedCorrectedTempFile = tempFile + "_"; + + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = Layout.FromLiteral(invalidLogFileName), + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + OpenFileCacheTimeout = 0 + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Fatal("aaa"); + + LogManager.Configuration = null; // Flush + + AssertFileContents(expectedCorrectedTempFile, "Fatal aaa\n", Encoding.UTF8); + } + finally + { + if (File.Exists(expectedCorrectedTempFile)) + File.Delete(expectedCorrectedTempFile); + if (File.Exists(invalidLogFileName)) + File.Delete(invalidLogFileName); + } + } + + [Fact] + public void FileTarget_LogAndArchiveFilesWithSameName_ShouldArchive() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "Application.log"); + var tempDirectory = new DirectoryInfo(tempDir); + try + { + + var archiveFile = Path.Combine(tempDir, "Application{#}.log"); + var archiveFileMask = "Application*.log"; + + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = archiveFile, + ArchiveAboveSize = 1, //Force immediate archival + ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, + MaxArchiveFiles = 5 + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + //Creates 5 archive files. + for (int i = 0; i <= 5; i++) + { + logger.Debug("a"); + } + + LogManager.Configuration = null; // Flush + + Assert.True(File.Exists(logFile)); + + //Five archive files, plus the log file itself. + Assert.True(tempDirectory.GetFiles(archiveFileMask).Length == 5 + 1); + } + finally + { + if (tempDirectory.Exists) + { + tempDirectory.Delete(true); + } + } + } + + [Fact] + public void FileTarget_Handle_Other_Files_That_Match_Archive_Format() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "Application.log"); + var tempDirectory = new DirectoryInfo(tempDir); + + try + { + string archiveFileLayout = Path.Combine(Path.GetDirectoryName(logFile), Path.GetFileNameWithoutExtension(logFile) + "{#}" + Path.GetExtension(logFile)); + + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + Layout = "${message}", + EnableFileDelete = false, + Encoding = Encoding.UTF8, + ArchiveFileName = archiveFileLayout, + ArchiveEvery = FileArchiveEveryPeriod.Day, + ArchiveNumbering = ArchiveNumberingMode.Date, + ArchiveDateFormat = "___________yyyyMMddHHmm", + MaxArchiveFiles = 10 // Get past the optimization to avoid deleting old files. + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + string existingFile = archiveFileLayout.Replace("{#}", "notadate"); + Directory.CreateDirectory(Path.GetDirectoryName(logFile)); + File.Create(existingFile).Close(); + + logger.Debug("test"); + + LogManager.Configuration = null; // Flush + + AssertFileContents(logFile, "test" + LineEndingMode.Default.NewLineCharacters, Encoding.UTF8); + Assert.True(File.Exists(existingFile)); + } + finally + { + if (tempDirectory.Exists) + { + tempDirectory.Delete(true); + } + } + } + + [Fact] + public void SingleArchiveFileRollsCorrectly() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(tempDir, "archive", "file.txt2"), + ArchiveAboveSize = 100, + LineEnding = LineEndingMode.LF, + Layout = "${message}", + MaxArchiveFiles = 1, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + // we emit 2 * 25 *(aaa + \n) bytes + // so that we should get a full file + 1 archives + var times = 25; + for (var i = 0; i < times; ++i) + { + logger.Debug("aaa"); + } + for (var i = 0; i < times; ++i) + { + logger.Debug("bbb"); + } + + LogManager.Flush(); + + AssertFileContents(logFile, + StringRepeat(times, "bbb\n"), + Encoding.UTF8); + AssertFileContents( + Path.Combine(tempDir, "archive", "file.txt2"), + StringRepeat(times, "aaa\n"), + Encoding.UTF8); + + for (var i = 0; i < times; ++i) + { + logger.Debug("ccc"); + } + + LogManager.Configuration = null; // Flush + + AssertFileContents(logFile, + StringRepeat(times, "ccc\n"), + Encoding.UTF8); + AssertFileContents( + Path.Combine(tempDir, "archive", "file.txt2"), + StringRepeat(times, "bbb\n"), + Encoding.UTF8); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void ArchiveFileRollsCorrectly() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(tempDir, "archive", "file.txt2"), + ArchiveAboveSize = 100, + LineEnding = LineEndingMode.LF, + Layout = "${message}", + MaxArchiveFiles = 2, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + // we emit 3 * 25 *(aaa + \n) bytes + // so that we should get a full file + 2 archives + var times = 25; + for (var i = 0; i < times; ++i) + { + logger.Debug("aaa"); + } + for (var i = 0; i < times; ++i) + { + logger.Debug("bbb"); + } + for (var i = 0; i < times; ++i) + { + logger.Debug("ccc"); + } + + LogManager.Flush(); + + AssertFileContents(logFile, + StringRepeat(times, "ccc\n"), + Encoding.UTF8); + AssertFileContents( + Path.Combine(tempDir, "archive", "file.1.txt2"), + StringRepeat(times, "bbb\n"), + Encoding.UTF8); + AssertFileContents( + Path.Combine(tempDir, "archive", "file.txt2"), + StringRepeat(times, "aaa\n"), + Encoding.UTF8); + + for (var i = 0; i < times; ++i) + { + logger.Debug("ddd"); + } + + LogManager.Configuration = null; // Flush + + AssertFileContents(logFile, + StringRepeat(times, "ddd\n"), + Encoding.UTF8); + AssertFileContents( + Path.Combine(tempDir, "archive", "file.2.txt2"), + StringRepeat(times, "ccc\n"), + Encoding.UTF8); + AssertFileContents( + Path.Combine(tempDir, "archive", "file.1.txt2"), + StringRepeat(times, "bbb\n"), + Encoding.UTF8); + Assert.False(File.Exists(Path.Combine(tempDir, "archive", "file.txt2"))); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void ArchiveFileRollsCorrectly_ExistingArchives() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + Directory.CreateDirectory(Path.Combine(tempDir, "archive")); + File.Create(Path.Combine(tempDir, "archive", "file.10.txt2")).Dispose(); + File.Create(Path.Combine(tempDir, "archive", "file.9.txt2")).Dispose(); + + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(tempDir, "archive", "file.txt2"), + ArchiveAboveSize = 100, + LineEnding = LineEndingMode.LF, + Layout = "${message}", + MaxArchiveFiles = 2, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + // we emit 2 * 25 *(aaa + \n) bytes + // so that we should get a full file + 1 archive + var times = 25; + for (var i = 0; i < times; ++i) + { + logger.Debug("aaa"); + } + for (var i = 0; i < times; ++i) + { + logger.Debug("bbb"); + } + + LogManager.Configuration = null; // Flush + + AssertFileContents(logFile, + StringRepeat(times, "bbb\n"), + Encoding.UTF8); + AssertFileContents( + Path.Combine(tempDir, "archive", "file.11.txt2"), + StringRepeat(times, "aaa\n"), + Encoding.UTF8); + Assert.True(File.Exists(Path.Combine(tempDir, "archive", "file.10.txt2"))); + Assert.False(File.Exists(Path.Combine(tempDir, "archive", "file.9.txt2"))); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + /// + /// Remove archived files in correct order + /// + [Fact] + public void FileTarget_ArchiveNumbering_remove_correct_order() + { + const int maxArchiveFiles = 10; + + var tempDir = ArchiveFileNameHelper.GenerateTempPath(); + var logFile = Path.Combine(tempDir, "file.txt"); + var archiveExtension = "txt"; + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(tempDir, "archive", "{#}." + archiveExtension), + ArchiveDateFormat = "yyyy-MM-dd", + ArchiveAboveSize = 100, + LineEnding = LineEndingMode.LF, + Layout = "${message}", + MaxArchiveFiles = maxArchiveFiles, + ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + ArchiveFileNameHelper helper = new ArchiveFileNameHelper(Path.Combine(tempDir, "archive"), DateTime.Now.ToString(fileTarget.ArchiveDateFormat), archiveExtension); + + Generate100BytesLog('a'); + + for (int i = 0; i < maxArchiveFiles; i++) + { + Generate100BytesLog('a'); + Assert.True(helper.Exists(i), $"file {i} is missing"); + } + + for (int i = maxArchiveFiles; i < 21; i++) + { + Generate100BytesLog('b'); + var numberToBeRemoved = i - maxArchiveFiles; // number 11, we need to remove 1 etc + Assert.False(helper.Exists(numberToBeRemoved), + $"archive file {numberToBeRemoved} has not been removed! We are created file {i}"); + } + + LogManager.Configuration = null; + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + /// + /// Allow multiple archives within the same directory + /// + [Fact] + public void FileTarget_ArchiveNumbering_remove_correct_wildcard() + { + const int maxArchiveFiles = 5; + + var tempDir = ArchiveFileNameHelper.GenerateTempPath(); + var logFile = Path.Combine(tempDir, "{0}{1}.txt"); + + var defaultTimeSource = TimeSource.Current; + try + { + var timeSource = new ShiftedTimeSource(DateTimeKind.Local); + if (timeSource.Time.Minute == 59) + { + // Avoid double-archive due to overflow of the hour. + timeSource.AddToLocalTime(TimeSpan.FromMinutes(1)); + timeSource.AddToSystemTime(TimeSpan.FromMinutes(1)); + } + TimeSource.Current = timeSource; + + var fileTarget = new ConcurrentFileTarget + { + FileName = string.Format(logFile, "${logger}", "${shortdate}"), + ArchiveAboveSize = 100, + LineEnding = LineEndingMode.LF, + Layout = "${message}", + MaxArchiveFiles = maxArchiveFiles, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + var logger1 = LogManager.GetLogger("log"); + var logger2 = LogManager.GetLogger("log-other"); + + timeSource.AddToLocalTime(TimeSpan.Zero - TimeSpan.FromDays(1)); + + Generate100BytesLog((char)('0'), logger1); + Generate100BytesLog((char)('0'), logger2); + + for (int i = 0; i <= maxArchiveFiles - 3; i++) + { + Generate100BytesLog((char)('1' + i), logger1); + Generate100BytesLog((char)('1' + i), logger2); + var logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd")); + var logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd")); + Assert.True(File.Exists(logFile1), + $"{logFile1} is missing"); + Assert.True(File.Exists(logFile2), + $"{logFile2} is missing"); + logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); + logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); + Assert.True(File.Exists(logFile1), + $"{logFile1} is missing"); + Assert.True(File.Exists(logFile2), + $"{logFile2} is missing"); + } + + TimeSource.Current = defaultTimeSource; // restore default time source + Generate100BytesLog((char)('a'), logger1); + Generate100BytesLog((char)('a'), logger2); + for (int i = 0; i < maxArchiveFiles; i++) + { + Generate100BytesLog((char)('b' + i), logger1); + Generate100BytesLog((char)('b' + i), logger2); + var logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); + var logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); + Assert.True(File.Exists(logFile1), + $"{logFile1} is missing"); + Assert.True(File.Exists(logFile2), + $"{logFile2} is missing"); + } + + for (int i = maxArchiveFiles; i < 10; i++) + { + Generate100BytesLog((char)('b' + i), logger1); + Generate100BytesLog((char)('b' + i), logger2); + var numberToBeRemoved = i - maxArchiveFiles; + + var logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + numberToBeRemoved.ToString()); + var logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + numberToBeRemoved.ToString()); + + Assert.False(File.Exists(logFile1), + $"archive FirstFile {numberToBeRemoved} has not been removed! We are created file {i}"); + Assert.False(File.Exists(logFile2), + $"archive SecondFile {numberToBeRemoved} has not been removed! We are created file {i}"); + + logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); + logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); + + Assert.True(File.Exists(logFile1), + $"{logFile1} is missing"); + Assert.True(File.Exists(logFile2), + $"{logFile2} is missing"); + } + + // Verify that archieve-cleanup after startup handles same folder archive correctly + fileTarget.ArchiveAboveSize = 200; + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + logger1.Info("Bye"); + logger2.Info("Bye"); + Assert.Equal(12, Directory.GetFiles(tempDir).Length); + + LogManager.Configuration = null; + } + finally + { + TimeSource.Current = defaultTimeSource; // restore default time source + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + /// + /// See that dynamic sequence archive supports same-folder archiving. + /// + [Fact] + public void FileTarget_SameDirectory_MaxArchiveFiles_One() + { + const int maxArchiveFiles = 1; + + var tempDir = ArchiveFileNameHelper.GenerateTempPath(); + var logFile1 = Path.Combine(tempDir, "Log{0}.txt"); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = string.Format(logFile1, ""), + ArchiveAboveSize = 100, + LineEnding = LineEndingMode.LF, + Layout = "${message}", + MaxArchiveFiles = maxArchiveFiles, + Encoding = Encoding.ASCII, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + Generate100BytesLog('a'); + Generate100BytesLog('b'); + Generate100BytesLog('c'); + + var times = 25; + AssertFileContents(string.Format(logFile1, ".0"), + StringRepeat(times, "bbb\n"), + Encoding.ASCII); + + AssertFileContents(string.Format(logFile1, ""), + StringRepeat(times, "ccc\n"), + Encoding.ASCII); + + LogManager.Configuration = null; + } + finally + { + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + private void Generate100BytesLog(char c, Logger logger = null) + { + logger = logger ?? this.logger; + for (var i = 0; i < 25; ++i) + { + //3 chars with newlines = 4 bytes + logger.Debug(new string(c, 3)); + } + } + + /// + /// Archive file helepr + /// + /// TODO rewrite older test + private sealed class ArchiveFileNameHelper + { + public string FolderName { get; private set; } + + public string FileName { get; private set; } + /// + /// Ext without dot + /// + public string Ext { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public ArchiveFileNameHelper(string folderName, string fileName, string ext) + { + Ext = ext.TrimStart('.'); + FileName = fileName; + FolderName = folderName; + } + + public bool Exists(int number) + { + return File.Exists(GetFullPath(number)); + } + + public string GetFullPath(int number) + { + return Path.Combine($"{FolderName}/{FileName}.{number}.{Ext}"); + } + + public static string GenerateTempPath() + { + return Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + } + } + + [Theory] + [InlineData("##", 0, "00")] + [InlineData("###", 1, "001")] + [InlineData("#", 20, "20")] + public void FileTarget_WithDateAndSequenceArchiveNumbering_ShouldPadSequenceNumberInArchiveFileName( + string placeHolderSharps, int sequenceNumber, string expectedSequenceInArchiveFileName) + { + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + const string archiveDateFormat = "yyyy-MM-dd"; + string archiveFileName = Path.Combine(tempDir, $"{{{placeHolderSharps}}}.log"); + string expectedArchiveFullName = + $"{tempDir}/{DateTime.Now.ToString(archiveDateFormat)}.{expectedSequenceInArchiveFileName}.log"; + + GenerateArchives(count: sequenceNumber + 1, archiveDateFormat: archiveDateFormat, + archiveFileName: archiveFileName, archiveNumbering: ArchiveNumberingMode.DateAndSequence); + bool resultArchiveWithExpectedNameExists = File.Exists(expectedArchiveFullName); + + Assert.True(resultArchiveWithExpectedNameExists); + } + + [Theory] + [InlineData("yyyy-MM-dd HHmm")] + [InlineData("y")] + [InlineData("D")] + public void FileTarget_WithDateAndSequenceArchiveNumbering_ShouldRespectArchiveDateFormat( + string archiveDateFormat) + { + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + string archiveFileName = Path.Combine(tempDir, "{#}.log"); + string expectedDateInArchiveFileName = DateTime.Now.ToString(archiveDateFormat); + string expectedArchiveFullName = $"{tempDir}/{expectedDateInArchiveFileName}.1.log"; + + // We generate 2 archives so that the algorithm that seeks old archives is also tested. + GenerateArchives(count: 2, archiveDateFormat: archiveDateFormat, archiveFileName: archiveFileName, + archiveNumbering: ArchiveNumberingMode.DateAndSequence); + bool resultArchiveWithExpectedNameExists = File.Exists(expectedArchiveFullName); + + Assert.True(resultArchiveWithExpectedNameExists); + } + + private void GenerateArchives(int count, string archiveDateFormat, string archiveFileName, + ArchiveNumberingMode archiveNumbering) + { + string logFileName = Path.GetTempFileName(); + const int logFileMaxSize = 1; + var fileTarget = new ConcurrentFileTarget + { + FileName = logFileName, + ArchiveFileName = archiveFileName, + ArchiveDateFormat = archiveDateFormat, + ArchiveNumbering = archiveNumbering, + ArchiveAboveSize = logFileMaxSize + }; + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + for (int currentSequenceNumber = 0; currentSequenceNumber < count; currentSequenceNumber++) + logger.Debug("Test {0}", currentSequenceNumber); + + LogManager.Flush(); + } + + [Fact] + public void Dont_throw_Exception_when_archiving_is_enabled() + { + try + { + LogManager.Setup().LoadConfigurationFromXml(@" + + + + + + + + + + +"); + + LogManager.GetLogger("Test").Info("very important message"); + + Assert.NotNull(LogManager.Configuration); + } + finally + { + LogManager.Configuration = null; + } + } + + [Fact] + public void Dont_throw_Exception_when_archiving_is_enabled_with_async() + { + try + { + LogManager.Setup().LoadConfigurationFromXml(@" + + + + + + + + + + +"); + + LogManager.GetLogger("Test").Info("very important message"); + + Assert.NotNull(LogManager.Configuration); + } + finally + { + LogManager.Configuration = null; + } + } + + [Fact] + public void DatedArchiveForFileTargetWithMultipleFiles() + { + var defaultTimeSource = TimeSource.Current; + + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()) + Path.DirectorySeparatorChar; + + try + { + var timeSource = new ShiftedTimeSource(DateTimeKind.Local); + if (timeSource.Time.Minute == 59) + { + // Avoid double-archive due to overflow of the hour. + timeSource.AddToLocalTime(TimeSpan.FromMinutes(1)); + timeSource.AddToSystemTime(TimeSpan.FromMinutes(1)); + } + TimeSource.Current = timeSource; + + GlobalDiagnosticsContext.Set("basedir", tempDir); + + LogManager.Setup().LoadConfigurationFromXml(@" + + + + + + + + + +"); + + var fileLogger = LogManager.GetLogger(nameof(DatedArchiveForFileTargetWithMultipleFiles)); + LogEventInfo logEvent = LogEventInfo.Create(LogLevel.Info, fileLogger.Name, "Very Important Message"); + logEvent.Properties["serialNo"] = "M91803ED2172"; + logger.Log(logEvent); + + LogEventInfo logEvent2 = LogEventInfo.Create(LogLevel.Info, fileLogger.Name, "Very Important Message"); + logEvent2.Properties["serialNo"] = "M91803ED2137"; + logger.Log(logEvent2); + + var currentDate = timeSource.Time.Date; + timeSource.AddToLocalTime(TimeSpan.FromDays(5)); + + LogEventInfo logEvent3 = LogEventInfo.Create(LogLevel.Info, fileLogger.Name, "Very Important Message"); + logEvent3.Properties["serialNo"] = logEvent.Properties["serialNo"]; + logger.Log(logEvent3); + + LogEventInfo logEvent4 = LogEventInfo.Create(LogLevel.Info, fileLogger.Name, "Very Important Message"); + logEvent4.Properties["serialNo"] = logEvent2.Properties["serialNo"]; + logger.Log(logEvent4); + + var currentFiles = new DirectoryInfo(tempDir).GetFiles(); + Assert.Equal(4, currentFiles.Length); + Assert.Contains(logEvent.Properties["serialNo"] + ".txt", currentFiles.Select(f => f.Name)); + Assert.Contains(logEvent.Properties["serialNo"] + "." + currentDate.ToString("yyyy-MM-dd") + ".txt", currentFiles.Select(f => f.Name)); + Assert.Contains(logEvent2.Properties["serialNo"] + ".txt", currentFiles.Select(f => f.Name)); + Assert.Contains(logEvent2.Properties["serialNo"] + "." + currentDate.ToString("yyyy-MM-dd") + ".txt", currentFiles.Select(f => f.Name)); + } + finally + { + TimeSource.Current = defaultTimeSource; + + LogManager.Configuration = null; + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void LoggingShouldNotTriggerTypeResolveEventTest() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "log.log"); + + System.ResolveEventHandler noResolveTest = (s, args) => { Assert.True(false); return null; }; + + try + { + LogManager.Setup().LoadConfigurationFromXml(@" + + + + + + + + + + + + + + "); + + LogManager.GetLogger("Test").Info("very important message"); + + Assert.NotNull(LogManager.Configuration); + + AppDomain.CurrentDomain.TypeResolve += noResolveTest; + AppDomain.CurrentDomain.AssemblyResolve += noResolveTest; + + LogManager.GetLogger("Test").Info("very important message"); + } + finally + { + AppDomain.CurrentDomain.TypeResolve -= noResolveTest; + AppDomain.CurrentDomain.AssemblyResolve -= noResolveTest; + LogManager.Configuration = null; + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Theory] + [InlineData("yyyyMMdd-HHmm")] + [InlineData("yyyyMMdd")] + [InlineData("yyyy-MM-dd")] + public void MaxArchiveFilesWithDateFormatTest(string archiveDateFormat) + { + TestMaxArchiveFilesWithDate(2, 2, archiveDateFormat, true); + TestMaxArchiveFilesWithDate(2, 2, archiveDateFormat, false); + } + + /// + /// + /// + /// max count of archived files + /// expected count of archived files + /// date format + /// change file creation/last write date + private static void TestMaxArchiveFilesWithDate(int maxArchiveFilesConfig, int expectedArchiveFiles, string dateFormat, bool changeCreationAndWriteTime) + { + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + string archivePath = Path.Combine(tempDir, "archive"); + + var archiveDir = new DirectoryInfo(archivePath); + try + { + archiveDir.Create(); + //set-up, create files. + + //same dateformat as in config + string fileExt = ".log"; + DateTime now = DateTime.Now; + int i = 0; + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, fileExt).Take(30)) + { + File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); + var time = now.AddDays(i); + if (changeCreationAndWriteTime) + { + File.SetCreationTime(filePath, time); + File.SetLastWriteTime(filePath, time); + } + i--; + } + + //create config with archiving + var configuration = XmlLoggingConfiguration.CreateFromXmlString(@" + + + + + + + + "); + + LogManager.Configuration = configuration; + var logger = LogManager.GetCurrentClassLogger(); + logger.Info("test"); + + var currentFilesCount = archiveDir.GetFiles().Length; + Assert.Equal(expectedArchiveFiles, currentFilesCount); + + LogManager.Configuration = null; // Flush + } + finally + { + //cleanup + archiveDir.Delete(true); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + /// unit test for issue #1681. + ///- When clearing out archive files exceeding maxArchiveFiles, NLog should + /// not delete all files in the directory.Only files matching the target's + /// archiveFileName pattern. + ///- Create test for 2 applications sharing the same archive directory. + ///- Create test for 2 applications sharing same archive directory and 1 + /// application containing multiple targets to the same archive directory. + /// *\* Expected outcome of this should be verified** + [Theory] + [InlineData(true)] + [InlineData(false)] + public void HandleArchiveFilesMultipleContextMultipleTargetTest(bool changeCreationAndWriteTime) + { + HandleArchiveFilesMultipleContextMultipleTargetsTest(2, 2, "yyyyMMdd-HHmm", changeCreationAndWriteTime); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void HandleArchiveFilesMultipleContextSingleTargetTest_ascii(bool changeCreationAndWriteTime) + { + HandleArchiveFilesMultipleContextSingleTargetsTest(2, 2, "yyyyMMdd-HHmm", changeCreationAndWriteTime); + } + + /// + /// Test the case when multiple applications are archiving to the same directory and using multiple targets. + /// Only the archives for this application instance should be deleted per the target archive rules. + /// + /// # to use for maxArchiveFiles in NLog configuration. + /// Expected number of archive files after archiving has occured. + /// string to be used for formatting log file names + /// + private static void HandleArchiveFilesMultipleContextMultipleTargetsTest( + int maxArchiveFilesConfig, int expectedArchiveFiles, string dateFormat, bool changeCreationAndWriteTime) + { + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + string archivePath = Path.Combine(tempDir, "archive"); + var archiveDir = new DirectoryInfo(archivePath); + try + { + archiveDir.Create(); + //set-up, create files. + var numberFilesCreatedPerTargetArchive = 30; + + // use same config vars for mock files, as for nlog config + var fileExt = ".log"; + var app1TraceNm = "App1_Trace"; + var app1DebugNm = "App1_Debug"; + var app2Nm = "App2"; + + var now = DateTime.Now; + var i = 0; + // create mock app1_trace archives (matches app1 config for trace target) + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, app1TraceNm + fileExt).Take(numberFilesCreatedPerTargetArchive)) + { + File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); + var time = now.AddDays(i); + if (changeCreationAndWriteTime) + { + File.SetCreationTime(filePath, time); + File.SetLastWriteTime(filePath, time); + } + i--; + } + i = 0; + // create mock app1_debug archives (matches app1 config for debug target) + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, app1DebugNm + fileExt).Take(numberFilesCreatedPerTargetArchive)) + { + File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); + var time = now.AddDays(i); + if (changeCreationAndWriteTime) + { + File.SetCreationTime(filePath, time); + File.SetLastWriteTime(filePath, time); + } + i--; + } + i = 0; + // create mock app2 archives (matches app2 config for target) + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, app2Nm + fileExt).Take(numberFilesCreatedPerTargetArchive)) + { + File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); + var time = now.AddDays(i); + if (changeCreationAndWriteTime) + { + File.SetCreationTime(filePath, time); + File.SetLastWriteTime(filePath, time); + } + i--; + } + + // Create same app1 Debug file as config defines. Will force archiving to happen on startup + File.WriteAllLines(Path.Combine(tempDir, app1DebugNm + fileExt), new[] { "Write first app debug target. Startup will archive this file" }, Encoding.ASCII); + + var app1Config = XmlLoggingConfiguration.CreateFromXmlString(@" + + + + + + + + + "); + + var app2Config = XmlLoggingConfiguration.CreateFromXmlString(@" + + + ; + + + + "); + + LogManager.Configuration = app1Config; + + var logger = LogManager.GetCurrentClassLogger(); + // Trigger archive to happen on startup + logger.Trace("Test 1 - Write to the log file that already exists; trigger archive to happen because archiveOldFileOnStartup='true'"); + + // TODO: perhaps extra App1 Debug and Trace files should both be deleted? (then app1TraceTargetFileCnt would be expected to = expectedArchiveFiles too) + // I think it depends on how NLog works with logging to both of those files in the call to logger.Debug() above + + // verify file counts. EXPECTED OUTCOME: + // app1 trace target: removed all extra + // app1 debug target: has all extra files + // app2: has all extra files + var app1TraceTargetFileCnt = archiveDir.GetFiles("*" + app1TraceNm + "*").Length; + var app1DebugTargetFileCnt = archiveDir.GetFiles("*" + app1DebugNm + "*").Length; + var app2FileTargetCnt = archiveDir.GetFiles("*" + app2Nm + "*").Length; + + Assert.Equal(numberFilesCreatedPerTargetArchive, app1DebugTargetFileCnt); + Assert.Equal(numberFilesCreatedPerTargetArchive, app2FileTargetCnt); + Assert.Equal(expectedArchiveFiles, app1TraceTargetFileCnt); + } + finally + { + //cleanup + LogManager.Configuration = null; + archiveDir.Delete(true); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + /// + /// Test the case when multiple applications are archiving to the same directory. + /// Only the archives for this application instance should be deleted per the target archive rules. + /// + /// # to use for maxArchiveFiles in NLog configuration. + /// Expected number of archive files after archiving has occured. + /// string to be used for formatting log file names + /// + private static void HandleArchiveFilesMultipleContextSingleTargetsTest( + int maxArchiveFilesConfig, int expectedArchiveFiles, string dateFormat, bool changeCreationAndWriteTime) + { + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + string archivePath = Path.Combine(tempDir, "archive"); + var archiveDir = new DirectoryInfo(archivePath); + try + { + archiveDir.Create(); + var numberFilesCreatedPerTargetArchive = 30; + + // use same config vars for mock files, as for nlog config + var fileExt = ".log"; + var app1Nm = "App1"; + var app2Nm = "App2"; + + var now = DateTime.Now; + var i = 0; + // create mock app1 archives (matches app1 config for target) + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, app1Nm + fileExt).Take(numberFilesCreatedPerTargetArchive)) + { + File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); + var time = now.AddDays(i); + if (changeCreationAndWriteTime) + { + File.SetCreationTime(filePath, time); + File.SetLastWriteTime(filePath, time); + } + i--; + } + i = 0; + // create mock app2 archives (matches app2 config for target) + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, app2Nm + fileExt).Take(numberFilesCreatedPerTargetArchive)) + { + File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); + var time = now.AddDays(i); + if (changeCreationAndWriteTime) + { + File.SetCreationTime(filePath, time); + File.SetLastWriteTime(filePath, time); + } + i--; + } + + // Create same app1 file as config defines. Will force archiving to happen on startup + File.WriteAllLines(Path.Combine(tempDir, app1Nm + fileExt), new[] { "Write first app debug target. Startup will archive this file" }, Encoding.ASCII); + + var app1Config = XmlLoggingConfiguration.CreateFromXmlString(@" + + + ; + + + + "); + + var app2Config = XmlLoggingConfiguration.CreateFromXmlString(@" + + + ; + + + + "); + + LogManager.Configuration = app1Config; + + var logger = LogManager.GetCurrentClassLogger(); + // Trigger archive to happen on startup + logger.Debug("Test 1 - Write to the log file that already exists; trigger archive to happen because archiveOldFileOnStartup='true'"); + + // verify file counts. EXPECTED OUTCOME: + // app1: Removed extra archives + // app2: Has all extra archives + var app1TargetFileCnt = archiveDir.GetFiles("*" + app1Nm + "*").Length; + var app2FileTargetCnt = archiveDir.GetFiles("*" + app2Nm + "*").Length; + + Assert.Equal(numberFilesCreatedPerTargetArchive, app2FileTargetCnt); + Assert.Equal(expectedArchiveFiles, app1TargetFileCnt); + } + finally + { + //cleanup + LogManager.Configuration = null; + archiveDir.Delete(true); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + /// + /// Generate unlimited archivefiles names. Don't use toList on this ;) + /// + /// + /// + /// fileext with . + /// + private static IEnumerable ArchiveFileNamesGenerator(string path, string dateFormat, string fileExt) + { + //yyyyMMdd-HHmm + int dateOffset = 1; + var now = DateTime.Now; + while (true) + { + dateOffset--; + yield return Path.Combine(path, now.AddDays(dateOffset).ToString(dateFormat) + fileExt); + } + } + + [Fact] + public void RelativeFileNaming_ShouldSuccess() + { + var relativeFileName = @"Logs\myapp.log"; + var fullFilePath = Path.GetFullPath(relativeFileName); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = fullFilePath, + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + OpenFileCacheTimeout = 0 + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + LogManager.Configuration = null; + AssertFileContents(fullFilePath, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + } + finally + { + if (File.Exists(fullFilePath)) + File.Delete(fullFilePath); + } + } + + [Fact] + public void RelativeFileNaming_DirectoryNavigation_ShouldSuccess() + { + var relativeFileName = @"..\..\Logs\myapp.log"; + var fullFilePath = Path.GetFullPath(relativeFileName); + try + { + var fileTarget = new ConcurrentFileTarget + { + FileName = fullFilePath, + LineEnding = LineEndingMode.LF, + Layout = "${level} ${message}", + OpenFileCacheTimeout = 0 + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaa"); + logger.Info("bbb"); + logger.Warn("ccc"); + LogManager.Configuration = null; + AssertFileContents(fullFilePath, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); + } + finally + { + if (File.Exists(fullFilePath)) + File.Delete(fullFilePath); + } + } + + [Fact] + public void RelativeSequentialArchiveTest_MaxArchiveFiles_0() + { + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logfile = Path.Combine(tempDir, "file.txt"); + try + { + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logfile, + ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveAboveSize = 100, + ArchiveOldFileOnStartup = true, // Verify ArchiveOldFileOnStartup works together with ArchiveAboveSize + LineEnding = LineEndingMode.LF, + ArchiveNumbering = ArchiveNumberingMode.Sequence, + Layout = "${message}", + MaxArchiveFiles = 0 + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + logfile = Path.GetFullPath(logfile); + // we emit 5 * 25 *(3 x aaa + \n) bytes + // so that we should get a full file + 4 archives + Generate100BytesLog('a'); + Generate100BytesLog('b'); + Generate100BytesLog('c'); + Generate100BytesLog('d'); + Generate100BytesLog('e'); + + LogManager.Configuration = null; + + var times = 25; + AssertFileContents(logfile, + StringRepeat(times, "eee\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0000.txt"), + StringRepeat(times, "aaa\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0001.txt"), + StringRepeat(times, "bbb\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0002.txt"), + StringRepeat(times, "ccc\n"), + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0003.txt"), + StringRepeat(times, "ddd\n"), + Encoding.UTF8); + + Assert.False(File.Exists(Path.Combine(archiveFolder, "0004.txt"))); + } + finally + { + if (File.Exists(logfile)) + File.Delete(logfile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void TestFilenameCleanup() + { + var invalidChars = Path.GetInvalidFileNameChars(); + var invalidFileName = Path.DirectorySeparatorChar.ToString(); + var expectedFileName = ""; + for (int i = 0; i < invalidChars.Length; i++) + { + var invalidChar = invalidChars[i]; + if (invalidChar == Path.DirectorySeparatorChar || invalidChar == Path.AltDirectorySeparatorChar) + { + //ignore, won't used in cleanup (but for find filename in path) + continue; + } + + invalidFileName += i + invalidChar.ToString(); + //underscore is used for clean + expectedFileName += i + "_"; + } + //under mono this the invalid chars is sometimes only 1 char (so min width 2) + Assert.True(invalidFileName.Length >= 2); + //CleanupFileName is default true; + var fileTarget = new ConcurrentFileTarget(); + fileTarget.FileName = invalidFileName; + + var filePathLayout = new NLog.Internal.FilePathLayout(invalidFileName, true, FilePathKind.Absolute); + + + var path = filePathLayout.Render(LogEventInfo.CreateNullEvent()); + Assert.Equal(expectedFileName, path); + } + + [Theory] + [InlineData(DayOfWeek.Sunday, "2017-03-02 15:27:34.651", "2017-03-05 15:27:34.651")] // On a Thursday, finding next Sunday + [InlineData(DayOfWeek.Thursday, "2017-03-02 15:27:34.651", "2017-03-09 15:27:34.651")] // On a Thursday, finding next Thursday + [InlineData(DayOfWeek.Monday, "2017-03-02 00:00:00.000", "2017-03-06 00:00:00.000")] // On Thursday at Midnight, finding next Monday + public void TestCalculateNextWeekday(DayOfWeek day, string todayString, string expectedString) + { + DateTime today = DateTime.Parse(todayString); + DateTime expected = DateTime.Parse(expectedString); + DateTime actual = ConcurrentFileTarget.CalculateNextWeekday(today, day); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("UTF-16", true)] + [InlineData("UTF-16BE", true)] + [InlineData("UTF-32", true)] + [InlineData("UTF-32BE", true)] +#if !NET6_0_OR_GREATER + [InlineData("UTF-7", false)] +#endif + [InlineData("UTF-8", false)] + [InlineData("ASCII", false)] + public void TestInitialBomValue(string encodingName, bool expected) + { + var fileTarget = new ConcurrentFileTarget(); + + // Act + fileTarget.Encoding = Encoding.GetEncoding(encodingName); + + // Assert + Assert.Equal(expected, fileTarget.WriteBom); + } + + [Fact] + public void BatchErrorHandlingTest() + { + try + { + LogManager.ThrowExceptions = false; + + var fileTarget = new ConcurrentFileTarget { FileName = "${logger}", Layout = "${message}", ArchiveAboveSize = 10, DiscardAll = true }; + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(fileTarget)).LogFactory; + + // make sure that when file names get sorted, the asynchronous continuations are sorted with them as well + var exceptions = new List(); + var events = new[] + { + new LogEventInfo(LogLevel.Info, "file99.txt", "msg1").WithContinuation(exceptions.Add), + new LogEventInfo(LogLevel.Info, "", "msg2").WithContinuation(exceptions.Add), + new LogEventInfo(LogLevel.Info, "", "msg3").WithContinuation(exceptions.Add), + new LogEventInfo(LogLevel.Info, "", "msg4").WithContinuation(exceptions.Add), + new LogEventInfo(LogLevel.Info, "file99.txt", "msg5").WithContinuation(exceptions.Add), + }; + + fileTarget.WriteAsyncLogEvents(events); + logFactory.Flush(); + + Assert.Equal(5, exceptions.Count); + Assert.Null(exceptions[0]); + Assert.Null(exceptions[1]); // Will be written together + Assert.NotNull(exceptions[2]); + Assert.NotNull(exceptions[3]); + Assert.NotNull(exceptions[4]); + } + finally + { + LogManager.ThrowExceptions = true; + } + } + + [Fact] + public void BatchBufferOverflowTest() + { + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logfile = Path.Combine(tempDir, "file.txt"); + try + { + // Arrange + var fileTarget = new ConcurrentFileTarget + { + FileName = logfile, + BufferSize = 5, + LineEnding = LineEndingMode.LF, + Layout = "${message}", + Encoding = Encoding.UTF8, + }; + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(fileTarget)).LogFactory; + + var result = new List(); + var events = new List(); + var times = 200; + for (int i = 1; i <= times; ++i) + { + int counter = i; + events.Add(new LogEventInfo(LogLevel.Info, "logger", counter.ToString()).WithContinuation(ex => result.Add(ex is null ? counter : -1))); + } + + // Act + fileTarget.WriteAsyncLogEvents(events); + logFactory.Flush(); + logFactory.Dispose(); + + // Assert + Assert.Equal(Enumerable.Range(1, times).ToList(), result); + AssertFileContents(logfile, string.Join("\n", result.ToArray()) + "\n", Encoding.UTF8); + } + finally + { + if (File.Exists(logfile)) + File.Delete(logfile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void HandleArchiveFileAlreadyExistsTest_noBom() + { + //NO bom + var utf8nobom = new UTF8Encoding(false); + + HandleArchiveFileAlreadyExistsTest(utf8nobom, false); + } + + [Fact] + public void HandleArchiveFileAlreadyExistsTest_withBom() + { + // bom + var utf8nobom = new UTF8Encoding(true); + + HandleArchiveFileAlreadyExistsTest(utf8nobom, true); + } + + [Fact] + public void HandleArchiveFileAlreadyExistsTest_ascii() + { + //NO bom + var encoding = Encoding.ASCII; + + HandleArchiveFileAlreadyExistsTest(encoding, false); + } + + private static void HandleArchiveFileAlreadyExistsTest(Encoding encoding, bool hasBom) + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + string logFile = Path.Combine(tempDir, "log.txt"); + try + { + // set log file access times the same way as when this issue comes up. + Directory.CreateDirectory(tempDir); + + File.WriteAllText(logFile, "some content" + Environment.NewLine, encoding); + var oldTime = DateTime.Now.AddDays(-2); + File.SetCreationTime(logFile, oldTime); + File.SetLastWriteTime(logFile, oldTime); + File.SetLastAccessTime(logFile, oldTime); + + //write to archive directly + var archiveDateFormat = "yyyy-MM-dd"; + var archiveFileNamePattern = Path.Combine(tempDir, "log-{#}.txt"); + var archiveFileName = archiveFileNamePattern.Replace("{#}", oldTime.ToString(archiveDateFormat)); + File.WriteAllText(archiveFileName, "message already in archive" + Environment.NewLine, encoding); + + LogManager.ThrowExceptions = true; + + // configure nlog + var fileTarget = new ConcurrentFileTarget("file") + { + FileName = logFile, + ArchiveEvery = FileArchiveEveryPeriod.Day, + ArchiveFileName = archiveFileNamePattern, + ArchiveNumbering = ArchiveNumberingMode.Date, + ArchiveDateFormat = archiveDateFormat, + Encoding = encoding, + Layout = "${message}", + WriteBom = hasBom, + }; + + var config = new LoggingConfiguration(); + config.AddTarget(fileTarget); + config.AddRuleForAllLevels(fileTarget); + + LogManager.Configuration = config; + + var logger = LogManager.GetLogger("HandleArchiveFileAlreadyExistsTest"); + // write, this should append. + logger.Info("log to force archiving"); + logger.Info("log to same file"); + + LogManager.Configuration = null; // Flush + + AssertFileContents(archiveFileName, "message already in archive" + Environment.NewLine + "some content" + Environment.NewLine, encoding, hasBom); + AssertFileContents(logFile, "log to force archiving" + Environment.NewLine + "log to same file" + Environment.NewLine, encoding, hasBom); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void DontCrashWhenDateAndSequenceDoesntMatchFiles() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + string logFile = Path.Combine(tempDir, "log.txt"); + try + { + // set log file access times the same way as when this issue comes up. + Directory.CreateDirectory(tempDir); + + File.WriteAllText(logFile, "some content" + Environment.NewLine); + var oldTime = DateTime.Now.AddDays(-2); + File.SetCreationTime(logFile, oldTime); + File.SetLastWriteTime(logFile, oldTime); + File.SetLastAccessTime(logFile, oldTime); + + //write to archive directly + var archiveDateFormat = "yyyyMMdd"; + var archiveFileNamePattern = Path.Combine(tempDir, "log-{#}.txt"); + var archiveFileName = archiveFileNamePattern.Replace("{#}", oldTime.ToString(archiveDateFormat)); + File.WriteAllText(archiveFileName, "some archive content"); + + LogManager.ThrowExceptions = true; + + // configure nlog + var fileTarget = new ConcurrentFileTarget("file") + { + FileName = logFile, + ArchiveEvery = FileArchiveEveryPeriod.Day, + ArchiveFileName = "log-{#}.txt", + ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, + ArchiveAboveSize = 50000, + MaxArchiveFiles = 7 + }; + + var config = new LoggingConfiguration(); + config.AddRuleForAllLevels(fileTarget); + LogManager.Configuration = config; + + // write + var logger = LogManager.GetLogger("DontCrashWhenDateAndSequenceDoesntMatchFiles"); + logger.Info("Log message"); + + LogManager.Configuration = null; + + Assert.True(File.Exists(logFile)); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Theory] + [InlineData(true, 100, true)] // archive, as size of file is 101 + [InlineData(true, 101, false)] //equals is not above + [InlineData(true, 102, false)] // don;t archive, we didn't reach the aboveSize + [InlineData(false, 100, false)] + [InlineData(null, 0, false)] + [InlineData(null, 99, true)] + [InlineData(null, 100, true)] + [InlineData(null, 101, false)] + public void ShouldArchiveOldFileOnStartupTest(bool? archiveOldFileOnStartup, long archiveOldFileOnStartupAboveSize, bool expected) + { + // Arrange + var fileAppenderCacheMock = Substitute.For(); + + var filePath = "x:/somewhere/file.txt"; + fileAppenderCacheMock.GetFileLength(filePath).Returns(101); + + var target = new ConcurrentFileTarget(fileAppenderCacheMock) + { + ArchiveOldFileOnStartupAboveSize = archiveOldFileOnStartupAboveSize + }; + if (archiveOldFileOnStartup.HasValue) + { + target.ArchiveOldFileOnStartup = archiveOldFileOnStartup.Value; + } + + // Act + var result = target.ShouldArchiveOldFileOnStartup(filePath); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void ShouldNotArchiveWhenMeetingOldLogEventTimestamps() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); + try + { + string archiveFolder = Path.Combine(tempDir, "archive"); + var fileTarget = new ConcurrentFileTarget + { + FileName = logFile, + ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveEvery = FileArchiveEveryPeriod.Day, + LineEnding = LineEndingMode.LF, + ArchiveNumbering = ArchiveNumberingMode.Sequence, + Layout = "${message}", + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + var logger = LogManager.GetLogger(nameof(ShouldNotArchiveWhenMeetingOldLogEventTimestamps)); + logger.Info("123"); + logger.Info("456"); + logger.Log(new LogEventInfo(LogLevel.Info, null, "789") { TimeStamp = NLog.Time.TimeSource.Current.Time.AddDays(-2) }); + logger.Log(new LogEventInfo(LogLevel.Info, null, "123") { TimeStamp = NLog.Time.TimeSource.Current.Time.AddDays(-2) }); + logger.Info("456"); + logger.Log(new LogEventInfo(LogLevel.Info, null, "123") { TimeStamp = NLog.Time.TimeSource.Current.Time.AddDays(1) }); + LogManager.Configuration = null; // Flush + + AssertFileContents(logFile, + "123\n", + Encoding.UTF8); + + AssertFileContents( + Path.Combine(archiveFolder, "0000.txt"), + "123\n456\n789\n123\n456\n", + Encoding.UTF8); + + Assert.False(File.Exists(Path.Combine(archiveFolder, "0001.txt"))); + } + finally + { + if (File.Exists(logFile)) + File.Delete(logFile); + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + private static string StringRepeat(int times, string s) + { + StringBuilder sb = new StringBuilder(s.Length * times); + for (int i = 0; i < times; ++i) + sb.Append(s); + return sb.ToString(); + } + + protected static bool IsLinux() + { + var val = Environment.GetEnvironmentVariable("WINDIR"); + return string.IsNullOrEmpty(val); + } + + private static void AssertFileContents(string fileName, string contents, Encoding encoding, bool addBom = false) + { + FileInfo fi = new FileInfo(fileName); + if (!fi.Exists) + Assert.Fail("File '" + fileName + "' doesn't exist."); + + byte[] encodedBuf = encoding.GetBytes(contents); + + //add bom if needed + if (addBom) + { + var preamble = encoding.GetPreamble(); + if (preamble.Length > 0) + { + //insert before + encodedBuf = preamble.Concat(encodedBuf).ToArray(); + } + } + + byte[] buf; + using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) + { + int index = 0; + int count = (int)fs.Length; + buf = new byte[count]; + while (count > 0) + { + int n = fs.Read(buf, index, count); + if (n == 0) + break; + index += n; + count -= n; + } + } + + Assert.True(encodedBuf.Length == buf.Length, + $"File:{fileName} encodedBytes:{encodedBuf.Length} does not match file.content:{buf.Length}, file.length = {fi.Length}"); + + for (int i = 0; i < buf.Length; ++i) + { + if (encodedBuf[i] != buf[i]) + Assert.True(encodedBuf[i] == buf[i], + $"File:{fileName} content mismatch {(int)encodedBuf[i]} <> {(int)buf[i]} at index {i}"); + } + } + + private static void AssertFileContentsStartsWith(string fileName, string contents, Encoding encoding) + { + FileInfo fi = new FileInfo(fileName); + if (!fi.Exists) + Assert.Fail("File '" + fileName + "' doesn't exist."); + + byte[] encodedBuf = encoding.GetBytes(contents); + + byte[] buf = File.ReadAllBytes(fileName); + Assert.True(encodedBuf.Length <= buf.Length, + $"File:{fileName} encodedBytes:{encodedBuf.Length} does not match file.content:{buf.Length}, file.length = {fi.Length}"); + + for (int i = 0; i < encodedBuf.Length; ++i) + { + if (encodedBuf[i] != buf[i]) + Assert.True(encodedBuf[i] == buf[i], + $"File:{fileName} content mismatch {(int)encodedBuf[i]} <> {(int)buf[i]} at index {i}"); + } + } + + private static void AssertFileContentsEndsWith(string fileName, string contents, Encoding encoding) + { + if (!File.Exists(fileName)) + Assert.Fail("File '" + fileName + "' doesn't exist."); + + string fileText = File.ReadAllText(fileName, encoding); + Assert.True(fileText.Length >= contents.Length); + Assert.Equal(contents, fileText.Substring(fileText.Length - contents.Length)); + } + + protected sealed class CustomFileCompressor : IArchiveFileCompressor + { + public void CompressFile(string fileName, string archiveFileName) + { + string entryName = Path.GetFileNameWithoutExtension(archiveFileName) + Path.GetExtension(fileName); + CompressFile(fileName, archiveFileName, entryName); + } + + public void CompressFile(string fileName, string archiveFileName, string entryName) + { +#if NETFRAMEWORK + using (var zip = new Ionic.Zip.ZipFile()) + { + ZipEntry entry = zip.AddFile(fileName); + entry.FileName = entryName; + zip.Save(archiveFileName); + } +#endif + } + } + +#if NET35 || NET40 + protected static void AssertZipFileContents(string fileName, string expectedEntryName, string contents, Encoding encoding) + { + if (!File.Exists(fileName)) + Assert.True(false, "File '" + fileName + "' doesn't exist."); + + byte[] encodedBuf = encoding.GetBytes(contents); + + using (var zip = new Ionic.Zip.ZipFile(fileName)) + { + Assert.Equal(1, zip.Count); + Assert.Equal(encodedBuf.Length, zip[0].UncompressedSize); + + byte[] buf = new byte[zip[0].UncompressedSize]; + using (var fs = zip[0].OpenReader()) + { + fs.Read(buf, 0, buf.Length); + } + + for (int i = 0; i < buf.Length; ++i) + { + Assert.Equal(encodedBuf[i], buf[i]); + } + } + } +#else + protected static void AssertZipFileContents(string fileName, string expectedEntryName, string contents, Encoding encoding) + { + FileInfo fi = new FileInfo(fileName); + if (!fi.Exists) + Assert.Fail("File '" + fileName + "' doesn't exist."); + + byte[] encodedBuf = encoding.GetBytes(contents); + using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var zip = new ZipArchive(stream, ZipArchiveMode.Read)) + { + Assert.Single(zip.Entries); + Assert.Equal(expectedEntryName, zip.Entries[0].Name); + Assert.Equal(encodedBuf.Length, zip.Entries[0].Length); + + byte[] buf = new byte[(int)zip.Entries[0].Length]; + using (var fs = zip.Entries[0].Open()) + { + fs.Read(buf, 0, buf.Length); + } + + for (int i = 0; i < buf.Length; ++i) + { + Assert.Equal(encodedBuf[i], buf[i]); + } + } + } +#endif + private sealed class ShiftedTimeSource : TimeSource + { + private readonly DateTimeKind kind; + private DateTimeOffset sourceTime; + private TimeSpan systemTimeDelta; + + public ShiftedTimeSource(DateTimeKind kind) + { + this.kind = kind; + sourceTime = DateTimeOffset.UtcNow; + systemTimeDelta = TimeSpan.Zero; + } + + public override DateTime Time => ConvertToKind(sourceTime); + + public DateTime SystemTime => ConvertToKind(sourceTime - systemTimeDelta); + + public override DateTime FromSystemTime(DateTime systemTime) + { + return ConvertToKind(systemTime + systemTimeDelta); + } + + private DateTime ConvertToKind(DateTimeOffset value) + { + return kind == DateTimeKind.Local ? value.LocalDateTime : value.UtcDateTime; + } + + public void AddToSystemTime(TimeSpan delta) + { + systemTimeDelta += delta; + } + + public void AddToLocalTime(TimeSpan delta) + { + sourceTime += delta; + } + } + } +} diff --git a/tests/NLog.UnitTests/Targets/ConcurrentFileTargetTests.cs b/tests/NLog.Targets.ConcurrentFile.Tests/ConcurrentWritesMultiProcessTests.cs similarity index 87% rename from tests/NLog.UnitTests/Targets/ConcurrentFileTargetTests.cs rename to tests/NLog.Targets.ConcurrentFile.Tests/ConcurrentWritesMultiProcessTests.cs index b13fb3ea52..c20daf694a 100644 --- a/tests/NLog.UnitTests/Targets/ConcurrentFileTargetTests.cs +++ b/tests/NLog.Targets.ConcurrentFile.Tests/ConcurrentWritesMultiProcessTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen // // All rights reserved. @@ -31,11 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD +#if NETFRAMEWORK #define DISABLE_FILE_INTERNAL_LOGGING -namespace NLog.UnitTests.Targets +namespace NLog.Targets.ConcurrentFile.Tests { using System; using System.Collections.Generic; @@ -43,21 +43,27 @@ namespace NLog.UnitTests.Targets using System.IO; using System.Linq; using System.Threading; - using NLog.Config; + using NLog.Common; using NLog.Targets; using NLog.Targets.Wrappers; using Xunit; - using Xunit.Extensions; - public class ConcurrentFileTargetTests : NLogTestBase + public class ConcurrentWritesMultiProcessTests { - private Logger _logger = LogManager.GetLogger("NLog.UnitTests.Targets.ConcurrentFileTargetTests"); + private readonly Logger _logger = LogManager.GetLogger(nameof(ConcurrentWritesMultiProcessTests)); + + public ConcurrentWritesMultiProcessTests() + { + InternalLogger.Reset(); + LogManager.Configuration = null; + LogManager.ThrowExceptions = true; + } private void ConfigureSharedFile(string mode, string fileName) { var modes = mode.Split('|'); - FileTarget ft = new FileTarget(); + ConcurrentFileTarget ft = new ConcurrentFileTarget(); ft.FileName = fileName; ft.Layout = "${message}"; ft.ConcurrentWrites = true; @@ -76,6 +82,8 @@ private void ConfigureSharedFile(string mode, string fileName) var name = "ConfigureSharedFile_" + mode.Replace('|', '_') + "-wrapper"; + LogManager.Setup().SetupExtensions(ext => ext.RegisterTarget("file")); + switch (modes[0]) { case "async": @@ -100,7 +108,7 @@ private void ConfigureSharedFile(string mode, string fileName) } #pragma warning disable xUnit1013 // Needed for test - public void Process(string processIndex, string fileName, string numLogsString, string mode) + public void MultiProcessExecutor(string processIndex, string fileName, string numLogsString, string mode) #pragma warning restore xUnit1013 { Thread.CurrentThread.Name = processIndex; @@ -125,17 +133,17 @@ public void Process(string processIndex, string fileName, string numLogsString, try #endif { - using (new NoThrowNLogExceptions()) - { - Thread.Sleep(Math.Max((10 - idxProcess), 1) * 5); // Delay to wait for the other processes + LogManager.ThrowExceptions = false; - for (int i = 0; i < numLogs; ++i) - { - _logger.Debug(format, i); - } + Thread.Sleep(Math.Max((10 - idxProcess), 1) * 5); // Delay to wait for the other processes - LogManager.Configuration = null; // Flush + Close + for (int i = 0; i < numLogs; ++i) + { + _logger.Debug(format, i); } + + LogManager.Configuration = null; // Flush + Close + LogManager.ThrowExceptions = true; } #if !DISABLE_FILE_INTERNAL_LOGGING catch (Exception ex) @@ -147,6 +155,10 @@ public void Process(string processIndex, string fileName, string numLogsString, } throw; } + finally + { + LogManager.ThrowExceptions = true; + } using (var textWriter = File.AppendText(Path.Combine(Path.GetDirectoryName(fileName), string.Format("Internal_{0}.txt", processIndex)))) { @@ -163,7 +175,7 @@ private string MakeFileName(int numProcesses, int numLogs, string mode) private void DoConcurrentTest(int numProcesses, int numLogs, string mode) { - string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string tempPath = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); string archivePath = Path.Combine(tempPath, "Archive"); try @@ -183,7 +195,7 @@ private void DoConcurrentTest(int numProcesses, int numLogs, string mode) { processes[i] = ProcessRunner.SpawnMethod( GetType(), - "Process", + nameof(MultiProcessExecutor), i.ToString(), logFile, numLogs.ToString(), @@ -215,10 +227,8 @@ private void DoConcurrentTest(int numProcesses, int numLogs, string mode) //Console.WriteLine("Verifying output file {0}", logFile); foreach (var file in files) { - using (StreamReader sr = File.OpenText(file)) { - string line; while ((line = sr.ReadLine()) != null) { @@ -232,7 +242,6 @@ private void DoConcurrentTest(int numProcesses, int numLogs, string mode) receivedNumbersSet[thread].Add(number); } - if (verifyFileSize) { if (sr.BaseStream.Length > 100) @@ -264,9 +273,9 @@ private void DoConcurrentTest(int numProcesses, int numLogs, string mode) { if (equalLength) { - var reodered = receivedNumbers.OrderBy(i => i); + var reordered = receivedNumbers.OrderBy(i => i); - equalsWhenReorderd = expected.SequenceEqual(reodered); + equalsWhenReorderd = expected.SequenceEqual(reordered); } Assert.Equal(string.Join(",", expected), string.Join(",", receivedNumbers)); @@ -278,7 +287,6 @@ private void DoConcurrentTest(int numProcesses, int numLogs, string mode) var reoderProblem = equalsWhenReorderd is null ? "Dunno" : (equalsWhenReorderd == true ? "Yes" : "No"); throw new InvalidOperationException($"Error when comparing path {tempPath} for process {currentProcess}. Is this a recording problem? {reoderProblem}", ex); } - } finally { @@ -309,7 +317,19 @@ private void DoConcurrentTest(int numProcesses, int numLogs, string mode) #endif public void SimpleConcurrentTest(int numProcesses, int numLogs, string mode) { - RetryingIntegrationTest(3, () => DoConcurrentTest(numProcesses, numLogs, mode)); + for (int i = 1; i <= 3; ++i) + { + try + { + DoConcurrentTest(numProcesses, numLogs, mode); + return; + } + catch + { + if (i == 3) + throw; + } + } } [Theory] diff --git a/tests/NLog.UnitTests/Internal/FileAppenders/FileAppenderCacheTests.cs b/tests/NLog.Targets.ConcurrentFile.Tests/FileAppenderCacheTests.cs similarity index 80% rename from tests/NLog.UnitTests/Internal/FileAppenders/FileAppenderCacheTests.cs rename to tests/NLog.Targets.ConcurrentFile.Tests/FileAppenderCacheTests.cs index 532ad15ca1..734a7e341c 100644 --- a/tests/NLog.UnitTests/Internal/FileAppenders/FileAppenderCacheTests.cs +++ b/tests/NLog.Targets.ConcurrentFile.Tests/FileAppenderCacheTests.cs @@ -31,17 +31,24 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.UnitTests.Internal.FileAppenders +namespace NLog.Targets.ConcurrentFile.Tests { using System; using System.IO; + using System.Linq; using System.Text; using NLog.Internal.FileAppenders; using NLog.Targets; using Xunit; - public class FileAppenderCacheTests : NLogTestBase + public class FileAppenderCacheTests { + public FileAppenderCacheTests() + { + LogManager.ThrowExceptions = true; + LogManager.Setup().SetupExtensions(ext => ext.RegisterTarget("file")); + } + [Fact] public void FileAppenderCache_Empty() { @@ -57,7 +64,7 @@ public void FileAppenderCache_Empty() public void FileAppenderCache_Construction() { IFileAppenderFactory appenderFactory = SingleProcessFileAppender.TheFactory; - ICreateFileParameters fileTarget = new FileTarget(); + ICreateFileParameters fileTarget = new ConcurrentFileTarget(); FileAppenderCache cache = new FileAppenderCache(3, appenderFactory, fileTarget); Assert.Equal(3, cache.Size); @@ -74,7 +81,7 @@ public void FileAppenderCache_Allocate() // Construct a on non-empty FileAppenderCache. IFileAppenderFactory appenderFactory = SingleProcessFileAppender.TheFactory; - ICreateFileParameters fileTarget = new FileTarget(); + ICreateFileParameters fileTarget = new ConcurrentFileTarget(); String tempFile = Path.Combine( Path.GetTempPath(), Path.Combine(Guid.NewGuid().ToString(), "file.txt") @@ -107,7 +114,7 @@ public void FileAppenderCache_InvalidateAppender() // Construct a on non-empty FileAppenderCache. IFileAppenderFactory appenderFactory = SingleProcessFileAppender.TheFactory; - ICreateFileParameters fileTarget = new FileTarget(); + ICreateFileParameters fileTarget = new ConcurrentFileTarget(); String tempFile = Path.Combine( Path.GetTempPath(), Path.Combine(Guid.NewGuid().ToString(), "file.txt") @@ -139,7 +146,7 @@ public void FileAppenderCache_CloseAppenders() emptyCache.CloseExpiredAppenders(DateTime.UtcNow); IFileAppenderFactory appenderFactory = RetryingMultiProcessFileAppender.TheFactory; - ICreateFileParameters fileTarget = new FileTarget(); + ICreateFileParameters fileTarget = new ConcurrentFileTarget(); FileAppenderCache cache = new FileAppenderCache(3, appenderFactory, fileTarget); // Invoke CloseAppenders() on non-empty FileAppenderCache - Before allocating any appenders. cache.CloseAppenders(string.Empty); @@ -182,7 +189,7 @@ public void FileAppenderCache_CloseAppenders() public void FileAppenderCache_GetFileCharacteristics_Single() { IFileAppenderFactory appenderFactory = SingleProcessFileAppender.TheFactory; - ICreateFileParameters fileTarget = new FileTarget() { ArchiveNumbering = ArchiveNumberingMode.Date }; + ICreateFileParameters fileTarget = new ConcurrentFileTarget() { ArchiveNumbering = ArchiveNumberingMode.Date }; FileAppenderCache_GetFileCharacteristics(appenderFactory, fileTarget); } @@ -190,18 +197,18 @@ public void FileAppenderCache_GetFileCharacteristics_Single() public void FileAppenderCache_GetFileCharacteristics_Multi() { IFileAppenderFactory appenderFactory = MutexMultiProcessFileAppender.TheFactory; - ICreateFileParameters fileTarget = new FileTarget() { ArchiveNumbering = ArchiveNumberingMode.Date, ForceManaged = true }; + ICreateFileParameters fileTarget = new ConcurrentFileTarget() { ArchiveNumbering = ArchiveNumberingMode.Date, ForceManaged = true }; FileAppenderCache_GetFileCharacteristics(appenderFactory, fileTarget); } -#if !MONO && !NETSTANDARD +#if NETFRAMEWORK && !MONO [Fact] public void FileAppenderCache_GetFileCharacteristics_Windows() { if (NLog.Internal.PlatformDetector.IsWin32) { IFileAppenderFactory appenderFactory = WindowsMultiProcessFileAppender.TheFactory; - ICreateFileParameters fileTarget = new FileTarget() { ArchiveNumbering = ArchiveNumberingMode.Date }; + ICreateFileParameters fileTarget = new ConcurrentFileTarget() { ArchiveNumbering = ArchiveNumberingMode.Date }; FileAppenderCache_GetFileCharacteristics(appenderFactory, fileTarget); } } @@ -262,5 +269,51 @@ private static byte[] StringToBytes(string text) Buffer.BlockCopy(text.ToCharArray(), 0, bytes, 0, bytes.Length); return bytes; } + + private static void AssertFileContents(string fileName, string contents, Encoding encoding, bool addBom = false) + { + FileInfo fi = new FileInfo(fileName); + if (!fi.Exists) + Assert.Fail("File '" + fileName + "' doesn't exist."); + + byte[] encodedBuf = encoding.GetBytes(contents); + + //add bom if needed + if (addBom) + { + var preamble = encoding.GetPreamble(); + if (preamble.Length > 0) + { + //insert before + encodedBuf = preamble.Concat(encodedBuf).ToArray(); + } + } + + byte[] buf; + using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) + { + int index = 0; + int count = (int)fs.Length; + buf = new byte[count]; + while (count > 0) + { + int n = fs.Read(buf, index, count); + if (n == 0) + break; + index += n; + count -= n; + } + } + + Assert.True(encodedBuf.Length == buf.Length, + $"File:{fileName} encodedBytes:{encodedBuf.Length} does not match file.content:{buf.Length}, file.length = {fi.Length}"); + + for (int i = 0; i < buf.Length; ++i) + { + if (encodedBuf[i] != buf[i]) + Assert.True(encodedBuf[i] == buf[i], + $"File:{fileName} content mismatch {(int)encodedBuf[i]} <> {(int)buf[i]} at index {i}"); + } + } } } diff --git a/tests/NLog.UnitTests/Internal/FilePathLayoutTests.cs b/tests/NLog.Targets.ConcurrentFile.Tests/FilePathLayoutTests.cs similarity index 99% rename from tests/NLog.UnitTests/Internal/FilePathLayoutTests.cs rename to tests/NLog.Targets.ConcurrentFile.Tests/FilePathLayoutTests.cs index a3a615459d..d49d41880f 100644 --- a/tests/NLog.UnitTests/Internal/FilePathLayoutTests.cs +++ b/tests/NLog.Targets.ConcurrentFile.Tests/FilePathLayoutTests.cs @@ -31,7 +31,7 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.UnitTests.Internal +namespace NLog.Targets.ConcurrentFile.Tests { using NLog.Internal; using NLog.Layouts; diff --git a/tests/NLog.Targets.ConcurrentFile.Tests/NLog.Targets.ConcurrentFile.Tests.csproj b/tests/NLog.Targets.ConcurrentFile.Tests/NLog.Targets.ConcurrentFile.Tests.csproj new file mode 100644 index 0000000000..0098685719 --- /dev/null +++ b/tests/NLog.Targets.ConcurrentFile.Tests/NLog.Targets.ConcurrentFile.Tests.csproj @@ -0,0 +1,41 @@ + + + + 17.0 + net462 + net462;net8.0 + + false + + NLogTests.snk + false + true + true + + Full + true + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + diff --git a/tests/NLog.Targets.ConcurrentFile.Tests/NLogTests.snk b/tests/NLog.Targets.ConcurrentFile.Tests/NLogTests.snk new file mode 100644 index 0000000000..f168e4dbec Binary files /dev/null and b/tests/NLog.Targets.ConcurrentFile.Tests/NLogTests.snk differ diff --git a/tests/NLog.UnitTests/ProcessRunner.cs b/tests/NLog.Targets.ConcurrentFile.Tests/ProcessRunner.cs similarity index 98% rename from tests/NLog.UnitTests/ProcessRunner.cs rename to tests/NLog.Targets.ConcurrentFile.Tests/ProcessRunner.cs index 37e0c9f532..2f834a9bbc 100644 --- a/tests/NLog.UnitTests/ProcessRunner.cs +++ b/tests/NLog.Targets.ConcurrentFile.Tests/ProcessRunner.cs @@ -31,11 +31,10 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD +#if NETFRAMEWORK -namespace NLog.UnitTests +namespace NLog.Targets.ConcurrentFile.Tests { - using System; using System.CodeDom.Compiler; using System.Diagnostics; diff --git a/src/NLog/Config/IInitializeSucceeded.cs b/tests/NLog.Targets.ConcurrentFile.Tests/Properties/AssemblyInfo.cs similarity index 84% rename from src/NLog/Config/IInitializeSucceeded.cs rename to tests/NLog.Targets.ConcurrentFile.Tests/Properties/AssemblyInfo.cs index b22a1d21fd..23035655fa 100644 --- a/src/NLog/Config/IInitializeSucceeded.cs +++ b/tests/NLog.Targets.ConcurrentFile.Tests/Properties/AssemblyInfo.cs @@ -31,13 +31,4 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.Config -{ - internal interface IInitializeSucceeded - { - /// - /// Did the Initialize Succeeded? true= success, false= error, null = initialize not started yet. - /// - bool? InitializeSucceeded { get; } - } -} +[assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/NLog.Targets.GZipFile.Tests/GZipFileTests.cs b/tests/NLog.Targets.GZipFile.Tests/GZipFileTests.cs new file mode 100644 index 0000000000..e76195911f --- /dev/null +++ b/tests/NLog.Targets.GZipFile.Tests/GZipFileTests.cs @@ -0,0 +1,226 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +using System; +using System.IO; +using System.IO.Compression; +using Xunit; + +namespace NLog.Targets.GZipFile.Tests +{ + public class GZipFileTests + { + [Fact] + public void SimpleFileGZipStream() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFileName = Path.Combine(tempDir, "log.gzip"); + + try + { + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => + { + cfg.ForLogger().WriteTo(new GZipFileTarget() { FileName = logFileName, Layout = "${message}", LineEnding = LineEndingMode.LF }); + }).LogFactory; + + logFactory.GetCurrentClassLogger().Info("Hello"); + logFactory.GetCurrentClassLogger().Info("World"); + logFactory.Shutdown(); + + using (var logFile = new StreamReader(new GZipStream(new FileStream(logFileName, FileMode.Open), CompressionMode.Decompress))) + { + Assert.Equal("Hello", logFile.ReadLine()); + Assert.Equal("World", logFile.ReadLine()); + Assert.Null(logFile.ReadLine()); + } + } + finally + { + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void SimpleFileGZipStream_AutoFlush_False() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFileName = Path.Combine(tempDir, "log.gzip"); + + try + { + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => + { + cfg.ForLogger().WriteTo(new GZipFileTarget() { FileName = logFileName, Layout = "${message}", LineEnding = LineEndingMode.LF, AutoFlush = false, CompressionLevel = CompressionLevel.Optimal }); + }).LogFactory; + + logFactory.GetCurrentClassLogger().Info("Hello"); + logFactory.GetCurrentClassLogger().Info("World"); + logFactory.Shutdown(); + + using (var logFile = new StreamReader(new GZipStream(new FileStream(logFileName, FileMode.Open), CompressionMode.Decompress))) + { + Assert.Equal("Hello", logFile.ReadLine()); + Assert.Equal("World", logFile.ReadLine()); + Assert.Null(logFile.ReadLine()); + } + } + finally + { + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void SimpleFileGZipStream_ArchiveAboveSize() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFileName = Path.Combine(tempDir, "log.gzip"); + var rollFileName = Path.Combine(tempDir, "log_01.gzip"); + + try + { + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => + { + cfg.ForLogger().WriteTo(new GZipFileTarget() { FileName = logFileName, Layout = "${message}", LineEnding = LineEndingMode.LF, CompressionLevel = CompressionLevel.Optimal, ArchiveAboveSize = 10, ArchiveSuffixFormat = "_{0:00}" }); + }).LogFactory; + + logFactory.GetCurrentClassLogger().Info("Hello"); + logFactory.GetCurrentClassLogger().Info("World"); + logFactory.GetCurrentClassLogger().Info("Again"); + logFactory.Shutdown(); + + using (var logFile = new StreamReader(new GZipStream(new FileStream(logFileName, FileMode.Open), CompressionMode.Decompress))) + { + Assert.Equal("Hello", logFile.ReadLine()); + Assert.Equal("World", logFile.ReadLine()); + Assert.Null(logFile.ReadLine()); + } + + using (var logFile = new StreamReader(new GZipStream(new FileStream(rollFileName, FileMode.Open), CompressionMode.Decompress))) + { + Assert.Equal("Again", logFile.ReadLine()); + Assert.Null(logFile.ReadLine()); + } + } + finally + { + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void SimpleFileGZipStream_ArchiveFileName_ArchiveAboveSize() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFileName = Path.Combine(tempDir, "log.gzip"); + var rollFileName = Path.Combine(tempDir, "log_01.gzip"); + + try + { + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => + { + cfg.ForLogger().WriteTo(new GZipFileTarget() { FileName = logFileName, Layout = "${message}", LineEnding = LineEndingMode.LF, CompressionLevel = CompressionLevel.Optimal, ArchiveAboveSize = 10, ArchiveSuffixFormat = "_{0:00}", ArchiveFileName = logFileName }); + }).LogFactory; + + logFactory.GetCurrentClassLogger().Info("Hello"); + logFactory.GetCurrentClassLogger().Info("World"); + logFactory.GetCurrentClassLogger().Info("Again"); + logFactory.Shutdown(); + + using (var logFile = new StreamReader(new GZipStream(new FileStream(logFileName, FileMode.Open), CompressionMode.Decompress))) + { + Assert.Equal("Again", logFile.ReadLine()); + Assert.Null(logFile.ReadLine()); + } + + using (var logFile = new StreamReader(new GZipStream(new FileStream(rollFileName, FileMode.Open), CompressionMode.Decompress))) + { + Assert.Equal("Hello", logFile.ReadLine()); + Assert.Equal("World", logFile.ReadLine()); + Assert.Null(logFile.ReadLine()); + } + } + finally + { + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void SimpleFileGZipStream_ArchiveOldFileOnStartup() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFileName = Path.Combine(tempDir, "log.gzip"); + + try + { + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => + { + cfg.ForLogger().WriteTo(new GZipFileTarget() { FileName = logFileName, Layout = "${message}", LineEnding = LineEndingMode.LF }); + }).LogFactory; + + logFactory.GetCurrentClassLogger().Info("Hello"); + logFactory.Shutdown(); + + var logFactory2 = new LogFactory().Setup().LoadConfiguration(cfg => + { + cfg.ForLogger().WriteTo(new GZipFileTarget() { FileName = logFileName, Layout = "${message}", LineEnding = LineEndingMode.LF }); + }).LogFactory; + + logFactory2.GetCurrentClassLogger().Info("World"); + logFactory2.Shutdown(); + + using (var logFile = new StreamReader(new GZipStream(new FileStream(logFileName, FileMode.Open), CompressionMode.Decompress))) + { + Assert.Equal("Hello", logFile.ReadLine()); + Assert.Null(logFile.ReadLine()); + } + + using (var logFile = new StreamReader(new GZipStream(new FileStream(Path.Combine(tempDir, "log_01.gzip"), FileMode.Open), CompressionMode.Decompress))) + { + Assert.Equal("World", logFile.ReadLine()); + Assert.Null(logFile.ReadLine()); + } + } + finally + { + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + } +} diff --git a/tests/NLog.Targets.GZipFile.Tests/NLog.Targets.GZipFile.Tests.csproj b/tests/NLog.Targets.GZipFile.Tests/NLog.Targets.GZipFile.Tests.csproj new file mode 100644 index 0000000000..f3c3169915 --- /dev/null +++ b/tests/NLog.Targets.GZipFile.Tests/NLog.Targets.GZipFile.Tests.csproj @@ -0,0 +1,31 @@ + + + + 17.0 + net462 + net462;net8.0 + + false + + Full + true + true + en + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/tests/NLog.UnitTests/Targets/MailTargetTests.cs b/tests/NLog.Targets.Mail.Tests/MailTargetTests.cs similarity index 96% rename from tests/NLog.UnitTests/Targets/MailTargetTests.cs rename to tests/NLog.Targets.Mail.Tests/MailTargetTests.cs index 3c02614240..5ff5a43578 100644 --- a/tests/NLog.UnitTests/Targets/MailTargetTests.cs +++ b/tests/NLog.Targets.Mail.Tests/MailTargetTests.cs @@ -31,11 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.UnitTests.Targets +namespace NLog.Targets.Mail { using System; using System.Collections.Generic; -#if !NETSTANDARD +#if NETFRAMEWORK using System.Net.Configuration; #endif using System.IO; @@ -584,7 +584,7 @@ public void MailTarget_WithValidToAndEmptyBcc_SendsMail() } [Fact] - public void MailTarget_WithEmptyTo_ThrowsNLogRuntimeException() + public void MailTarget_WithEmptyTo_ThrowsConfigException() { var mmt = new MockMailTarget { @@ -596,17 +596,17 @@ public void MailTarget_WithEmptyTo_ThrowsNLogRuntimeException() Body = "${level} ${logger} ${message}", }; - var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => - { - cfg.LogFactory.ThrowExceptions = true; - cfg.Configuration.AddRuleForAllLevels(mmt); - }).LogFactory; - - Assert.Throws(() => logFactory.GetLogger("MyLogger").Info("log message 1")); + Assert.Throws(() => + new LogFactory().Setup().LoadConfiguration(cfg => + { + cfg.LogFactory.ThrowConfigExceptions = true; + cfg.Configuration.AddRuleForAllLevels(mmt); + }) + ); } [Fact] - public void MailTarget_WithEmptyFrom_ThrowsNLogRuntimeException() + public void MailTarget_WithEmptyFrom_ThrowsConfigException() { var mmt = new MockMailTarget { @@ -617,17 +617,18 @@ public void MailTarget_WithEmptyFrom_ThrowsNLogRuntimeException() SmtpPort = 27, Body = "${level} ${logger} ${message}" }; - var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => - { - cfg.LogFactory.ThrowExceptions = true; - cfg.Configuration.AddRuleForAllLevels(mmt); - }).LogFactory; - Assert.Throws(() => logFactory.GetLogger("MyLogger").Info("log message 1")); + Assert.Throws(() => + new LogFactory().Setup().LoadConfiguration(cfg => + { + cfg.LogFactory.ThrowConfigExceptions = true; + cfg.Configuration.AddRuleForAllLevels(mmt); + }) + ); } [Fact] - public void MailTarget_WithEmptySmtpServer_ThrowsNLogRuntimeException() + public void MailTarget_WithEmptySmtpServer_ThrowsConfigException() { var mmt = new MockMailTarget { @@ -638,13 +639,14 @@ public void MailTarget_WithEmptySmtpServer_ThrowsNLogRuntimeException() SmtpPort = 27, Body = "${level} ${logger} ${message}" }; - var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => - { - cfg.LogFactory.ThrowExceptions = true; - cfg.Configuration.AddRuleForAllLevels(mmt); - }).LogFactory; - Assert.Throws(() => logFactory.GetLogger("MyLogger").Info("log message 1")); + Assert.Throws(() => + new LogFactory().Setup().LoadConfiguration(cfg => + { + cfg.LogFactory.ThrowConfigExceptions = true; + cfg.Configuration.AddRuleForAllLevels(mmt); + }) + ); } [Fact] @@ -826,7 +828,7 @@ public void MailTarget_UseSystemNetMailSettings_True_ReadFromFromConfigFile_dont SmtpPort = 27, Body = "${level} ${logger} ${message}", UseSystemNetMailSettings = true, -#if !NETSTANDARD +#if NETFRAMEWORK SmtpSection = new SmtpSection { From = "config@foo.com" } #endif }; @@ -840,7 +842,7 @@ public void MailTarget_UseSystemNetMailSettings_True_ReadFromFromConfigFile_dont Assert.Equal("nlog@foo.com", mmt.From.ToString()); } -#if !NETSTANDARD +#if NETFRAMEWORK [Fact] public void MailTarget_UseSystemNetMailSettings_True_ReadFromFromConfigFile() { @@ -865,7 +867,7 @@ public void MailTarget_UseSystemNetMailSettings_True_ReadFromFromConfigFile() } #endif -#if !NETSTANDARD +#if NETFRAMEWORK [Fact] public void MailTarget_UseSystemNetMailSettings_False_ReadFromFromConfigFile() { diff --git a/tests/NLog.Targets.Mail.Tests/NLog.Targets.Mail.Tests.csproj b/tests/NLog.Targets.Mail.Tests/NLog.Targets.Mail.Tests.csproj new file mode 100644 index 0000000000..88575c8e9b --- /dev/null +++ b/tests/NLog.Targets.Mail.Tests/NLog.Targets.Mail.Tests.csproj @@ -0,0 +1,31 @@ + + + + 17.0 + net462 + net462;net8.0 + + false + + NLogTests.snk + false + true + true + + Full + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/tests/NLog.Targets.Mail.Tests/NLogTests.snk b/tests/NLog.Targets.Mail.Tests/NLogTests.snk new file mode 100644 index 0000000000..f168e4dbec Binary files /dev/null and b/tests/NLog.Targets.Mail.Tests/NLogTests.snk differ diff --git a/tests/NLog.Targets.Network.Tests/GelfLayoutTests.cs b/tests/NLog.Targets.Network.Tests/GelfLayoutTests.cs new file mode 100644 index 0000000000..758c717511 --- /dev/null +++ b/tests/NLog.Targets.Network.Tests/GelfLayoutTests.cs @@ -0,0 +1,315 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.Network +{ + using System; + using NLog.Layouts; + using Xunit; + + public class GelfLayoutTests + { + private static readonly string HostName = ResolveHostname(); + + public GelfLayoutTests() + { + LogManager.ThrowExceptions = true; + LogManager.Setup().SetupExtensions(ext => + { + ext.RegisterLayout(); + }); + } + + [Fact] + public void CanRenderGelf() + { + var gelfLayout = new GelfLayout(); + + var memTarget = new NLog.Targets.MemoryTarget() { Layout = gelfLayout }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var loggerName = "TestLogger"; + var dateTime = DateTime.Now; + var message = "hello, gelf :)"; + var logLevel = LogLevel.Info; + + var logEvent = new LogEventInfo + { + LoggerName = loggerName, + Level = logLevel, + Message = message, + TimeStamp = dateTime, + }; + + logFactory.GetLogger(loggerName).Log(logEvent); + Assert.Single(memTarget.Logs); + var renderedGelf = memTarget.Logs[0]; + + var expectedDateTime = GelfLayout.ToUnixTimeStamp(dateTime); + var expectedGelf = string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{{"+ "\"version\":\"1.1\"," + + "\"host\":\"{0}\"," + + "\"short_message\":\"{1}\"," + + "\"timestamp\":{2}," + + "\"level\":{3}," + + "\"_loglevel\":\"{4}\"," + + "\"_logger\":\"{5}\"" + + "}}", + HostName, + message, + expectedDateTime, + (int)GelfLayout.ToSyslogLevel(logLevel), + logLevel, + loggerName); + + Assert.Equal(expectedGelf, renderedGelf); + } + } + + [Fact] + public void CanRenderGelfFacility() + { + var gelfLayout = new GelfLayout(); + gelfLayout.GelfFields.Add(new TargetPropertyWithContext("_LoggerName", "${logger}")); + + var memTarget = new NLog.Targets.MemoryTarget() { Layout = gelfLayout }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var loggerName = "TestLogger"; + var facility = "TestFacility"; + var dateTime = DateTime.Now; + var message = "hello, gelf :)"; + var logLevel = LogLevel.Info; + + gelfLayout.GelfFacility = facility; + + var logEvent = new LogEventInfo + { + LoggerName = loggerName, + Level = logLevel, + Message = message, + TimeStamp = dateTime, + }; + + logFactory.GetLogger(loggerName).Log(logEvent); + Assert.Single(memTarget.Logs); + var renderedGelf = memTarget.Logs[0]; + + var expectedDateTime = GelfLayout.ToUnixTimeStamp(dateTime); + var expectedGelf = string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{{"+ "\"version\":\"1.1\"," + + "\"host\":\"{0}\"," + + "\"short_message\":\"{1}\"," + + "\"timestamp\":{2}," + + "\"level\":{3}," + + "\"facility\":\"{4}\"," + + "\"file\":\"TestLogger\"," + + "\"_LoggerName\":\"{5}\"" + + "}}", + HostName, + message, + expectedDateTime, + (int)GelfLayout.ToSyslogLevel(logLevel), + facility, + loggerName); + + Assert.Equal(expectedGelf, renderedGelf); + } + } + + [Fact] + public void CanRenderGelfAdditionalFieldsCustomMessage() + { + var gelfLayout = new GelfLayout(); + gelfLayout.GelfFields.Add(new TargetPropertyWithContext("ThreadId", "${threadid}") { PropertyType = typeof(int) }); + gelfLayout.GelfFullMessage = "${hostname}|${message}"; + gelfLayout.GelfShortMessage = "short|${message}"; + + var memTarget = new NLog.Targets.MemoryTarget() { Layout = gelfLayout }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget).WithAsync()).LogFactory) + { + var loggerName = "TestLogger"; + var dateTime = DateTime.Now; + var message = "hello, gelf :)"; + var logLevel = LogLevel.Info; + + var logEvent = new LogEventInfo + { + LoggerName = loggerName, + Level = logLevel, + Message = message, + TimeStamp = dateTime, + }; + + logFactory.GetLogger(loggerName).Log(logEvent); + logFactory.Flush(); + Assert.Single(memTarget.Logs); + var renderedGelf = memTarget.Logs[0]; + + int threadId = System.Threading.Thread.CurrentThread.ManagedThreadId; + var expectedDateTime = GelfLayout.ToUnixTimeStamp(dateTime); + var expectedGelf = string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{{" + "\"version\":\"1.1\"," + + "\"host\":\"{0}\"," + + "\"short_message\":\"short|{1}\"," + + "\"full_message\":\"{0}|{1}\"," + + "\"timestamp\":{2}," + + "\"level\":{3}," + + "\"_ThreadId\":{4}" + + "}}", + HostName, + message, + expectedDateTime, + (int)GelfLayout.ToSyslogLevel(logLevel), + threadId); + Assert.Equal(expectedGelf, renderedGelf); + } + } + + [Fact] + public void CanRenderEventProperties() + { + var gelfLayout = new GelfLayout(); + gelfLayout.GelfFields.Add(new TargetPropertyWithContext(" ThreadId ", "${threadid}") { PropertyType = typeof(int) }); + gelfLayout.IncludeEventProperties = true; + gelfLayout.IncludeProperties.Add("RequestId"); + + var memTarget = new NLog.Targets.MemoryTarget() { Layout = gelfLayout }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget).WithAsync()).LogFactory) + { + var loggerName = "TestLogger"; + var dateTime = DateTime.Now; + var message = "hello, {world} from {RequestId} :)"; + var logLevel = LogLevel.Info; + var requestId = Guid.NewGuid(); + + var logEvent = new LogEventInfo + { + TimeStamp = dateTime, + LoggerName = loggerName, + Level = logLevel, + Message = message, + Parameters = new object[] { "Gelf", requestId }, + }; + + logFactory.GetLogger(loggerName).Log(logEvent); + logFactory.Flush(); + Assert.Single(memTarget.Logs); + var renderedGelf = memTarget.Logs[0]; + + int threadId = System.Threading.Thread.CurrentThread.ManagedThreadId; + var expectedDateTime = GelfLayout.ToUnixTimeStamp(dateTime); + var expectedGelf = string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{{" + "\"version\":\"1.1\"," + + "\"host\":\"{0}\"," + + "\"short_message\":\"hello, Gelf from {5} :)\"," + + "\"timestamp\":{2}," + + "\"level\":{3}," + + "\"_ThreadId\":{4}," + + "\"_RequestId\":\"{5}\"" + + "}}", + HostName, + message, + expectedDateTime, + (int)GelfLayout.ToSyslogLevel(logLevel), + threadId, + requestId); + Assert.Equal(expectedGelf, renderedGelf); + } + } + + [Fact] + public void CanRenderScopeContext() + { + var gelfLayout = new GelfLayout(); + gelfLayout.GelfFields.Add(new TargetPropertyWithContext(" ThreadId ", "${threadid}") { PropertyType = typeof(int) }); + gelfLayout.IncludeEventProperties = true; + gelfLayout.IncludeScopeProperties = true; + gelfLayout.ExcludeProperties.Add("World"); + + var memTarget = new NLog.Targets.MemoryTarget() { Layout = gelfLayout }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget).WithAsync()).LogFactory) + { + var loggerName = "TestLogger"; + var dateTime = DateTime.Now; + var message = "hello, {world} :)"; + var logLevel = LogLevel.Info; + + var logEvent = new LogEventInfo + { + TimeStamp = dateTime, + LoggerName = loggerName, + Level = logLevel, + Message = message, + Parameters = new object[] { "Gelf" }, + }; + + var requestId = Guid.NewGuid(); + using (logFactory.GetLogger(loggerName).PushScopeProperty("RequestId", requestId)) + { + logFactory.GetLogger(loggerName).Log(logEvent); + } + logFactory.Flush(); + Assert.Single(memTarget.Logs); + var renderedGelf = memTarget.Logs[0]; + + int threadId = System.Threading.Thread.CurrentThread.ManagedThreadId; + var expectedDateTime = GelfLayout.ToUnixTimeStamp(dateTime); + var expectedGelf = string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{{" + "\"version\":\"1.1\"," + + "\"host\":\"{0}\"," + + "\"short_message\":\"hello, Gelf :)\"," + + "\"timestamp\":{2}," + + "\"level\":{3}," + + "\"_ThreadId\":{4}," + + "\"_RequestId\":\"{5}\"" + + "}}", + HostName, + message, + expectedDateTime, + (int)GelfLayout.ToSyslogLevel(logLevel), + threadId, + requestId); + Assert.Equal(expectedGelf, renderedGelf); + } + } + + static string ResolveHostname() + { + return Environment.GetEnvironmentVariable("HOSTNAME") + ?? Environment.GetEnvironmentVariable("COMPUTERNAME") + ?? Environment.GetEnvironmentVariable("MACHINENAME") + ?? Environment.MachineName; + } + } +} diff --git a/tests/NLog.UnitTests/LayoutRenderers/LocalIpAddressLayoutRendererTests.cs b/tests/NLog.Targets.Network.Tests/LocalIpAddressLayoutRendererTests.cs similarity index 94% rename from tests/NLog.UnitTests/LayoutRenderers/LocalIpAddressLayoutRendererTests.cs rename to tests/NLog.Targets.Network.Tests/LocalIpAddressLayoutRendererTests.cs index 2e59306a05..e39a9eb3a6 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/LocalIpAddressLayoutRendererTests.cs +++ b/tests/NLog.Targets.Network.Tests/LocalIpAddressLayoutRendererTests.cs @@ -31,7 +31,7 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.UnitTests.LayoutRenderers +namespace NLog.Targets.Network { using System; using System.Collections.Generic; @@ -45,7 +45,7 @@ namespace NLog.UnitTests.LayoutRenderers using NSubstitute.ExceptionExtensions; using Xunit; - public class LocalIpAddressLayoutRendererTests : NLogTestBase + public class LocalIpAddressLayoutRendererTests { private const string Mac1 = "F0-E1-D2-C3-B4-A5"; @@ -248,9 +248,16 @@ public void LocalIpAddress_RetrieverThrowsException_RenderEmptyString() internal sealed class NetworkInterfaceRetrieverBuilder { - private readonly IDictionary>> _ips = new Dictionary>>(); + private readonly Dictionary>> _ips = new Dictionary>>(); - private IList<(NetworkInterfaceType networkInterfaceType, string mac, OperationalStatus status)> _networkInterfaces = new List<(NetworkInterfaceType networkInterfaceType, string mac, OperationalStatus status)>(); + struct NetworkInfo + { + public NetworkInterfaceType NetworkType { get; set; } + public string MacAddress { get; set; } + public OperationalStatus NetworkStatus { get; set; } + } + + private List _networkInterfaces = new List(); private readonly INetworkInterfaceRetriever _networkInterfaceRetrieverMock; /// @@ -261,7 +268,12 @@ public NetworkInterfaceRetrieverBuilder() public NetworkInterfaceRetrieverBuilder WithInterface(NetworkInterfaceType networkInterfaceType, string mac, OperationalStatus status = OperationalStatus.Up) { - _networkInterfaces.Add((networkInterfaceType, mac, status)); + _networkInterfaces.Add(new NetworkInfo() + { + NetworkType = networkInterfaceType, + MacAddress = mac, + NetworkStatus = status + }); return this; } @@ -301,7 +313,7 @@ private IEnumerable BuildAllNetworkInterfaces() var networkInterface = _networkInterfaces[i]; if (_ips.TryGetValue(i, out var ips)) { - var networkInterfaceMock = BuildNetworkInterfaceMock(ips, networkInterface.mac, networkInterface.networkInterfaceType, networkInterface.status); + var networkInterfaceMock = BuildNetworkInterfaceMock(ips, networkInterface.MacAddress, networkInterface.NetworkType, networkInterface.NetworkStatus); networkInterfaceMock.Id.Returns($"#{i}"); networkInterfaceMock.Description.Returns("ips: " + string.Join(";", ips.ToArray())); yield return networkInterfaceMock; diff --git a/tests/NLog.Targets.Network.Tests/Log4JXmlTests.cs b/tests/NLog.Targets.Network.Tests/Log4JXmlTests.cs new file mode 100644 index 0000000000..ddc0de68f3 --- /dev/null +++ b/tests/NLog.Targets.Network.Tests/Log4JXmlTests.cs @@ -0,0 +1,411 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.Network +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Reflection; + using System.Text; + using System.Xml; + using NLog.Layouts; + using NLog.Targets; + using Xunit; + + public class Log4JXmlTests + { + public Log4JXmlTests() + { + LogManager.ThrowExceptions = true; + LogManager.Setup().SetupExtensions(ext => + { + ext.RegisterLayout(); + }); + } + + [Fact] + public void Log4JXmlTest() + { + var logFactory = new LogFactory().Setup() + .LoadConfigurationFromXml(@" + + + + + + + + + + + ").LogFactory; + + ScopeContext.Clear(); + + ScopeContext.PushProperty("foo1", "bar1"); + ScopeContext.PushProperty("foo2", "bar2"); + ScopeContext.PushProperty("foo3", "bar3"); + + ScopeContext.PushNestedState("baz1"); + ScopeContext.PushNestedState("baz2"); + ScopeContext.PushNestedState("baz3"); + + var logger = logFactory.GetLogger("A"); + var logEventInfo = LogEventInfo.Create(LogLevel.Debug, "A", new Exception("Hello Exception", new Exception("Goodbye Exception")), null, "some message \u0014"); + logEventInfo.Properties["nlogPropertyKey"] = "nlogPropertyValue"; + logger.Log(logEventInfo); + logFactory.Flush(); + + string result = logFactory.Configuration.FindTargetByName("debug").LastMessage; + Assert.DoesNotContain("dummy", result); + + string wrappedResult = "" + result + ""; + + Assert.NotEqual("", result); + // make sure the XML can be read back and verify some fields + StringReader stringReader = new StringReader(wrappedResult); + + var foundsChilds = new Dictionary(); + + var requiredChilds = new List + { + "log4j.event", + "log4j.message", + "log4j.NDC", + "log4j.locationInfo", + "log4j.properties", + "log4j.throwable", + "log4j.data", + }; + + using (XmlReader reader = XmlReader.Create(stringReader)) + { + + while (reader.Read()) + { + var key = reader.LocalName; + var fullKey = reader.Prefix + "." + key; + if (!foundsChilds.ContainsKey(fullKey)) + { + foundsChilds[fullKey] = 0; + } + foundsChilds[fullKey]++; + + if (reader.NodeType == XmlNodeType.Element && reader.Prefix == "log4j") + { + switch (reader.LocalName) + { + case "dummyRoot": + break; + + case "event": + Assert.Equal("DEBUG", reader.GetAttribute("level")); + Assert.Equal("A", reader.GetAttribute("logger")); + + var epochStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + long timestamp = Convert.ToInt64(reader.GetAttribute("timestamp")); + var eventTime = logEventInfo.TimeStamp.Date.AddHours(logEventInfo.TimeStamp.Hour).AddMinutes(logEventInfo.TimeStamp.Minute).AddSeconds(logEventInfo.TimeStamp.Second).AddMilliseconds(logEventInfo.TimeStamp.Millisecond).ToUniversalTime(); + Assert.Equal(eventTime, epochStart.AddMilliseconds(timestamp)); + Assert.Equal(Environment.CurrentManagedThreadId.ToString(), reader.GetAttribute("thread")); + break; + + case "message": + reader.Read(); + Assert.Equal("some message ", reader.Value); + break; + + case "NDC": + reader.Read(); + Assert.Equal("baz1::baz2::baz3", reader.Value); + break; + + case "locationInfo": + Assert.Equal(MethodBase.GetCurrentMethod().DeclaringType.FullName, reader.GetAttribute("class")); + Assert.Equal(MethodBase.GetCurrentMethod().Name, reader.GetAttribute("method")); + break; + + case "properties": + break; + + case "throwable": + reader.Read(); + Assert.Contains("Hello Exception", reader.Value); + Assert.Contains("Goodbye Exception", reader.Value); + break; + case "data": + string name = reader.GetAttribute("name"); + string value = reader.GetAttribute("value"); + + switch (name) + { + case "log4japp": + var expectedAppInfo = string.Format(CultureInfo.InvariantCulture, "{0}({1})", AppDomain.CurrentDomain.FriendlyName, System.Diagnostics.Process.GetCurrentProcess().Id); + Assert.Equal(expectedAppInfo, value); + break; + + case "log4jmachinename": + Assert.Equal(Environment.MachineName, value); + break; + + case "foo1": + Assert.Equal("bar1", value); + break; + + case "foo2": + Assert.Equal("bar2", value); + break; + + case "foo3": + Assert.Equal("bar3", value); + break; + + case "nlogPropertyKey": + Assert.Equal("nlogPropertyValue", value); + break; + + default: + Assert.Fail("Unknown : " + name); + break; + } + break; + + default: + throw new NotSupportedException("Unknown element: " + key); + } + } + } + } + + foreach (var required in requiredChilds) + { + Assert.True(foundsChilds.ContainsKey(required), $"{required} not found!"); + } + } + + [Fact] + public void Log4JXmlEventLayoutParameterTest() + { + var log4jLayout = new Log4JXmlEventLayout() + { + IncludeEventProperties = false, + Parameters = + { + new Log4JXmlEventParameter + { + Name = "mt", + Layout = "${event-properties:planet}", + }, + }, + }; + log4jLayout.AppInfo = "MyApp"; + var logEventInfo = new LogEventInfo + { + LoggerName = "MyLOgger", + TimeStamp = new DateTime(2010, 01, 01, 12, 34, 56, DateTimeKind.Utc), + Level = LogLevel.Info, + Message = "hello, {planet}", + Parameters = new[] { "" } + }; + + var threadid = Environment.CurrentManagedThreadId; + var machinename = Environment.MachineName; + var result = log4jLayout.Render(logEventInfo); + Assert.Equal($"hello, <earth>", result); + + var logEventInfo2 = new LogEventInfo + { + LoggerName = "MyLOgger", + TimeStamp = new DateTime(2010, 01, 01, 12, 34, 56, DateTimeKind.Utc), + Level = LogLevel.Info, + Message = "hello, ", + }; + + var result2 = log4jLayout.Render(logEventInfo2); + Assert.Equal($"hello, <earth>", result2); + } + + [Fact] + public void Log4JXmlEventLayout_ThrowableWrappedInCData_Test() + { + var log4jLayout = new Log4JXmlEventLayout + { + WriteThrowableCData = true, + AppInfo = "TestApp", + }; + + var logEventInfo = new LogEventInfo + { + LoggerName = "TestLogger", + TimeStamp = new DateTime(2010, 01, 01, 12, 34, 56, DateTimeKind.Utc), + Level = LogLevel.Error, + Message = "Error occurred", + Exception = new Exception("Something went wrong <>&") + }; + + var threadid = Environment.CurrentManagedThreadId; + var machinename = Environment.MachineName; + var result = log4jLayout.Render(logEventInfo); + Assert.Equal($"Error occurred&]]>", result); + } + + [Fact] + public void Log4JXmlEventLayout_IncludeEventProperties_Test() + { + var log4jLayout = new Log4JXmlEventLayout + { + IncludeEventProperties = true, + AppInfo = "MyApp", + }; + + var pathString = new KeyValuePair("path", "/test"); + var logEvent = LogEventInfo.Create(LogLevel.Info, "TestLogger", null, "Test Message {Path}", new object[] { pathString }); + logEvent.TimeStamp = new DateTime(2010, 01, 01, 12, 34, 56, DateTimeKind.Utc); + var result = log4jLayout.Render(logEvent); + + var threadid = Environment.CurrentManagedThreadId; + var machinename = Environment.MachineName; + + Assert.Equal($"Test Message [path, /test]", result); + } + + [Fact] + public void Log4JXmlEventLayout_IncludeScopeNested_Test() + { + var log4jLayout = new Log4JXmlEventLayout + { + IncludeScopeNested = true, + ScopeNestedSeparator = "::", + AppInfo = "MyApp", + }; + + ScopeContext.Clear(); + ScopeContext.PushNestedState("One"); + ScopeContext.PushNestedState("Two"); + ScopeContext.PushNestedState("Three"); + + var logEventInfo = new LogEventInfo + { + LoggerName = "TestLogger", + TimeStamp = new DateTime(2010, 01, 01, 12, 34, 56, DateTimeKind.Utc), + Level = LogLevel.Info, + Message = "Nested scope log test" + }; + + var threadid = Environment.CurrentManagedThreadId; + var machinename = Environment.MachineName; + var result = log4jLayout.Render(logEventInfo); + Assert.Equal($"Nested scope log testOne::Two::Three", result); + } + + [Fact] + public void Log4JXmlEventLayout_ThrowableWithoutCData_EncodesCorrectly() + { + var log4jLayout = new Log4JXmlEventLayout + { + WriteThrowableCData = false, + AppInfo = "TestApp", + }; + + var logEvent = new LogEventInfo(LogLevel.Error, "TestLogger", "Error occurred") + { + LoggerName = "TestLogger", + TimeStamp = new DateTime(2010, 01, 01, 12, 34, 56, DateTimeKind.Utc), + Level = LogLevel.Error, + Exception = new Exception("Boom < & >") + }; + + var threadid = Environment.CurrentManagedThreadId; + var machinename = Environment.MachineName; + var result = log4jLayout.Render(logEvent); + Assert.Equal($"Error occurredSystem.Exception: Boom < & >", result); + } + + [Fact] + public void Log4JXmlEventLayout_Should_Remove_InvalidXmlCharacters() + { + // Arrange + var sb = new System.Text.StringBuilder(); + + // Build a string with all legal XML characters, plus some "known safe" + var validContent = new StringBuilder("abc"); // something we can track + int start = 0; int end = 0xFFFD; + for (int i = start; i <= end; i++) + { + char c = (char)i; + if (!char.IsSurrogate(c) && XmlConvert.IsXmlChar(c)) + { + validContent.Append(c); + } + } + + var badString = validContent.ToString(); + + var log4jLayout = new Log4JXmlEventLayout() + { + FormattedMessage = badString, // assigning directly + }; + + var logEventInfo = new LogEventInfo(LogLevel.Info, "loggerName", badString); + + // Act + string xmlOutput = log4jLayout.Render(logEventInfo); + + // Assert: Try reading the XML back and verify content + string wrappedXml = $"{xmlOutput}"; + string goodString = null; + + using (var reader = XmlReader.Create(new StringReader(wrappedXml))) + { + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Text) + { + if (reader.Value.Contains("abc")) + goodString = reader.Value; + } + } + } + + Assert.NotNull(goodString); + Assert.Contains("abc", goodString); + } + } +} diff --git a/tests/NLog.Targets.Network.Tests/NLog.Targets.Network.Tests.csproj b/tests/NLog.Targets.Network.Tests/NLog.Targets.Network.Tests.csproj new file mode 100644 index 0000000000..3e3376f641 --- /dev/null +++ b/tests/NLog.Targets.Network.Tests/NLog.Targets.Network.Tests.csproj @@ -0,0 +1,32 @@ + + + + 17.0 + net462 + net462;net8.0 + + false + + NLogTests.snk + false + true + true + + Full + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/tests/NLog.Targets.Network.Tests/NLogTests.snk b/tests/NLog.Targets.Network.Tests/NLogTests.snk new file mode 100644 index 0000000000..f168e4dbec Binary files /dev/null and b/tests/NLog.Targets.Network.Tests/NLogTests.snk differ diff --git a/tests/NLog.UnitTests/Internal/NetworkSenders/HttpNetworkSenderTests.cs b/tests/NLog.Targets.Network.Tests/NetworkSenders/HttpNetworkSenderTests.cs similarity index 89% rename from tests/NLog.UnitTests/Internal/NetworkSenders/HttpNetworkSenderTests.cs rename to tests/NLog.Targets.Network.Tests/NetworkSenders/HttpNetworkSenderTests.cs index c63e13687e..c1c139d796 100644 --- a/tests/NLog.UnitTests/Internal/NetworkSenders/HttpNetworkSenderTests.cs +++ b/tests/NLog.Targets.Network.Tests/NetworkSenders/HttpNetworkSenderTests.cs @@ -31,19 +31,23 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Security.Authentication; -using NLog.Config; -using NLog.Internal.NetworkSenders; -using NLog.Targets; -using NLog.UnitTests.Mocks; -using NSubstitute; -using Xunit; - -namespace NLog.UnitTests.Internal.NetworkSenders +namespace NLog.Targets.Network { - public class HttpNetworkSenderTests : NLogTestBase + using System; + using System.Security.Authentication; + using System.Security.Cryptography.X509Certificates; + using NLog.Config; + using NLog.Internal.NetworkSenders; + using NSubstitute; + using Xunit; + + public class HttpNetworkSenderTests { + public HttpNetworkSenderTests() + { + LogManager.ThrowExceptions = true; + } + /// /// Test via /// @@ -85,7 +89,7 @@ public void HttpNetworkSenderViaNetworkTargetTest() Assert.Equal("HttpHappyPathTestLogger|test message1|", requestedString); Assert.Equal("POST", mock.Method); - networkSenderFactoryMock.Received(1).Create("http://test.with.mock", 1234, NetworkTargetQueueOverflowAction.Block, 0, SslProtocols.None, TimeSpan.Zero, TimeSpan.Zero); + networkSenderFactoryMock.Received(1).Create("http://test.with.mock", null, networkTarget); // Cleanup mock.Dispose(); @@ -131,7 +135,7 @@ public void HttpNetworkSenderViaNetworkTargetRecoveryTest() Assert.Equal("HttpHappyPathTestLogger|test message2|", requestedString); Assert.Equal("POST", mock.Method); - networkSenderFactoryMock.Received(1).Create("http://test.with.mock", 1234, NetworkTargetQueueOverflowAction.Block, 0, SslProtocols.None, TimeSpan.Zero, TimeSpan.Zero); // Only created one HttpNetworkSender + networkSenderFactoryMock.Received(1).Create("http://test.with.mock", null, networkTarget); // Only created one HttpNetworkSender // Cleanup mock.Dispose(); @@ -142,7 +146,7 @@ private static INetworkSenderFactory CreateNetworkSenderFactoryMock(WebRequestMo { var networkSenderFactoryMock = Substitute.For(); - networkSenderFactoryMock.Create(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + networkSenderFactoryMock.Create(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(url => new HttpNetworkSender(url.Arg()) { HttpRequestFactory = new WebRequestFactoryMock(webRequestMock) diff --git a/tests/NLog.Targets.Network.Tests/NetworkSenders/ManualDisposableMemoryStream.cs b/tests/NLog.Targets.Network.Tests/NetworkSenders/ManualDisposableMemoryStream.cs new file mode 100644 index 0000000000..2129f3f815 --- /dev/null +++ b/tests/NLog.Targets.Network.Tests/NetworkSenders/ManualDisposableMemoryStream.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.Network +{ + using System.IO; + + /// + /// Memorystream that doesn't dispose by default for asserting the content + /// + public sealed class ManualDisposableMemoryStream : MemoryStream + { + public bool Disposed { get; set; } + protected override void Dispose(bool disposing) + { + Disposed = true; + } + public void RealDispose() + { + base.Dispose(true); + } + } +} diff --git a/tests/NLog.UnitTests/Internal/NetworkSenders/TcpNetworkSenderTests.cs b/tests/NLog.Targets.Network.Tests/NetworkSenders/TcpNetworkSenderTests.cs similarity index 98% rename from tests/NLog.UnitTests/Internal/NetworkSenders/TcpNetworkSenderTests.cs rename to tests/NLog.Targets.Network.Tests/NetworkSenders/TcpNetworkSenderTests.cs index a322d6df2f..fcf97e5b03 100644 --- a/tests/NLog.UnitTests/Internal/NetworkSenders/TcpNetworkSenderTests.cs +++ b/tests/NLog.Targets.Network.Tests/NetworkSenders/TcpNetworkSenderTests.cs @@ -31,7 +31,7 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.UnitTests.Internal.NetworkSenders +namespace NLog.Targets.Network { using System; using System.Collections.Generic; @@ -43,8 +43,13 @@ namespace NLog.UnitTests.Internal.NetworkSenders using NLog.Internal.NetworkSenders; using Xunit; - public class TcpNetworkSenderTests : NLogTestBase + public class TcpNetworkSenderTests { + public TcpNetworkSenderTests() + { + LogManager.ThrowExceptions = true; + } + [Fact] public void TcpHappyPathTest() { @@ -137,7 +142,7 @@ public void TcpHappyPathTest() public void TcpProxyTest() { var sender = new TcpNetworkSender("tcp://foo:1234", AddressFamily.Unspecified); - var socket = sender.CreateSocket("foo", AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp, TimeSpan.Zero); + var socket = sender.CreateSocket("foo", AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); Assert.IsType(socket); } @@ -265,7 +270,7 @@ public MyTcpNetworkSender(string url, AddressFamily addressFamily) Log = new StringWriter(); } - protected internal override ISocket CreateSocket(string host, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, TimeSpan sendTimeout) + protected internal override ISocket CreateSocket(string host, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) { return new MockSocket(addressFamily, socketType, protocolType, this); } diff --git a/tests/NLog.UnitTests/Internal/NetworkSenders/UdpNetworkSenderTests.cs b/tests/NLog.Targets.Network.Tests/NetworkSenders/UdpNetworkSenderTests.cs similarity index 94% rename from tests/NLog.UnitTests/Internal/NetworkSenders/UdpNetworkSenderTests.cs rename to tests/NLog.Targets.Network.Tests/NetworkSenders/UdpNetworkSenderTests.cs index 89e9da3f6c..0ee65bf38b 100644 --- a/tests/NLog.UnitTests/Internal/NetworkSenders/UdpNetworkSenderTests.cs +++ b/tests/NLog.Targets.Network.Tests/NetworkSenders/UdpNetworkSenderTests.cs @@ -31,16 +31,19 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.UnitTests.Internal.NetworkSenders +namespace NLog.Targets.Network { using System.Net.Sockets; - using NLog.Internal.NetworkSenders; - using Xunit; public class UdpNetworkSenderTests { + public UdpNetworkSenderTests() + { + LogManager.ThrowExceptions = true; + } + [Theory] [InlineData("udp://127.0.0.1:8080", false)] [InlineData("udp://255.255.255.255:8080", true)] diff --git a/tests/NLog.UnitTests/Mocks/WebRequestFactoryMock.cs b/tests/NLog.Targets.Network.Tests/NetworkSenders/WebRequestFactoryMock.cs similarity index 95% rename from tests/NLog.UnitTests/Mocks/WebRequestFactoryMock.cs rename to tests/NLog.Targets.Network.Tests/NetworkSenders/WebRequestFactoryMock.cs index 9888152d07..9f01b6a08c 100644 --- a/tests/NLog.UnitTests/Mocks/WebRequestFactoryMock.cs +++ b/tests/NLog.Targets.Network.Tests/NetworkSenders/WebRequestFactoryMock.cs @@ -31,12 +31,12 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.Net; -using NLog.Internal.NetworkSenders; - -namespace NLog.UnitTests.Mocks +namespace NLog.Targets.Network { + using System; + using System.Net; + using NLog.Internal.NetworkSenders; + [Obsolete("WebRequest is obsolete. Use HttpClient instead.")] class WebRequestFactoryMock : IWebRequestFactory { diff --git a/tests/NLog.UnitTests/Mocks/WebRequestMock.cs b/tests/NLog.Targets.Network.Tests/NetworkSenders/WebRequestMock.cs similarity index 97% rename from tests/NLog.UnitTests/Mocks/WebRequestMock.cs rename to tests/NLog.Targets.Network.Tests/NetworkSenders/WebRequestMock.cs index fa714a0fe0..5d89419463 100644 --- a/tests/NLog.UnitTests/Mocks/WebRequestMock.cs +++ b/tests/NLog.Targets.Network.Tests/NetworkSenders/WebRequestMock.cs @@ -31,13 +31,13 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System; -using System.IO; -using System.Net; -using NSubstitute; - -namespace NLog.UnitTests.Mocks +namespace NLog.Targets.Network { + using System; + using System.IO; + using System.Net; + using NSubstitute; + [Obsolete("WebRequest is obsolete. Use HttpClient instead.")] public sealed class WebRequestMock : WebRequest, IDisposable { diff --git a/tests/NLog.UnitTests/Targets/NetworkTargetTests.cs b/tests/NLog.Targets.Network.Tests/NetworkTargetTests.cs similarity index 86% rename from tests/NLog.UnitTests/Targets/NetworkTargetTests.cs rename to tests/NLog.Targets.Network.Tests/NetworkTargetTests.cs index c64a50fa37..2a35bcca6d 100644 --- a/tests/NLog.UnitTests/Targets/NetworkTargetTests.cs +++ b/tests/NLog.Targets.Network.Tests/NetworkTargetTests.cs @@ -31,7 +31,7 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.UnitTests.Targets +namespace NLog.Targets.Network { using System; using System.Collections.Generic; @@ -43,13 +43,17 @@ namespace NLog.UnitTests.Targets using System.Text; using System.Threading; using NLog.Common; - using NLog.Config; - using NLog.Internal.NetworkSenders; using NLog.Targets; + using NLog.Internal.NetworkSenders; using Xunit; - public class NetworkTargetTests : NLogTestBase + public class NetworkTargetTests { + public NetworkTargetTests() + { + LogManager.ThrowExceptions = true; + } + [Fact] public void HappyPathDefaultsTest() { @@ -77,7 +81,8 @@ private static void HappyPathTest(LineEndingMode lineEnding, params string[] mes target.Layout = "${message}"; target.LineEnding = lineEnding; target.KeepConnection = true; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; var exceptions = new List(); var mre = new ManualResetEvent(false); @@ -107,7 +112,7 @@ private static void HappyPathTest(LineEndingMode lineEnding, params string[] mes Assert.Single(senderFactory.Senders); var sender = senderFactory.Senders[0]; - target.Close(); + logFactory.Shutdown(); // Get the length of all the messages and their line endings var eol = lineEnding?.NewLineCharacters ?? string.Empty; @@ -151,7 +156,8 @@ public void NetworkTargetMultipleConnectionsTest() target.SenderFactory = senderFactory; target.Layout = "${message}"; target.KeepConnection = true; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; var exceptions = new List(); var mre = new ManualResetEvent(false); @@ -187,7 +193,7 @@ public void NetworkTargetMultipleConnectionsTest() target.Flush(flushContinuation); Assert.True(mre.WaitOne(10000), "Network Flush not completed"); - target.Close(); + logFactory.Shutdown(); var actual = senderFactory.Log.ToString(); Assert.Contains("1: connect tcp://logger1.company.lan/", actual); @@ -213,7 +219,8 @@ public void NothingToFlushTest() target.SenderFactory = senderFactory; target.Layout = "${message}"; target.KeepConnection = true; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; var mre = new ManualResetEvent(false); @@ -224,7 +231,7 @@ public void NothingToFlushTest() target.Flush(flushContinuation); Assert.True(mre.WaitOne(10000), "Network Flush not completed"); - target.Close(); + logFactory.Shutdown(); string expectedLog = @""; Assert.Equal(expectedLog, senderFactory.Log.ToString()); @@ -240,7 +247,8 @@ public void NetworkTargetMultipleConnectionsWithCacheOverflowTest() target.Layout = "${message}"; target.KeepConnection = true; target.ConnectionCacheSize = 2; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; var exceptions = new List(); var mre = new ManualResetEvent(false); @@ -271,7 +279,7 @@ public void NetworkTargetMultipleConnectionsWithCacheOverflowTest() Assert.True(ex == null, $"Network Write Exception: {ex?.ToString()}"); } - target.Close(); + logFactory.Shutdown(); string result = senderFactory.Log.ToString(); Assert.Contains("1: connect tcp://logger1.company.lan/", result); @@ -299,7 +307,8 @@ public void NetworkTargetMultipleConnectionsWithoutKeepAliveTest() target.SenderFactory = senderFactory; target.Layout = "${message}"; target.KeepConnection = false; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; var exceptions = new List(); var mre = new ManualResetEvent(false); @@ -329,7 +338,7 @@ public void NetworkTargetMultipleConnectionsWithoutKeepAliveTest() Assert.True(ex == null, $"Network Write Exception: {ex?.ToString()}"); } - target.Close(); + logFactory.Shutdown(); string result = senderFactory.Log.ToString(); Assert.Contains("1: connect tcp://logger1.company.lan/", result); @@ -363,7 +372,8 @@ public void NetworkTargetMultipleConnectionsWithMessageDiscardTest() target.KeepConnection = true; target.MaxMessageSize = 10; target.OnOverflow = NetworkTargetOverflowAction.Discard; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; int droppedLogs = 0; target.LogEventDropped += (sender, args) => droppedLogs++; @@ -393,7 +403,7 @@ public void NetworkTargetMultipleConnectionsWithMessageDiscardTest() Assert.True(ex == null, $"Network Write Exception: {ex?.ToString()}"); } - target.Close(); + logFactory.Shutdown(); string result = senderFactory.Log.ToString(); Assert.Contains("1: connect tcp://logger1.company.lan/", result); @@ -419,7 +429,8 @@ public void NetworkTargetMultipleConnectionsWithMessageErrorTest() target.KeepConnection = true; target.MaxMessageSize = 10; target.OnOverflow = NetworkTargetOverflowAction.Error; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; int droppedLogs = 0; target.LogEventDropped += (sender, args) => droppedLogs++; @@ -449,7 +460,7 @@ public void NetworkTargetMultipleConnectionsWithMessageErrorTest() Assert.Equal("NetworkTarget: Discarded LogEvent because MessageSize=15 is above MaxMessageSize=10", exceptions[1].Message); Assert.Null(exceptions[2]); - target.Close(); + logFactory.Shutdown(); string result = senderFactory.Log.ToString(); Assert.Contains("1: connect tcp://logger1.company.lan/", result); @@ -492,7 +503,7 @@ public void NetworkTargetSendFailureTests() } }; - target.Initialize(null); + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; var exceptions = new List(); var mre = new ManualResetEvent(false); @@ -522,7 +533,7 @@ public void NetworkTargetSendFailureTests() Assert.Null(exceptions[3]); Assert.Null(exceptions[4]); - target.Close(); + logFactory.Shutdown(); var result = senderFactory.Log.ToString(); Assert.Contains("1: connect tcp://logger1.company.lan/", result); @@ -595,7 +606,8 @@ public void NetworkTargetTcpTest() } }, null); - target.Initialize(new LoggingConfiguration()); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; int pendingWrites = 100; var writeCompleted = new ManualResetEvent(false); @@ -624,7 +636,7 @@ public void NetworkTargetTcpTest() } Assert.True(writeCompleted.WaitOne(10000), "Network Writes did not complete"); - target.Close(); + logFactory.Shutdown(); foreach (var ex in exceptions) { Assert.True(ex == null, $"Network Write Exception: {ex?.ToString()}"); @@ -639,13 +651,41 @@ public void NetworkTargetTcpTest() [Fact] public void NetworkTargetUdpSplitEnabledTest() { - RetryingIntegrationTest(3, () => NetworkTargetUdpTest(true)); + for (int i = 1; i <= 3; ++i) + { + try + { + NetworkTargetUdpTest(true); + break; + } + catch + { + if (i == 3) + throw; + + System.Threading.Thread.Sleep(1000 * i); + } + } } [Fact] public void NetworkTargetUdpSplitDisabledTest() { - RetryingIntegrationTest(3, () => NetworkTargetUdpTest(false)); + for (int i = 1; i <= 3; ++i) + { + try + { + NetworkTargetUdpTest(false); + break; + } + catch + { + if (i == 3) + throw; + + System.Threading.Thread.Sleep(1000 * i); + } + } } private static int getNewNetworkPort() @@ -727,7 +767,7 @@ private static void NetworkTargetUdpTest(bool splitMessage) remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); listener.BeginReceiveFrom(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, ref remoteEndPoint, receivedDatagram, null); - target.Initialize(new LoggingConfiguration()); + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; var writeCompleted = new ManualResetEvent(false); var exceptions = new List(); @@ -754,7 +794,7 @@ private static void NetworkTargetUdpTest(bool splitMessage) } Assert.True(writeCompleted.WaitOne(10000), "Network Write not completed"); - target.Close(); + logFactory.Shutdown(); foreach (var ex in exceptions) { Assert.True(ex == null, $"Network Write Exception: {ex?.ToString()}"); @@ -779,7 +819,7 @@ public void NetworkTargetNotConnectedTest() KeepConnection = true, }; - target.Initialize(new LoggingConfiguration()); + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; int toWrite = 10; int pendingWrites = toWrite; @@ -800,27 +840,28 @@ public void NetworkTargetNotConnectedTest() } }; - var loggerTask = new NLog.Internal.AsyncHelpersTask(state => + WaitCallback loggerTask = (state) => { for (int i = 0; i < toWrite; ++i) { var ev = new LogEventInfo(LogLevel.Info, "logger1", "message" + i).WithContinuation(writeFinished); target.WriteAsyncLogEvent(ev); } - }); - AsyncHelpers.StartAsyncTask(loggerTask, null); + }; + System.Threading.ThreadPool.QueueUserWorkItem(loggerTask, null); Assert.True(writeCompleted.WaitOne(10000), "Network Write not completed"); var shutdownCompleted = new ManualResetEvent(false); - var closeTask = new NLog.Internal.AsyncHelpersTask(state => + WaitCallback closeTask = (state) => { // no exception - target.Close(); shutdownCompleted.Set(); - }); - AsyncHelpers.StartAsyncTask(closeTask, null); + }; + System.Threading.ThreadPool.QueueUserWorkItem(closeTask, null); Assert.True(shutdownCompleted.WaitOne(10000), "Network Close not completed"); + logFactory.Shutdown(); + Assert.Equal(toWrite, exceptions.Count); foreach (var ex in exceptions) { @@ -838,7 +879,8 @@ public void NetworkTargetQueueDiscardTest() target.SenderFactory = senderFactory; target.Layout = "${message}"; target.KeepConnection = true; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; int discardEventCalls = 0; target.LogEventDropped += (sender, args) => discardEventCalls++; @@ -876,6 +918,9 @@ public void NetworkTargetQueueDiscardTest() target.WriteAsyncLogEvent(new LogEventInfo(LogLevel.Info, "logger", $"msg{i}").WithContinuation(asyncContinuation)); } Assert.True(mre.WaitOne(10000), "Network Write not completed"); + + logFactory.Shutdown(); + Assert.True(exceptions.Count >= 6, $"Network write not completed: {exceptions.Count}"); Assert.True(senderFactory.BeginRequestCounter <= 3, $"Network write not discarded: {senderFactory.BeginRequestCounter}"); @@ -898,7 +943,8 @@ public void NetworkTargetQueueGrowTest() target.SenderFactory = senderFactory; target.Layout = "${message}"; target.KeepConnection = true; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; int pendingWrites = 1; var exceptions = new List(); @@ -933,6 +979,9 @@ public void NetworkTargetQueueGrowTest() } Assert.True(exceptions.Count < 4, $"Network write not growing: {exceptions.Count}"); Assert.True(mre.WaitOne(10000), "Network Write not completed"); + + logFactory.Shutdown(); + Assert.Equal(6, exceptions.Count); Assert.Equal(6, senderFactory.BeginRequestCounter); @@ -953,7 +1002,8 @@ public void NetworkTargetQueueBlockTest() target.SenderFactory = senderFactory; target.Layout = "${message}"; target.KeepConnection = true; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; int pendingWrites = 1; var exceptions = new List(); @@ -988,6 +1038,7 @@ public void NetworkTargetQueueBlockTest() } Assert.True(exceptions.Count >= 4, $"Network write not blocking: {exceptions.Count}"); Assert.True(mre.WaitOne(10000), "Network Write not completed"); + logFactory.Shutdown(); Assert.Equal(6, exceptions.Count); Assert.Equal(6, senderFactory.BeginRequestCounter); @@ -1012,7 +1063,8 @@ public void NetworkTargetSendFailureWithoutKeepAliveTests() target.Layout = "${message}"; target.KeepConnection = false; target.OnOverflow = NetworkTargetOverflowAction.Discard; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; var exceptions = new List(); var mre = new ManualResetEvent(false); @@ -1042,7 +1094,7 @@ public void NetworkTargetSendFailureWithoutKeepAliveTests() Assert.Null(exceptions[3]); Assert.Null(exceptions[4]); - target.Close(); + logFactory.Shutdown(); var result = senderFactory.Log.ToString(); Assert.Contains("1: connect tcp://logger1.company.lan/", result); @@ -1079,7 +1131,8 @@ public void AsyncConnectionFailureOnCloseShouldNotDeadlock() target.SenderFactory = senderFactory; target.Layout = "${message}"; target.KeepConnection = true; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; var exceptions = new List(); var writeCompleted = new ManualResetEvent(false); @@ -1097,16 +1150,16 @@ public void AsyncConnectionFailureOnCloseShouldNotDeadlock() } }; - var shutdownTask = new NLog.Internal.AsyncHelpersTask(state => + WaitCallback shutdownTask = (state) => { Thread.Sleep(10); target.Flush(ex => { }); - target.Close(); + logFactory.Shutdown(); shutdownCompleted.Set(); - }); - AsyncHelpers.StartAsyncTask(shutdownTask, null); + }; + System.Threading.ThreadPool.QueueUserWorkItem(shutdownTask, null); - var loggerTask = new NLog.Internal.AsyncHelpersTask(state => + WaitCallback loggerTask = (state) => { for (int i = 0; i < total; ++i) { @@ -1115,8 +1168,8 @@ public void AsyncConnectionFailureOnCloseShouldNotDeadlock() } writeCompleted.Set(); - }); - AsyncHelpers.StartAsyncTask(loggerTask, null); + }; + System.Threading.ThreadPool.QueueUserWorkItem(loggerTask, null); Assert.True(writeCompleted.WaitOne(10000), "Network Write not completed"); Assert.True(shutdownCompleted.WaitOne(10000), "Network Shutdown not completed"); @@ -1124,19 +1177,69 @@ public void AsyncConnectionFailureOnCloseShouldNotDeadlock() [Theory] [InlineData("none", SslProtocols.None)] //we can't set it on "" +#pragma warning disable SYSLIB0039 // Type or member is obsolete [InlineData("tls", SslProtocols.Tls)] [InlineData("tls11", SslProtocols.Tls11)] [InlineData("tls,tls11", SslProtocols.Tls11 | SslProtocols.Tls)] +#pragma warning restore SYSLIB0039 // Type or member is obsolete +#if NET6_0_OR_GREATER + [InlineData("Tls13", SslProtocols.Tls13)] +#endif public void SslProtocolsConfigTest(string sslOptions, SslProtocols expected) { - var config = XmlLoggingConfiguration.CreateFromXmlString($@" + var logFactory = new LogFactory().Setup().SetupExtensions(ext => ext.RegisterTarget()) + .LoadConfigurationFromXml($@" - "); + ").LogFactory; - var target = config.FindTargetByName("target1"); + var target = logFactory.Configuration.FindTargetByName("target1"); Assert.Equal(expected, target.SslProtocols); + + logFactory.Shutdown(); + } + + [Fact] + public void InvalidSslCertificateFilePath() + { + var senderFactory = new MyQueudSenderFactory(); + + var target = new NetworkTarget(); + target.Address = "tcp://${logger}.company.lan/"; + target.SenderFactory = senderFactory; + target.Layout = "${message}"; + target.KeepConnection = true; + target.SslProtocols = SslProtocols.Tls12; + target.SslCertificateFile = "{Invalid-Certificate-Path}"; + target.SslCertificatePassword = ""; + + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory) + { + var exception = Assert.Throws(() => logFactory.GetCurrentClassLogger().Info("Fails because invalid SSL certificate path")); + Assert.Contains("SSL", exception.Message); + } + } + + [Fact] + public void EmptySslCertificateFilePath() + { + var senderFactory = new MyQueudSenderFactory(); + senderFactory.FailCounter = 1; + + var target = new NetworkTarget(); + target.Address = "tcp://[Invalid-SSL-Host]"; + target.SenderFactory = senderFactory; + target.Layout = "${message}"; + target.KeepConnection = true; + target.SslProtocols = SslProtocols.Tls12; + target.SslCertificateFile = ""; + target.SslCertificatePassword = ""; + + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory) + { + Assert.Throws(() => logFactory.GetCurrentClassLogger().Info("Fails because invalid SSL Host Address")); + } } [Theory] @@ -1144,7 +1247,8 @@ public void SslProtocolsConfigTest(string sslOptions, SslProtocols expected) [InlineData("30", 30)] public void KeepAliveTimeConfigTest(string keepAliveTimeSeconds, int expected) { - var logFactory = new LogFactory().Setup().LoadConfigurationFromXml($@" + var logFactory = new LogFactory().Setup().SetupExtensions(ext => ext.RegisterTarget()) + .LoadConfigurationFromXml($@" @@ -1162,7 +1266,8 @@ public void KeepAliveTimeConfigTest(string keepAliveTimeSeconds, int expected) [InlineData("30", 30)] public void SendTimeoutConfigTest(string sendTimeoutSeconds, int expected) { - var logFactory = new LogFactory().Setup().LoadConfigurationFromXml($@" + var logFactory = new LogFactory().Setup().SetupExtensions(ext => ext.RegisterTarget()) + .LoadConfigurationFromXml($@" @@ -1175,12 +1280,32 @@ public void SendTimeoutConfigTest(string sendTimeoutSeconds, int expected) logger.Info("Hello"); } + [Theory] + [InlineData("false", false)] + [InlineData("true", true)] + public void NoDelayConfigTest(string noDelay, bool expected) + { + var logFactory = new LogFactory().Setup().SetupExtensions(ext => ext.RegisterTarget()) + .LoadConfigurationFromXml($@" + + + + ").LogFactory; + + var target = logFactory.Configuration.FindTargetByName("target1"); + Assert.Equal(expected, target.NoDelay); + + var logger = logFactory.GetLogger("SendTimeoutSeconds"); + logger.Info("Hello"); + } + [Fact] public void Bug3990StackOverflowWhenUsingNLogViewerTarget() { // this would fail because of stack overflow in the // constructor of NLogViewerTarget - var config = XmlLoggingConfiguration.CreateFromXmlString(@" + var logFactory = new LogFactory().Setup().SetupExtensions(ext => ext.RegisterTarget("NLogViewer")) + .LoadConfigurationFromXml(@" @@ -1188,9 +1313,9 @@ public void Bug3990StackOverflowWhenUsingNLogViewerTarget() -"); +").LogFactory; - var target = config.LoggingRules[0].Targets[0] as NLogViewerTarget; + var target = logFactory.Configuration.LoggingRules[0].Targets[0] as Log4JXmlTarget; Assert.NotNull(target); } @@ -1206,7 +1331,8 @@ public void GzipCompressionTest() target.Compress = NetworkTargetCompressionType.GZip; target.CompressMinBytes = 15; target.LineEnding = LineEndingMode.CRLF; - target.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; var exceptions = new List(); var mre = new ManualResetEvent(false); @@ -1237,6 +1363,8 @@ public void GzipCompressionTest() var bigMessage = target.Encoding.GetString(outstream.GetBuffer(), 0, (int)outstream.Length); Assert.Equal("superbigmessage\r\n", bigMessage); } + + logFactory.Shutdown(); } internal sealed class MyQueudSenderFactory : INetworkSenderFactory @@ -1245,9 +1373,9 @@ internal sealed class MyQueudSenderFactory : INetworkSenderFactory internal StringWriter Log = new StringWriter(); private int _idCounter; - public QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQueueOverflowAction onQueueOverflow, int maxMessageSize, SslProtocols sslProtocols, TimeSpan keepAliveTime, TimeSpan sendTimeout) + public QueuedNetworkSender Create(string url, System.Security.Cryptography.X509Certificates.X509Certificate2Collection sslCertificateOverride, NetworkTarget networkTarget) { - var sender = new MyQueudNetworkSender(url, ++_idCounter, Log, this) { MaxQueueSize = maxQueueSize, OnQueueOverflow = onQueueOverflow }; + var sender = new MyQueudNetworkSender(url, ++_idCounter, Log, this) { MaxQueueSize = networkTarget.MaxQueueSize, OnQueueOverflow = networkTarget.OnQueueOverflow }; Senders.Add(sender); return sender; } @@ -1269,7 +1397,7 @@ internal sealed class MyQueudNetworkSender : QueuedNetworkSender public MemoryStream MemoryStream { get; } = new MemoryStream(); public MyQueudNetworkSender(string url, int id, TextWriter log, MyQueudSenderFactory senderFactory) - : base(url) + : base(new Uri(url).ToString()) { _id = id; _log = log; @@ -1310,8 +1438,8 @@ protected override void BeginRequest(NetworkRequestArgs eventArgs) if (_senderFactory.AsyncMode) { - var asyncTask = new NLog.Internal.AsyncHelpersTask(state => { SendSync(eventArgs); }); - AsyncHelpers.StartAsyncTask(asyncTask, null); + WaitCallback asyncTask = (state) => SendSync(eventArgs); + System.Threading.ThreadPool.QueueUserWorkItem(asyncTask, null); } else { diff --git a/tests/NLog.Targets.Network.Tests/SplunkLayoutTests.cs b/tests/NLog.Targets.Network.Tests/SplunkLayoutTests.cs new file mode 100644 index 0000000000..be9954af05 --- /dev/null +++ b/tests/NLog.Targets.Network.Tests/SplunkLayoutTests.cs @@ -0,0 +1,312 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.Network +{ + using System; + using NLog.Layouts; + using Xunit; + + public class SplunkLayoutTests + { + private static readonly string HostName = ResolveHostname(); + + public SplunkLayoutTests() + { + LogManager.ThrowExceptions = true; + LogManager.Setup().SetupExtensions(ext => + { + ext.RegisterLayout(); + }); + } + + [Fact] + public void CanRenderSplunk() + { + var splunkLayout = new SplunkLayout(); + + var memTarget = new NLog.Targets.MemoryTarget() { Layout = splunkLayout }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var loggerName = "TestLogger"; + var dateTime = DateTime.Now; + var message = "hello, splunk :)"; + var logLevel = LogLevel.Info; + + var logEvent = new LogEventInfo + { + LoggerName = loggerName, + Level = logLevel, + Message = message, + TimeStamp = dateTime, + }; + + logFactory.GetLogger(loggerName).Log(logEvent); + Assert.Single(memTarget.Logs); + var renderedSplunk = memTarget.Logs[0]; + + var expectedDateTime = SplunkLayout.ToUnixTimeStamp(dateTime); + var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; + var expectedSplunk = string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{{" + "\"time\":{0}," + + "\"host\":\"{1}\"," + + "\"source\":\"{2}\"," + + "\"event\":{{" + + "\"level\":\"{3}\"," + + "\"message\":\"{4}\"," + + "\"logger\":\"{5}\"" + + "}}}}", + expectedDateTime, + HostName, + processName, + logLevel, + message, + loggerName); + + Assert.Equal(expectedSplunk, renderedSplunk); + } + } + + [Fact] + public void CanRenderSplunkSourceType() + { + var splunkLayout = new SplunkLayout(); + + var memTarget = new NLog.Targets.MemoryTarget() { Layout = splunkLayout }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var loggerName = "TestLogger"; + var facility = "TestFacility"; + var dateTime = DateTime.Now; + var message = "hello, splunk :)"; + var logLevel = LogLevel.Info; + + splunkLayout.SplunkSourceType = facility; + + var logEvent = new LogEventInfo + { + LoggerName = loggerName, + Level = logLevel, + Message = message, + TimeStamp = dateTime, + }; + + logFactory.GetLogger(loggerName).Log(logEvent); + Assert.Single(memTarget.Logs); + var renderedSplunk = memTarget.Logs[0]; + + var expectedDateTime = SplunkLayout.ToUnixTimeStamp(dateTime); + var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; + var expectedSplunk = string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{{" + "\"time\":{0}," + + "\"host\":\"{1}\"," + + "\"source\":\"{2}\"," + + "\"sourcetype\":\"{3}\"," + + "\"event\":{{" + + "\"level\":\"{4}\"," + + "\"message\":\"{5}\"," + + "\"logger\":\"{6}\"" + + "}}}}", + expectedDateTime, + HostName, + processName, + facility, + logLevel, + message, + loggerName); + + Assert.Equal(expectedSplunk, renderedSplunk); + } + } + + [Fact] + public void CanRenderSplunkAdditionalEventCustomMessage() + { + var splunkLayout = new SplunkLayout(); + splunkLayout.SplunkEvents.Add(new JsonAttribute("threadid", "${threadid}") { Encode = false }); + + var memTarget = new NLog.Targets.MemoryTarget() { Layout = splunkLayout }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget).WithAsync()).LogFactory) + { + var loggerName = "TestLogger"; + var dateTime = DateTime.Now; + var message = "hello, splunk :)"; + var logLevel = LogLevel.Info; + + var logEvent = new LogEventInfo + { + LoggerName = loggerName, + Level = logLevel, + Message = message, + TimeStamp = dateTime, + }; + + logFactory.GetLogger(loggerName).Log(logEvent); + logFactory.Flush(); + Assert.Single(memTarget.Logs); + var renderedSplunk = memTarget.Logs[0]; + + int threadId = System.Threading.Thread.CurrentThread.ManagedThreadId; + var expectedDateTime = SplunkLayout.ToUnixTimeStamp(dateTime); + var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; + var expectedSplunk = string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{{" + "\"time\":{0}," + + "\"host\":\"{1}\"," + + "\"source\":\"{2}\"," + + "\"event\":{{" + + "\"threadid\":{3}" + + "}}}}", + expectedDateTime, + HostName, + processName, + threadId); + + Assert.Equal(expectedSplunk, renderedSplunk); + } + } + + [Fact] + public void CanRenderEventProperties() + { + var splunkLayout = new SplunkLayout(); + splunkLayout.SplunkEvents.Add(new JsonAttribute("mt", "${message:raw=true}")); + splunkLayout.IncludeEventProperties = true; + + var memTarget = new NLog.Targets.MemoryTarget() { Layout = splunkLayout }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget).WithAsync()).LogFactory) + { + var loggerName = "TestLogger"; + var dateTime = DateTime.Now; + var message = "hello, {world} from {RequestId} :)"; + var logLevel = LogLevel.Info; + var requestId = Guid.NewGuid(); + + var logEvent = new LogEventInfo + { + TimeStamp = dateTime, + LoggerName = loggerName, + Level = logLevel, + Message = message, + Parameters = new object[] { "Splunk", requestId }, + }; + + logFactory.GetLogger(loggerName).Log(logEvent); + logFactory.Flush(); + Assert.Single(memTarget.Logs); + var renderedSplunk = memTarget.Logs[0]; + + var expectedDateTime = SplunkLayout.ToUnixTimeStamp(dateTime); + var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; + var expectedSplunk = string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{{" + "\"time\":{0}," + + "\"host\":\"{1}\"," + + "\"source\":\"{2}\"," + + "\"event\":{{" + + "\"mt\":\"{3}\"," + + "\"world\":\"Splunk\"," + + "\"RequestId\":\"{4}\"" + + "}}}}", + expectedDateTime, + HostName, + processName, + message, + requestId); + + Assert.Equal(expectedSplunk, renderedSplunk); + } + } + + [Fact] + public void CanRenderScopeContext() + { + var splunkLayout = new SplunkLayout(); + splunkLayout.SplunkEvents.Add(new JsonAttribute("mt", "${message:raw=true}")); + splunkLayout.IncludeEventProperties = true; + splunkLayout.IncludeScopeProperties = true; + splunkLayout.ExcludeProperties.Add("World"); + + var memTarget = new NLog.Targets.MemoryTarget() { Layout = splunkLayout }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget).WithAsync()).LogFactory) + { + var loggerName = "TestLogger"; + var dateTime = DateTime.Now; + var message = "hello, {world} :)"; + var logLevel = LogLevel.Info; + + var logEvent = new LogEventInfo + { + TimeStamp = dateTime, + LoggerName = loggerName, + Level = logLevel, + Message = message, + Parameters = new object[] { "Splunk" }, + }; + + var requestId = Guid.NewGuid(); + using (logFactory.GetLogger(loggerName).PushScopeProperty("RequestId", requestId)) + { + logFactory.GetLogger(loggerName).Log(logEvent); + } + logFactory.Flush(); + Assert.Single(memTarget.Logs); + var renderedSplunk = memTarget.Logs[0]; + + var expectedDateTime = SplunkLayout.ToUnixTimeStamp(dateTime); + var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; + var expectedSplunk = string.Format(System.Globalization.CultureInfo.InvariantCulture, + "{{" + "\"time\":{0}," + + "\"host\":\"{1}\"," + + "\"source\":\"{2}\"," + + "\"event\":{{" + + "\"mt\":\"{3}\"," + + "\"RequestId\":\"{4}\"" + + "}}}}", + expectedDateTime, + HostName, + processName, + message, + requestId); + + Assert.Equal(expectedSplunk, renderedSplunk); + } + } + + static string ResolveHostname() + { + return Environment.GetEnvironmentVariable("HOSTNAME") + ?? Environment.GetEnvironmentVariable("COMPUTERNAME") + ?? Environment.GetEnvironmentVariable("MACHINENAME") + ?? Environment.MachineName; + } + } +} diff --git a/tests/NLog.Targets.Network.Tests/SyslogLayoutTests.cs b/tests/NLog.Targets.Network.Tests/SyslogLayoutTests.cs new file mode 100644 index 0000000000..cf5abf94a6 --- /dev/null +++ b/tests/NLog.Targets.Network.Tests/SyslogLayoutTests.cs @@ -0,0 +1,234 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.Targets.Network +{ + using System; + using NLog.Layouts; + using Xunit; + + public class SyslogLayoutTests + { + private static readonly string HostName = ResolveHostname(); + private static readonly string ProcessName = ResolveProcessName(); + private static readonly int ProcessId = ResolveProcessId(); + + public SyslogLayoutTests() + { + LogManager.ThrowExceptions = true; + LogManager.Setup().SetupExtensions(ext => + { + ext.RegisterLayout(); + }); + } + + [Fact] + public void SyslogLayout_Rfc3164() + { + var memTarget = new NLog.Targets.MemoryTarget() { Layout = new SyslogLayout() }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var logger = logFactory.GetCurrentClassLogger(); + var logEvent = new LogEventInfo(LogLevel.Info, null, "Hello World"); + logger.Log(logEvent); + + var dateFormat = logEvent.TimeStamp.Day < 10 ? "{0:MMM d HH:mm:ss}" : "{0:MMM dd HH:mm:ss}"; + var timestamp = string.Format(System.Globalization.CultureInfo.InvariantCulture, dateFormat, logEvent.TimeStamp); + + Assert.Single(memTarget.Logs); + Assert.Equal($"<14>{timestamp} {HostName} {ProcessName}[{ProcessId}]: {logEvent.Message}", memTarget.Logs[0]); + } + } + + [Fact] + public void SyslogLayout_Rfc3164_Newline() + { + var memTarget = new NLog.Targets.MemoryTarget() { Layout = new SyslogLayout() }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var logger = logFactory.GetCurrentClassLogger(); + var logEvent = new LogEventInfo(LogLevel.Info, null, "Hello World\r\nGoodbye World"); + logger.Log(logEvent); + + var dateFormat = logEvent.TimeStamp.Day < 10 ? "{0:MMM d HH:mm:ss}" : "{0:MMM dd HH:mm:ss}"; + var timestamp = string.Format(System.Globalization.CultureInfo.InvariantCulture, dateFormat, logEvent.TimeStamp); + + Assert.Single(memTarget.Logs); + Assert.Equal($"<14>{timestamp} {HostName} {ProcessName}[{ProcessId}]: Hello World Goodbye World", memTarget.Logs[0]); + } + } + + [Theory] + [InlineData("Trace", "<143>")] + [InlineData("Debug", "<143>")] + [InlineData("Info", "<142>")] + [InlineData("Warn", "<140>")] + [InlineData("Error", "<139>")] + [InlineData("Fatal", "<136>")] + public void SyslogLayout_Rfc3164_LogLevels(string logLevel, string priValue) + { + var memTarget = new NLog.Targets.MemoryTarget() { Layout = new SyslogLayout() { SyslogFacility = SyslogFacility.Local1 } }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var logger = logFactory.GetCurrentClassLogger(); + var logEvent = new LogEventInfo(LogLevel.FromString(logLevel), null, "Hello World"); + logger.Log(logEvent); + + var dateFormat = logEvent.TimeStamp.Day < 10 ? "{0:MMM d HH:mm:ss}" : "{0:MMM dd HH:mm:ss}"; + var timestamp = string.Format(System.Globalization.CultureInfo.InvariantCulture, dateFormat, logEvent.TimeStamp); + + Assert.Single(memTarget.Logs); + Assert.Equal($"{priValue}{timestamp} {HostName} {ProcessName}[{ProcessId}]: {logEvent.Message}", memTarget.Logs[0]); + } + } + + [Fact] + public void SyslogLayout_Rfc3164_EscapeNames() + { + var memTarget = new NLog.Targets.MemoryTarget() { Layout = new SyslogLayout() { SyslogHostName = " Hello World ", SyslogAppName = " Destroy World ", SyslogProcessId = " Goodbye World " } }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var logger = logFactory.GetCurrentClassLogger(); + var logEvent = new LogEventInfo(LogLevel.Info, null, "Hello World"); + logger.Log(logEvent); + + var dateFormat = logEvent.TimeStamp.Day < 10 ? "{0:MMM d HH:mm:ss}" : "{0:MMM dd HH:mm:ss}"; + var timestamp = string.Format(System.Globalization.CultureInfo.InvariantCulture, dateFormat, logEvent.TimeStamp); + + Assert.Single(memTarget.Logs); + Assert.Equal($"<14>{timestamp} Hello_World Destroy_World[Goodbye_World]: {logEvent.Message}", memTarget.Logs[0]); + } + } + + [Fact] + public void SyslogLayout_Rfc5424() + { + var memTarget = new NLog.Targets.MemoryTarget() { Layout = new SyslogLayout() { Rfc5424 = true } }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var logger = logFactory.GetCurrentClassLogger(); + var logEvent = LogEventInfo.Create(LogLevel.Info, null, "Hello World"); + logger.Log(logEvent); + + Assert.Single(memTarget.Logs); + Assert.Equal($"<14>1 {logEvent.TimeStamp:o} {HostName} {ProcessName} {ProcessId} - {logEvent.Message}", memTarget.Logs[0]); + } + } + + [Fact] + public void SyslogLayout_Rfc5424_Newline() + { + var memTarget = new NLog.Targets.MemoryTarget() { Layout = new SyslogLayout() { Rfc5424 = true } }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var logger = logFactory.GetCurrentClassLogger(); + var logEvent = LogEventInfo.Create(LogLevel.Info, null, "Hello World\r\nGoodbye World"); + logger.Log(logEvent); + + Assert.Single(memTarget.Logs); + Assert.Equal($"<14>1 {logEvent.TimeStamp:o} {HostName} {ProcessName} {ProcessId} - Hello World\r\nGoodbye World", memTarget.Logs[0]); + } + } + + [Theory] + [InlineData("Trace", "<143>")] + [InlineData("Debug", "<143>")] + [InlineData("Info", "<142>")] + [InlineData("Warn", "<140>")] + [InlineData("Error", "<139>")] + [InlineData("Fatal", "<136>")] + public void SyslogLayout_Rfc5424_LogLevels(string logLevel, string priValue) + { + var memTarget = new NLog.Targets.MemoryTarget() { Layout = new SyslogLayout() { Rfc5424 = true, SyslogFacility = SyslogFacility.Local1 } }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var logger = logFactory.GetCurrentClassLogger(); + var logEvent = LogEventInfo.Create(LogLevel.FromString(logLevel), null, "Hello World"); + logger.Log(logEvent); + + Assert.Single(memTarget.Logs); + Assert.Equal($"{priValue}1 {logEvent.TimeStamp:o} {HostName} {ProcessName} {ProcessId} - {logEvent.Message}", memTarget.Logs[0]); + } + } + + [Fact] + public void SyslogLayout_Rfc5424_EscapeNames() + { + var memTarget = new NLog.Targets.MemoryTarget() { Layout = new SyslogLayout() { Rfc5424 = true } }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget)).LogFactory) + { + var logger = logFactory.GetCurrentClassLogger(); + var logEvent = LogEventInfo.Create(LogLevel.Info, null, "Hello World"); + logger.Log(logEvent); + + Assert.Single(memTarget.Logs); + Assert.Equal($"<14>1 {logEvent.TimeStamp:o} {HostName} {ProcessName} {ProcessId} - {logEvent.Message}", memTarget.Logs[0]); + } + } + + [Fact] + public void SyslogLayout_Rfc5424_StructuredData() + { + var syslogLayout = new SyslogLayout() { Rfc5424 = true }; + syslogLayout.StructuredDataParams.Add(new TargetPropertyWithContext(" ThreadId ", " ${threadid} ")); + syslogLayout.IncludeEventProperties = true; + + var memTarget = new NLog.Targets.MemoryTarget() { Layout = syslogLayout }; + using (var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memTarget).WithAsync()).LogFactory) + { + var guid = Guid.NewGuid(); + + var logger = logFactory.GetCurrentClassLogger(); + var logEvent = LogEventInfo.Create(LogLevel.Info, null, null, "Hello {World} with {CorrelationKey}", new object[] { "\nEarth\n", guid }); + logger.Log(logEvent); + logFactory.Flush(); + + int threadId = System.Threading.Thread.CurrentThread.ManagedThreadId; + Assert.Single(memTarget.Logs); + Assert.Equal($"<14>1 {logEvent.TimeStamp:o} {HostName} {ProcessName} {ProcessId} - [meta World=\" Earth \" CorrelationKey=\"{guid}\" ThreadId=\" {threadId} \"] {logEvent.FormattedMessage}", memTarget.Logs[0]); + } + } + + static string ResolveHostname() + { + return Environment.GetEnvironmentVariable("HOSTNAME") + ?? Environment.GetEnvironmentVariable("COMPUTERNAME") + ?? Environment.GetEnvironmentVariable("MACHINENAME") + ?? Environment.MachineName; + } + + static string ResolveProcessName() => System.Diagnostics.Process.GetCurrentProcess().ProcessName; + + static int ResolveProcessId() => System.Diagnostics.Process.GetCurrentProcess().Id; + } +} diff --git a/tests/NLog.Targets.Trace.Tests/NLog.Targets.Trace.Tests.csproj b/tests/NLog.Targets.Trace.Tests/NLog.Targets.Trace.Tests.csproj new file mode 100644 index 0000000000..8874bd9a74 --- /dev/null +++ b/tests/NLog.Targets.Trace.Tests/NLog.Targets.Trace.Tests.csproj @@ -0,0 +1,31 @@ + + + + 17.0 + net462 + net462;net8.0 + + false + + NLogTests.snk + false + true + true + + Full + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/tests/NLog.Targets.Trace.Tests/NLogTests.snk b/tests/NLog.Targets.Trace.Tests/NLogTests.snk new file mode 100644 index 0000000000..f168e4dbec Binary files /dev/null and b/tests/NLog.Targets.Trace.Tests/NLogTests.snk differ diff --git a/tests/NLog.UnitTests/NLogTraceListenerTests.cs b/tests/NLog.Targets.Trace.Tests/NLogTraceListenerTests.cs similarity index 82% rename from tests/NLog.UnitTests/NLogTraceListenerTests.cs rename to tests/NLog.Targets.Trace.Tests/NLogTraceListenerTests.cs index 1adf5d69ba..96e4935817 100644 --- a/tests/NLog.UnitTests/NLogTraceListenerTests.cs +++ b/tests/NLog.Targets.Trace.Tests/NLogTraceListenerTests.cs @@ -32,22 +32,31 @@ // #define DEBUG +#define TRACE -namespace NLog.UnitTests +namespace NLog.Targets.Trace { using System; using System.Diagnostics; using System.Globalization; using System.Threading; using NLog.Config; + using NLog.LayoutRenderers; using Xunit; - public sealed class NLogTraceListenerTests : NLogTestBase, IDisposable + public sealed class NLogTraceListenerTests : IDisposable { private readonly CultureInfo previousCultureInfo; public NLogTraceListenerTests() { + LogManager.ThrowExceptions = true; + LogManager.Setup().SetupExtensions(ext => + { + ext.RegisterLayoutRenderer(); + ext.RegisterTarget(); + }); + previousCultureInfo = Thread.CurrentThread.CurrentCulture; // set the culture info with the decimal separator (comma) different from InvariantCulture separator (point) Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR"); @@ -59,7 +68,6 @@ public void Dispose() Thread.CurrentThread.CurrentCulture = previousCultureInfo; } -#if !NETSTANDARD1_5 [Fact] public void TraceWriteTest() { @@ -75,16 +83,16 @@ public void TraceWriteTest() Trace.Listeners.Add(new NLogTraceListener { Name = "Logger1" }); Trace.Write("Hello"); - AssertDebugLastMessage("debug", "Logger1 Debug Hello "); + AssertDebugLastMessage("Logger1 Debug Hello "); Trace.Write("Hello", "Cat1"); - AssertDebugLastMessage("debug", "Logger1 Debug Cat1: Hello "); + AssertDebugLastMessage("Logger1 Debug Cat1: Hello "); Trace.Write(3.1415); - AssertDebugLastMessage("debug", $"Logger1 Debug {3.1415} "); + AssertDebugLastMessage($"Logger1 Debug {3.1415} "); Trace.Write(3.1415, "Cat2"); - AssertDebugLastMessage("debug", $"Logger1 Debug Cat2: {3.1415} "); + AssertDebugLastMessage($"Logger1 Debug Cat2: {3.1415} "); } [Fact] @@ -102,16 +110,16 @@ public void TraceWriteLineTest() Trace.Listeners.Add(new NLogTraceListener { Name = "Logger1" }); Trace.WriteLine("Hello"); - AssertDebugLastMessage("debug", "Logger1 Debug Hello "); + AssertDebugLastMessage("Logger1 Debug Hello "); Trace.WriteLine("Hello", "Cat1"); - AssertDebugLastMessage("debug", "Logger1 Debug Cat1: Hello "); + AssertDebugLastMessage("Logger1 Debug Cat1: Hello "); Trace.WriteLine(3.1415); - AssertDebugLastMessage("debug", $"Logger1 Debug {3.1415} "); + AssertDebugLastMessage($"Logger1 Debug {3.1415} "); Trace.WriteLine(3.1415, "Cat2"); - AssertDebugLastMessage("debug", $"Logger1 Debug Cat2: {3.1415} "); + AssertDebugLastMessage($"Logger1 Debug Cat2: {3.1415} "); } [Fact] @@ -129,7 +137,7 @@ public void TraceWriteNonDefaultLevelTest() Trace.Listeners.Add(new NLogTraceListener { Name = "Logger1", DefaultLogLevel = LogLevel.Trace }); Trace.Write("Hello"); - AssertDebugLastMessage("debug", "Logger1 Trace Hello "); + AssertDebugLastMessage("Logger1 Trace Hello "); } [Fact] @@ -162,10 +170,10 @@ public void TraceFailTest() Trace.Listeners.Add(new NLogTraceListener { Name = "Logger1" }); Trace.Fail("Message"); - AssertDebugLastMessage("debug", "Logger1 Error Message Error"); + AssertDebugLastMessage("Logger1 Error Message Error"); Trace.Fail("Message", "Detailed Message"); - AssertDebugLastMessage("debug", "Logger1 Error Message Detailed Message Error"); + AssertDebugLastMessage("Logger1 Error Message Detailed Message Error"); } [Fact] @@ -183,7 +191,7 @@ public void AutoLoggerNameTest() Trace.Listeners.Add(new NLogTraceListener { Name = "Logger1", AutoLoggerName = true }); Trace.Write("Hello"); - AssertDebugLastMessage("debug", GetType().FullName + " Debug Hello "); + AssertDebugLastMessage(GetType().FullName + " Debug Hello "); } [Fact] @@ -201,10 +209,10 @@ public void TraceDataTests() ts.Listeners.Add(new NLogTraceListener { Name = "Logger1", DefaultLogLevel = LogLevel.Trace }); ts.TraceData(TraceEventType.Critical, 123, 42); - AssertDebugLastMessage("debug", "MySource1 Fatal 42 123 Critical"); + AssertDebugLastMessage("MySource1 Fatal 42 123 Critical"); ts.TraceData(TraceEventType.Critical, 145, 42, 3.14, "foo"); - AssertDebugLastMessage("debug", $"MySource1 Fatal 42, {3.14.ToString(CultureInfo.CurrentCulture)}, foo 145 Critical"); + AssertDebugLastMessage($"MySource1 Fatal 42, {3.14.ToString(CultureInfo.CurrentCulture)}, foo 145 Critical"); } #if MONO @@ -226,10 +234,10 @@ public void LogInformationTest() ts.Listeners.Add(new NLogTraceListener { Name = "Logger1", DefaultLogLevel = LogLevel.Trace }); ts.TraceInformation("Quick brown fox"); - AssertDebugLastMessage("debug", "MySource1 Info Quick brown fox Information"); + AssertDebugLastMessage("MySource1 Info Quick brown fox Information"); ts.TraceInformation("Mary had {0} lamb", "a little"); - AssertDebugLastMessage("debug", "MySource1 Info Mary had a little lamb Information"); + AssertDebugLastMessage("MySource1 Info Mary had a little lamb Information"); } [Fact] @@ -247,28 +255,28 @@ public void TraceEventTests() ts.Listeners.Add(new NLogTraceListener { Name = "Logger1", DefaultLogLevel = LogLevel.Trace }); ts.TraceEvent(TraceEventType.Information, 123, "Quick brown {0} jumps over the lazy {1}.", "fox", "dog"); - AssertDebugLastMessage("debug", "MySource1 Info Quick brown fox jumps over the lazy dog. 123 Information"); + AssertDebugLastMessage("MySource1 Info Quick brown fox jumps over the lazy dog. 123 Information"); ts.TraceEvent(TraceEventType.Information, 123); - AssertDebugLastMessage("debug", "MySource1 Info 123 Information"); + AssertDebugLastMessage("MySource1 Info 123 Information"); ts.TraceEvent(TraceEventType.Verbose, 145, "Bar"); - AssertDebugLastMessage("debug", "MySource1 Trace Bar 145 "); + AssertDebugLastMessage("MySource1 Trace Bar 145 "); ts.TraceEvent(TraceEventType.Error, 145, "Foo"); - AssertDebugLastMessage("debug", "MySource1 Error Foo 145 Error"); + AssertDebugLastMessage("MySource1 Error Foo 145 Error"); ts.TraceEvent(TraceEventType.Suspend, 145, "Bar"); - AssertDebugLastMessage("debug", "MySource1 Debug Bar 145 Suspend"); + AssertDebugLastMessage("MySource1 Debug Bar 145 Suspend"); ts.TraceEvent(TraceEventType.Resume, 145, "Foo"); - AssertDebugLastMessage("debug", "MySource1 Debug Foo 145 Resume"); + AssertDebugLastMessage("MySource1 Debug Foo 145 Resume"); ts.TraceEvent(TraceEventType.Warning, 145, "Bar"); - AssertDebugLastMessage("debug", "MySource1 Warn Bar 145 Warning"); + AssertDebugLastMessage("MySource1 Warn Bar 145 Warning"); ts.TraceEvent(TraceEventType.Critical, 145, "Foo"); - AssertDebugLastMessage("debug", "MySource1 Fatal Foo 145 Critical"); + AssertDebugLastMessage("MySource1 Fatal Foo 145 Critical"); } #if MONO @@ -291,10 +299,10 @@ public void ForceLogLevelTest() // force all logs to be Warn, DefaultLogLevel has no effect on TraceSource ts.TraceInformation("Quick brown fox"); - AssertDebugLastMessage("debug", "MySource1 Warn Quick brown fox Information"); + AssertDebugLastMessage("MySource1 Warn Quick brown fox Information"); ts.TraceInformation("Mary had {0} lamb", "a little"); - AssertDebugLastMessage("debug", "MySource1 Warn Mary had a little lamb Information"); + AssertDebugLastMessage("MySource1 Warn Mary had a little lamb Information"); } [Fact] @@ -313,12 +321,11 @@ public void FilterTraceTest() // force all logs to be Warn, DefaultLogLevel has no effect on TraceSource ts.TraceEvent(TraceEventType.Error, 0, "Quick brown fox"); - AssertDebugLastMessage("debug", "MySource1 Warn Quick brown fox Error"); + AssertDebugLastMessage("MySource1 Warn Quick brown fox Error"); ts.TraceInformation("Mary had {0} lamb", "a little"); - AssertDebugLastMessage("debug", "MySource1 Warn Quick brown fox Error"); + AssertDebugLastMessage("MySource1 Warn Quick brown fox Error"); } -#endif [Fact] public void GlobalAllFilterTraceTest() @@ -337,13 +344,13 @@ public void GlobalAllFilterTraceTest() "); Trace.WriteLine("Quick brown fox"); - AssertDebugLastMessage("debug", "Logger1 Debug Quick brown fox "); + AssertDebugLastMessage("Logger1 Debug Quick brown fox "); Trace.WriteLine(new ArgumentException("Mary had a little lamb")); - AssertDebugLastMessage("debug", "Logger1 Debug System.ArgumentException: Mary had a little lamb "); + AssertDebugLastMessage("Logger1 Debug System.ArgumentException: Mary had a little lamb "); Trace.Write("Quick brown fox"); - AssertDebugLastMessage("debug", "Logger1 Debug Quick brown fox "); + AssertDebugLastMessage("Logger1 Debug Quick brown fox "); Trace.Write(new ArgumentException("Mary had a little lamb")); - AssertDebugLastMessage("debug", "Logger1 Debug System.ArgumentException: Mary had a little lamb "); + AssertDebugLastMessage("Logger1 Debug System.ArgumentException: Mary had a little lamb "); Trace.Flush(); } finally @@ -391,7 +398,7 @@ public void TraceTargetWriteLineTest() { var logger = new LogFactory().Setup().LoadConfiguration(builder => { - builder.ForLogger().WriteToTrace(layout: "${logger} ${level} ${message} ${event-properties:EventID} ${event-properties:EventType}", rawWrite: true); + builder.ForLogger().WriteTo(new TraceTarget() { Layout = "${logger} ${level} ${message} ${event-properties:EventID} ${event-properties:EventType}", RawWrite = true }); }).GetLogger("MySource1"); var sw = new System.IO.StringWriter(); @@ -465,6 +472,19 @@ public void TraceTargetEnableTraceFailTest(bool enableTraceFail) } } + private static void AssertDebugLastMessage(string msg) + { + var debugTarget = LogManager.Configuration.FindTargetByName("debug"); + Assert.Equal(msg, debugTarget.LastMessage); + } + + private static void AssertDebugLastMessageContains(string targetName, string msg) + { + var debugTarget = LogManager.Configuration.FindTargetByName("debug"); + Assert.True(debugTarget.LastMessage.Contains(msg), + $"Expected to find '{msg}' in last message value on '{targetName}', but found '{debugTarget.LastMessage}'"); + } + private static TraceSource CreateTraceSource() { var ts = new TraceSource("MySource1", SourceLevels.All); diff --git a/tests/NLog.Targets.WebService.Tests/NLog.Targets.WebService.Tests.csproj b/tests/NLog.Targets.WebService.Tests/NLog.Targets.WebService.Tests.csproj new file mode 100644 index 0000000000..c2921825c2 --- /dev/null +++ b/tests/NLog.Targets.WebService.Tests/NLog.Targets.WebService.Tests.csproj @@ -0,0 +1,35 @@ + + + + 17.0 + net462 + net462;net8.0 + + false + + NLogTests.snk + false + true + true + + Full + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/tests/NLog.Targets.WebService.Tests/NLogTests.snk b/tests/NLog.Targets.WebService.Tests/NLogTests.snk new file mode 100644 index 0000000000..f168e4dbec Binary files /dev/null and b/tests/NLog.Targets.WebService.Tests/NLogTests.snk differ diff --git a/tests/NLog.UnitTests/Targets/WebServiceTargetTests.cs b/tests/NLog.Targets.WebService.Tests/WebServiceTargetTests.cs similarity index 98% rename from tests/NLog.UnitTests/Targets/WebServiceTargetTests.cs rename to tests/NLog.Targets.WebService.Tests/WebServiceTargetTests.cs index e270340ce4..571d98de64 100644 --- a/tests/NLog.UnitTests/Targets/WebServiceTargetTests.cs +++ b/tests/NLog.Targets.WebService.Tests/WebServiceTargetTests.cs @@ -31,7 +31,7 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -namespace NLog.UnitTests.Targets +namespace NLog.Targets.WebService { using System; using System.Collections.Concurrent; @@ -44,7 +44,7 @@ namespace NLog.UnitTests.Targets using System.Threading; using System.Threading.Tasks; using System.Xml.Serialization; -#if !NETSTANDARD +#if NETFRAMEWORK using System.Web.Http; using System.Web.Http.Dependencies; using Microsoft.Owin.Hosting; @@ -54,7 +54,7 @@ namespace NLog.UnitTests.Targets using NLog.Targets; using Xunit; - public class WebServiceTargetTests : NLogTestBase + public class WebServiceTargetTests { public WebServiceTargetTests() { @@ -166,7 +166,7 @@ private static void WebserviceTest_httppost_utf8(string bomAttr, bool includeBom var bytes = streamMock.bytes; var url = streamMock.stringed; - const string expectedUrl = "empty=&guid=336cec87129942eeabab3d8babceead7&m=Debg&date=2014-06-26+23%3a15%3a14.6348&logger=TestClient.Program&level=Debug"; + const string expectedUrl = "empty=&guid=336cec87129942eeabab3d8babceead7&m=Debg&date=2014-06-26+23%3A15%3A14.6348&logger=TestClient.Program&level=Debug"; Assert.Equal(expectedUrl, url); Assert.True(bytes.Length > 3); @@ -193,10 +193,6 @@ private class StreamMock : MemoryStream public byte[] bytes; public string stringed; - /// - /// Releases the unmanaged resources used by the class and optionally releases the managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { //save stuff before dispose @@ -214,7 +210,7 @@ private static string StreamToString(Stream s) } } -#if !NETSTANDARD +#if NETFRAMEWORK private static string getNewWsAddress() { string WsAddress = "http://localhost:9000/"; @@ -712,7 +708,6 @@ public IEnumerable Get(string param1 = "", string param2 = "") public void Post([FromBody] ComplexType complexType) { //this is working. - Guard.ThrowIfNull(complexType); Context.ReceivedLogsPostParam1.Add(complexType.Param1); if (Context.CountdownEvent != null) @@ -886,7 +881,7 @@ public IEnumerable GetServices(Type serviceType) } else { - return ArrayHelper.Empty(); + return Array.Empty(); } } } @@ -899,16 +894,12 @@ public class LogDocController : ApiController [HttpPost] public void Json(LogMeController.ComplexType complexType) { - Guard.ThrowIfNull(complexType); - processRequest(complexType); } [HttpPost] public void Xml(LogMeController.ComplexType complexType) { - Guard.ThrowIfNull(complexType); - processRequest(complexType); } diff --git a/tests/NLog.UnitTests/ApiTests.cs b/tests/NLog.UnitTests/ApiTests.cs index 717fe2d86b..00ec8df654 100644 --- a/tests/NLog.UnitTests/ApiTests.cs +++ b/tests/NLog.UnitTests/ApiTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen // // All rights reserved. @@ -31,13 +31,13 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using System.Linq; - namespace NLog.UnitTests { using System; using System.Collections.Generic; + using System.Linq; using System.Reflection; + using System.Runtime.CompilerServices; using System.Text; using NLog.Config; using Xunit; @@ -47,8 +47,8 @@ namespace NLog.UnitTests /// public class ApiTests : NLogTestBase { - private Type[] allTypes; - private Assembly nlogAssembly = typeof(LogManager).Assembly; + private readonly Type[] allTypes; + private readonly Assembly nlogAssembly = typeof(LogManager).Assembly; private readonly Dictionary typeUsageCount = new Dictionary(); public ApiTests() @@ -135,18 +135,9 @@ public void PublicEnumsTest() [Fact] public void TypesInInternalNamespaceShouldBeInternalTest() { - var excludes = new HashSet - { - typeof(NLog.Internal.Xamarin.PreserveAttribute), -#pragma warning disable CS0618 // Type or member is obsolete - typeof(NLog.Internal.Fakeables.IAppDomain), // TODO NLog 5 - handle IAppDomain -#pragma warning restore CS0618 // Type or member is obsolete - }; - var notInternalTypes = allTypes .Where(t => t.Namespace != null && t.Namespace.Contains(".Internal")) .Where(t => !t.IsNested && (t.IsVisible || t.IsPublic)) - .Where(n => !excludes.Contains(n)) .Select(t => t.FullName) .ToList(); @@ -199,7 +190,7 @@ public void IStringValueRenderer_AppDomainFixedOutput_Attribute_NotRequired() { foreach (Type type in allTypes) { - if (typeof(NLog.Internal.IStringValueRenderer).IsAssignableFrom(type) && !type.IsInterface) + if (typeof(NLog.Internal.IStringValueRenderer).IsAssignableFrom(type) && !type.IsInterface && !typeof(NLog.Layouts.SimpleLayout).Equals(type)) { var appDomainFixedOutputAttribute = type.GetCustomAttribute(); Assert.True(appDomainFixedOutputAttribute is null, $"{type.ToString()} should not implement IStringValueRenderer"); @@ -207,23 +198,6 @@ public void IStringValueRenderer_AppDomainFixedOutput_Attribute_NotRequired() } } - [Fact] - public void RequiredConfigOptionMustBeClass() - { - foreach (Type type in allTypes) - { - var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - foreach (var prop in properties) - { - var requiredParameter = prop.GetCustomAttribute(); - if (requiredParameter != null) - { - Assert.True(prop.PropertyType.IsClass, type.Name); - } - } - } - } - [Fact] public void SingleDefaultConfigOption() { @@ -283,21 +257,19 @@ public void ValidateLayoutRendererTypeAlias() // Do NOT add more incorrect class-names to this exlusion-list HashSet oldFaultyClassNames = new HashSet() { - "GarbageCollectorInfoLayoutRenderer", - "ScopeContextNestedStatesLayoutRenderer", - "ScopeContextPropertyLayoutRenderer", - "ScopeContextTimingLayoutRenderer", - "ScopeContextIndentLayoutRenderer", - "TraceActivityIdLayoutRenderer", - "SpecialFolderApplicationDataLayoutRenderer", - "SpecialFolderCommonApplicationDataLayoutRenderer", - "SpecialFolderLocalApplicationDataLayoutRenderer", - "DirectorySeparatorLayoutRenderer", - "LiteralWithRawValueLayoutRenderer", - "LocalIpAddressLayoutRenderer", - "VariableLayoutRenderer", - "ObjectPathRendererWrapper", - "PaddingLayoutRendererWrapper", + nameof(NLog.LayoutRenderers.GarbageCollectorInfoLayoutRenderer), + nameof(NLog.LayoutRenderers.ScopeContextNestedStatesLayoutRenderer), + nameof(NLog.LayoutRenderers.ScopeContextPropertyLayoutRenderer), + nameof(NLog.LayoutRenderers.ScopeContextTimingLayoutRenderer), + nameof(NLog.LayoutRenderers.ScopeContextIndentLayoutRenderer), + nameof(NLog.LayoutRenderers.SpecialFolderApplicationDataLayoutRenderer), + nameof(NLog.LayoutRenderers.SpecialFolderCommonApplicationDataLayoutRenderer), + nameof(NLog.LayoutRenderers.SpecialFolderLocalApplicationDataLayoutRenderer), + nameof(NLog.LayoutRenderers.DirectorySeparatorLayoutRenderer), + nameof(NLog.LayoutRenderers.LiteralWithRawValueLayoutRenderer), + nameof(NLog.LayoutRenderers.VariableLayoutRenderer), + nameof(NLog.LayoutRenderers.Wrappers.ObjectPathRendererWrapper), + nameof(NLog.LayoutRenderers.Wrappers.PaddingLayoutRendererWrapper), }; foreach (Type type in allTypes) @@ -478,5 +450,106 @@ public void ValidateConfigurationItemFactory() Assert.Empty(missingTypes); } + + [Fact] + public void ShouldNotHaveExplicitStaticConstructors() + { + // Known allowed explicit static constructors (full type names) + var knownStaticConstructors = new HashSet + { + // Explicit static constructors + "NLog.LogFactory", + + // Likely compiler generated or false-positives. + "NLog.GlobalDiagnosticsContext", + "NLog.LogEventInfo", + "NLog.Logger", + "NLog.LogLevel", + "NLog.LogManager", + "NLog.ScopeContext", + "NLog.Time.TimeSource", + "NLog.Targets.ConsoleRowHighlightingRule", + "NLog.Targets.ConsoleTargetHelper", + "NLog.Targets.DefaultJsonSerializer", + "NLog.Targets.FileTarget", + "NLog.Targets.LineEndingMode", + "NLog.Targets.FileArchiveHandlers.DisabledFileArchiveHandler", + "NLog.MessageTemplates.MessageTemplateParameters", + "NLog.MessageTemplates.TemplateEnumerator", + "NLog.MessageTemplates.ValueFormatter", + "NLog.Layouts.Layout", + "NLog.Layouts.LayoutParser", + "NLog.Layouts.ValueTypeLayoutInfo", + "NLog.Layouts.XmlElementBase", + "NLog.LayoutRenderers.AllEventPropertiesLayoutRenderer", + "NLog.LayoutRenderers.CounterLayoutRenderer", + "NLog.LayoutRenderers.ExceptionLayoutRenderer", + "NLog.LayoutRenderers.LevelLayoutRenderer", + "NLog.Internal.AppendBuilderCreator", + "NLog.Internal.CallSiteInformation", + "NLog.Internal.DictionaryEntryEnumerable+EmptyDictionaryEnumerator", + "NLog.Internal.ExceptionMessageFormatProvider", + "NLog.Internal.FactoryHelper", + "NLog.Internal.LogMessageStringFormatter", + "NLog.Internal.LogMessageTemplateFormatter", + "NLog.Internal.PathHelpers", + "NLog.Internal.PropertiesDictionary", + "NLog.Internal.PropertyHelper", + "NLog.Internal.SingleCallContinuation", + "NLog.Internal.StackTraceUsageUtils", + "NLog.Internal.StringBuilderExt", + "NLog.Internal.TargetWithFilterChain", + "NLog.Internal.UrlHelper", + "NLog.Internal.XmlHelper", + "NLog.Internal.XmlParser", + "NLog.Config.ConfigurationItemFactory", + "NLog.Config.InstallationContext", + "NLog.Config.LoggingRuleLevelFilter", + "NLog.Config.PropertyTypeConverter", + "NLog.Config.XmlLoggingConfiguration", + "NLog.Conditions.ConditionExpression", + "NLog.Conditions.ConditionLiteralExpression", + "NLog.Conditions.ConditionRelationalExpression", + "NLog.Conditions.ConditionTokenizer", + "NLog.Filters.ConditionBasedFilter", + "NLog.Common.InternalLogger", + "NLog.Internal.ObjectReflectionCache+ObjectPropertyList", + "NLog.Internal.ObjectReflectionCache+ObjectPropertyInfos", + "NLog.Internal.ObjectReflectionCache+EmptyDictionaryEnumerator", + "NLog.Internal.PropertiesDictionary+PropertyKeyComparer", + "NLog.Internal.SingleItemOptimizedHashSet`1+ReferenceEqualityComparer", + "NLog.Internal.ArrayHelper+EmptyArray`1", + "NLog.Config.LoggerNameMatcher+NoneLoggerNameMatcher", + "NLog.Config.LoggerNameMatcher+AllLoggerNameMatcher", + "NLog.Config.LoggingConfigurationParser+ValidatedConfigurationElement" + }; + + knownStaticConstructors.Remove("NLog.Layouts.SimpleLayout"); // Prevent random initialization order of static contructors, because NLog.Layouts.Layout has static-constructor that depends on NLog.Layouts.SimpleLayout + + var typesWithStaticConstructors = allTypes + // Exclude compiler-generated types (e.g., lambdas, nested <>c classes) + .Where(type => !type.IsDefined(typeof(CompilerGeneratedAttribute), inherit: false)) + .Select(type => new + { + TypeName = type.FullName, + StaticConstructor = type.TypeInitializer + }) + + // Check for non-compiler-generated static constructors + .Where(t => t.StaticConstructor != null + && !t.StaticConstructor.IsDefined(typeof(CompilerGeneratedAttribute), inherit: false)) + .Select(t => t.TypeName) + .Distinct() + .ToList(); + + var newConstructors = typesWithStaticConstructors + .Where(type => !knownStaticConstructors.Contains(type)) + .ToList(); + + if (newConstructors.Count > 0) + { + Assert.Fail($"Found new explicit static constructors in:\n{string.Join("\n", newConstructors)}"); + } + } } } diff --git a/tests/NLog.UnitTests/AsyncHelperTests.cs b/tests/NLog.UnitTests/AsyncHelperTests.cs index 3a3617686d..2f58c5ed9c 100644 --- a/tests/NLog.UnitTests/AsyncHelperTests.cs +++ b/tests/NLog.UnitTests/AsyncHelperTests.cs @@ -133,6 +133,7 @@ public void OneTimeOnlyExceptionInHandlerTest_RethrowExceptionEnabled() } [Fact] + [Obsolete("Marked obsolete on NLog 6.0")] public void ContinuationTimeoutTest() { RetryingIntegrationTest(3, () => @@ -165,6 +166,7 @@ public void ContinuationTimeoutTest() } [Fact] + [Obsolete("Marked obsolete on NLog 6.0")] public void ContinuationTimeoutNotHitTest() { var exceptions = new List(); @@ -192,8 +194,8 @@ public void ContinuationTimeoutNotHitTest() Assert.Null(exceptions[0]); } - [Fact] + [Obsolete("Marked obsolete on NLog 6.0")] public void ContinuationErrorTimeoutNotHitTest() { var exceptions = new List(); diff --git a/tests/NLog.UnitTests/Common/InternalLoggerTests.cs b/tests/NLog.UnitTests/Common/InternalLoggerTests.cs index 1ffa6180a6..408c3ee500 100644 --- a/tests/NLog.UnitTests/Common/InternalLoggerTests.cs +++ b/tests/NLog.UnitTests/Common/InternalLoggerTests.cs @@ -803,14 +803,14 @@ public void TestReceivedLogEventTest() using (var loggerScope = new InternalLoggerScope()) { // Arrange - var receivedArgs = new List(); + var receivedArgs = new List(); var logFactory = new LogFactory(); logFactory.Setup().SetupInternalLogger(s => { - EventHandler eventHandler = (sender, e) => receivedArgs.Add(e); - s.AddLogSubscription(eventHandler); - s.RemoveLogSubscription(eventHandler); - s.AddLogSubscription(eventHandler); + InternalEventOccurredHandler eventHandler = (sender, e) => receivedArgs.Add(e); + s.AddEventSubscription(eventHandler); + s.RemoveEventSubscription(eventHandler); + s.AddEventSubscription(eventHandler); }); var exception = new Exception(); @@ -827,14 +827,13 @@ public void TestReceivedLogEventTest() } [Fact] - [Obsolete("Instead use InternalEventOccurred. Marked obsolete with NLog v5.3")] public void TestReceivedLogEventThrowingTest() { using (var loggerScope = new InternalLoggerScope()) { // Arrange - var receivedArgs = new List(); - InternalLogger.LogMessageReceived += (sender, e) => + var receivedArgs = new List(); + InternalLogger.InternalEventOccurred += (sender, e) => { receivedArgs.Add(e); throw new ApplicationException("I'm a bad programmer"); @@ -854,15 +853,14 @@ public void TestReceivedLogEventThrowingTest() } [Fact] - [Obsolete("Instead use InternalEventOccurred. Marked obsolete with NLog v5.3")] public void TestReceivedLogEventContextTest() { using (var loggerScope = new InternalLoggerScope()) { // Arrange var targetContext = new NLog.Targets.DebugTarget() { Name = "Ugly" }; - var receivedArgs = new List(); - InternalLogger.LogMessageReceived += (sender, e) => + var receivedArgs = new List(); + InternalLogger.InternalEventOccurred += (sender, e) => { receivedArgs.Add(e); }; diff --git a/tests/NLog.UnitTests/Common/InternalLoggerTests_Trace.cs b/tests/NLog.UnitTests/Common/InternalLoggerTests_Trace.cs deleted file mode 100644 index 5585e8f4da..0000000000 --- a/tests/NLog.UnitTests/Common/InternalLoggerTests_Trace.cs +++ /dev/null @@ -1,356 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -#define DEBUG - -namespace NLog.UnitTests.Common -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using NLog.Common; - using NLog.Config; - using Xunit; - - public class InternalLoggerTests_Trace : NLogTestBase - { - - [Theory] - [InlineData(null, null)] - [InlineData(false, null)] - [InlineData(null, false)] - [InlineData(false, false)] - public void ShouldNotLogInternalWhenLogToTraceIsDisabled(bool? internalLogToTrace, bool? logToTrace) - { - var mockTraceListener = SetupTestConfiguration(LogLevel.Trace, internalLogToTrace, logToTrace); - - InternalLogger.Trace("Logger1 Hello"); - - Assert.Empty(mockTraceListener.Messages); - } - - [Theory] - [InlineData(null, null)] - [InlineData(false, null)] - [InlineData(null, false)] - [InlineData(false, false)] - [InlineData(true, null)] - [InlineData(null, true)] - [InlineData(true, true)] - public void ShouldNotLogInternalWhenLogLevelIsOff(bool? internalLogToTrace, bool? logToTrace) - { - var mockTraceListener = SetupTestConfiguration(LogLevel.Off, internalLogToTrace, logToTrace); - - InternalLogger.Trace("Logger1 Hello"); - - Assert.Empty(mockTraceListener.Messages); - } - - [Theory] - [InlineData(true, null)] - [InlineData(null, true)] - [InlineData(true, true)] - public void VerifyInternalLoggerLevelFilter(bool? internalLogToTrace, bool? logToTrace) - { - foreach (LogLevel logLevelConfig in LogLevel.AllLevels) - { - var mockTraceListener = SetupTestConfiguration(logLevelConfig, internalLogToTrace, logToTrace); - - List expected = new List(); - - string input = "Logger1 Hello"; - expected.Add(input); - InternalLogger.Trace(input); - InternalLogger.Debug(input); - InternalLogger.Info(input); - InternalLogger.Warn(input); - InternalLogger.Error(input); - InternalLogger.Fatal(input); - input += "No.{0}"; - expected.Add(string.Format(input, 1)); - InternalLogger.Trace(input, 1); - InternalLogger.Debug(input, 1); - InternalLogger.Info(input, 1); - InternalLogger.Warn(input, 1); - InternalLogger.Error(input, 1); - InternalLogger.Fatal(input, 1); - input += ", We come in {1}"; - expected.Add(string.Format(input, 1, "Peace")); - InternalLogger.Trace(input, 1, "Peace"); - InternalLogger.Debug(input, 1, "Peace"); - InternalLogger.Info(input, 1, "Peace"); - InternalLogger.Warn(input, 1, "Peace"); - InternalLogger.Error(input, 1, "Peace"); - InternalLogger.Fatal(input, 1, "Peace"); - input += " and we are {2} to god"; - expected.Add(string.Format(input, 1, "Peace", true)); - InternalLogger.Trace(input, 1, "Peace", true); - InternalLogger.Debug(input, 1, "Peace", true); - InternalLogger.Info(input, 1, "Peace", true); - InternalLogger.Warn(input, 1, "Peace", true); - InternalLogger.Error(input, 1, "Peace", true); - InternalLogger.Fatal(input, 1, "Peace", true); - input += ", Please don't {3}"; - expected.Add(string.Format(input, 1, "Peace", true, null)); - InternalLogger.Trace(input, 1, "Peace", true, null); - InternalLogger.Debug(input, 1, "Peace", true, null); - InternalLogger.Info(input, 1, "Peace", true, null); - InternalLogger.Warn(input, 1, "Peace", true, null); - InternalLogger.Error(input, 1, "Peace", true, null); - InternalLogger.Fatal(input, 1, "Peace", true, null); - input += " the {4}"; - expected.Add(string.Format(input, 1, "Peace", true, null, "Messenger")); - InternalLogger.Trace(input, 1, "Peace", true, null, "Messenger"); - InternalLogger.Debug(input, 1, "Peace", true, null, "Messenger"); - InternalLogger.Info(input, 1, "Peace", true, null, "Messenger"); - InternalLogger.Warn(input, 1, "Peace", true, null, "Messenger"); - InternalLogger.Error(input, 1, "Peace", true, null, "Messenger"); - InternalLogger.Fatal(input, 1, "Peace", true, null, "Messenger"); - Assert.Equal(expected.Count * (LogLevel.Fatal.Ordinal - logLevelConfig.Ordinal + 1), mockTraceListener.Messages.Count); - for (int i = 0; i < expected.Count; ++i) - { - int msgCount = LogLevel.Fatal.Ordinal - logLevelConfig.Ordinal + 1; - for (int j = 0; j < msgCount; ++j) - { - Assert.Contains(expected[i], mockTraceListener.Messages.First()); - mockTraceListener.Messages.RemoveAt(0); - } - } - Assert.Empty(mockTraceListener.Messages); - } - } - - [Theory] - [InlineData(true, null)] - [InlineData(null, true)] - [InlineData(true, true)] - public void ShouldLogToTraceWhenInternalLogToTraceIsOnAndLogLevelIsTrace(bool? internalLogToTrace, bool? logToTrace) - { - var mockTraceListener = SetupTestConfiguration(LogLevel.Trace, internalLogToTrace, logToTrace); - - InternalLogger.Trace("Logger1 Hello"); - - Assert.Single(mockTraceListener.Messages); - Assert.Equal("NLog: Trace Logger1 Hello" + Environment.NewLine, mockTraceListener.Messages.First()); - } - - [Theory] - [InlineData(true, null)] - [InlineData(null, true)] - [InlineData(true, true)] - public void ShouldLogToTraceWhenInternalLogToTraceIsOnAndLogLevelIsDebug(bool? internalLogToTrace, bool? logToTrace) - { - var mockTraceListener = SetupTestConfiguration(LogLevel.Debug, internalLogToTrace, logToTrace); - - InternalLogger.Debug("Logger1 Hello"); - - Assert.Single(mockTraceListener.Messages); - Assert.Equal("NLog: Debug Logger1 Hello" + Environment.NewLine, mockTraceListener.Messages.First()); - } - - [Theory] - [InlineData(true, null)] - [InlineData(null, true)] - [InlineData(true, true)] - public void ShouldLogToTraceWhenInternalLogToTraceIsOnAndLogLevelIsInfo(bool? internalLogToTrace, bool? logToTrace) - { - var mockTraceListener = SetupTestConfiguration(LogLevel.Info, internalLogToTrace, logToTrace); - - InternalLogger.Info("Logger1 Hello"); - - Assert.Single(mockTraceListener.Messages); - Assert.Equal("NLog: Info Logger1 Hello" + Environment.NewLine, mockTraceListener.Messages.First()); - } - - [Theory] - [InlineData(true, null)] - [InlineData(null, true)] - [InlineData(true, true)] - public void ShouldLogToTraceWhenInternalLogToTraceIsOnAndLogLevelIsWarn(bool? internalLogToTrace, bool? logToTrace) - { - var mockTraceListener = SetupTestConfiguration(LogLevel.Warn, internalLogToTrace, logToTrace); - - InternalLogger.Warn("Logger1 Hello"); - - Assert.Single(mockTraceListener.Messages); - Assert.Equal("NLog: Warn Logger1 Hello" + Environment.NewLine, mockTraceListener.Messages.First()); - } - - [Theory] - [InlineData(true, null)] - [InlineData(null, true)] - [InlineData(true, true)] - public void ShouldLogToTraceWhenInternalLogToTraceIsOnAndLogLevelIsError(bool? internalLogToTrace, bool? logToTrace) - { - var mockTraceListener = SetupTestConfiguration(LogLevel.Error, internalLogToTrace, logToTrace); - - InternalLogger.Error("Logger1 Hello"); - - Assert.Single(mockTraceListener.Messages); - Assert.Equal("NLog: Error Logger1 Hello" + Environment.NewLine, mockTraceListener.Messages.First()); - } - - [Theory] - [InlineData(true, null)] - [InlineData(null, true)] - [InlineData(true, true)] - public void ShouldLogToTraceWhenInternalLogToTraceIsOnAndLogLevelIsFatal(bool? internalLogToTrace, bool? logToTrace) - { - var mockTraceListener = SetupTestConfiguration(LogLevel.Fatal, internalLogToTrace, logToTrace); - - InternalLogger.Fatal("Logger1 Hello"); - - Assert.Single(mockTraceListener.Messages); - Assert.Equal("NLog: Fatal Logger1 Hello" + Environment.NewLine, mockTraceListener.Messages.First()); - } - -#if !NETSTANDARD1_5 - [Fact(Skip = "This test's not working - explenation is in documentation: https://msdn.microsoft.com/pl-pl/library/system.stackoverflowexception(v=vs.110).aspx#Anchor_5. To clarify if StackOverflowException should be thrown.")] - public void ShouldThrowStackOverFlowExceptionWhenUsingNLogTraceListener() - { - SetupTestConfiguration(LogLevel.Trace, true, null); - - Assert.Throws(() => Trace.WriteLine("StackOverFlowException")); - } -#endif - - /// - /// Helper method to setup tests configuration - /// - /// The for the log event. - /// internalLogToTrace XML attribute value. If null attribute is omitted. - /// Value of property. If null property is not set. - /// instance. - private static T SetupTestConfiguration(LogLevel logLevel, bool? internalLogToTrace, bool? logToTrace) where T : TraceListener - { - var internalLogToTraceAttribute = ""; - if (internalLogToTrace.HasValue) - { - internalLogToTraceAttribute = $" internalLogToTrace='{internalLogToTrace.Value}'"; - } - - var xmlConfiguration = string.Format(XmlConfigurationFormat, logLevel, internalLogToTraceAttribute); - - LogManager.Configuration = XmlLoggingConfiguration.CreateFromXmlString(xmlConfiguration); - - InternalLogger.IncludeTimestamp = false; - - if (logToTrace.HasValue) - { -#pragma warning disable CS0618 // Type or member is obsolete - InternalLogger.LogToTrace = logToTrace.Value; -#pragma warning restore CS0618 // Type or member is obsolete - } - - T traceListener; - if (typeof(T) == typeof(MockTraceListener)) - { - traceListener = CreateMockTraceListener() as T; - } - else - { - traceListener = CreateNLogTraceListener() as T; - } - - Trace.Listeners.Clear(); - - if (traceListener is null) - { - return null; - } - - Trace.Listeners.Add(traceListener); - - return traceListener; - } - - private const string XmlConfigurationFormat = @" - - - - - - -"; - - /// - /// Creates instance. - /// - /// instance. - private static MockTraceListener CreateMockTraceListener() - { - return new MockTraceListener(); - } - - /// - /// Creates instance. - /// - /// instance. - private static TraceListener CreateNLogTraceListener() - { -#if !NETSTANDARD1_5 - return new NLogTraceListener { Name = "Logger1", ForceLogLevel = LogLevel.Trace }; -#else - return null; -#endif - } - - private sealed class MockTraceListener : TraceListener - { - - internal readonly List Messages = new List(); - - /// - /// When overridden in a derived class, writes the specified message to the listener you create in the derived class. - /// - /// A message to write. - public override void Write(string message) - { - Messages.Add(message); - } - - /// - /// When overridden in a derived class, writes a message to the listener you create in the derived class, followed by a line terminator. - /// - /// A message to write. - public override void WriteLine(string message) - { - Messages.Add(message + Environment.NewLine); - } - - } - - } - -} diff --git a/tests/NLog.UnitTests/Conditions/ConditionEvaluatorTests.cs b/tests/NLog.UnitTests/Conditions/ConditionEvaluatorTests.cs index de74ed98a9..5528e9d59d 100644 --- a/tests/NLog.UnitTests/Conditions/ConditionEvaluatorTests.cs +++ b/tests/NLog.UnitTests/Conditions/ConditionEvaluatorTests.cs @@ -35,8 +35,6 @@ namespace NLog.UnitTests.Conditions { using System; using System.Globalization; - using System.IO; - using System.Runtime.Serialization.Formatters.Binary; using NLog.Conditions; using NLog.Config; using Xunit; @@ -76,20 +74,6 @@ public void ConditionMethodsTest() AssertEvaluationResult(false, "contains('foobar','oobe')"); AssertEvaluationResult(false, "contains('','foo')"); AssertEvaluationResult(true, "contains('foo','')"); - - AssertEvaluationResult(true, "regex-matches('foo', '^foo$')"); - AssertEvaluationResult(false, "regex-matches('foo', '^bar$')"); - - //Check that calling with empty string is equivalent with not passing the parameter - AssertEvaluationResult(true, "regex-matches('foo', '^foo$', '')"); - AssertEvaluationResult(false, "regex-matches('foo', '^bar$', '')"); - - //Check that options are parsed correctly - AssertEvaluationResult(true, "regex-matches('Foo', '^foo$', 'ignorecase')"); - AssertEvaluationResult(false, "regex-matches('Foo', '^foo$')"); - AssertEvaluationResult(true, "regex-matches('foo\nbar', '^Foo$', 'ignorecase,multiline')"); - AssertEvaluationResult(false, "regex-matches('foo\nbar', '^Foo$')"); - Assert.Throws(() => AssertEvaluationResult(true, "regex-matches('foo\nbar', '^Foo$', 'ignorecase,nonexistent')")); } [Fact] @@ -335,8 +319,8 @@ public void ExceptionTest4() { var inner = new InvalidOperationException("f"); var ex1 = new ConditionEvaluationException("msg", inner); - BinaryFormatter bf = new BinaryFormatter(); - MemoryStream ms = new MemoryStream(); + var bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); + var ms = new System.IO.MemoryStream(); bf.Serialize(ms, ex1); ms.Position = 0; Exception ex2 = (Exception)bf.Deserialize(ms); @@ -375,8 +359,8 @@ public void ExceptionTest14() { var inner = new InvalidOperationException("f"); var ex1 = new ConditionParseException("msg", inner); - BinaryFormatter bf = new BinaryFormatter(); - MemoryStream ms = new MemoryStream(); + var bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); + var ms = new System.IO.MemoryStream(); bf.Serialize(ms, ex1); ms.Position = 0; Exception ex2 = (Exception)bf.Deserialize(ms); diff --git a/tests/NLog.UnitTests/Conditions/ConditionParserTests.cs b/tests/NLog.UnitTests/Conditions/ConditionParserTests.cs index 54b8993dd2..998015e9eb 100644 --- a/tests/NLog.UnitTests/Conditions/ConditionParserTests.cs +++ b/tests/NLog.UnitTests/Conditions/ConditionParserTests.cs @@ -33,6 +33,7 @@ namespace NLog.UnitTests.Conditions { + using System; using NLog.Conditions; using NLog.Config; using NLog.Internal; @@ -45,7 +46,7 @@ public class ConditionParserTests : NLogTestBase [Fact] public void ParseNullText() { - Assert.Null(ConditionParser.ParseExpression(null)); + Assert.Throws(() => ConditionParser.ParseExpression(null)); } [Fact] @@ -205,7 +206,7 @@ public void ConditionFunctionTests() public void CustomNLogFactoriesTest() { var configurationItemFactory = new ConfigurationItemFactory(); - configurationItemFactory.LayoutRendererFactory.RegisterType("foo"); + configurationItemFactory.GetLayoutRendererFactory().RegisterType("foo"); configurationItemFactory.ConditionMethodFactory.RegisterDefinition("check", typeof(MyConditionMethods).GetMethod("CheckIt")); var result = ConditionParser.ParseExpression("check('${foo}')", configurationItemFactory); @@ -216,7 +217,7 @@ public void CustomNLogFactoriesTest() public void MethodNameWithUnderscores() { var configurationItemFactory = new ConfigurationItemFactory(); - configurationItemFactory.LayoutRendererFactory.RegisterType("foo"); + configurationItemFactory.GetLayoutRendererFactory().RegisterType("foo"); configurationItemFactory.ConditionMethodFactory.RegisterDefinition("__check__", typeof(MyConditionMethods).GetMethod("CheckIt")); var result = ConditionParser.ParseExpression("__check__('${foo}')", configurationItemFactory); diff --git a/tests/NLog.UnitTests/Config/AutoReloadTests.cs b/tests/NLog.UnitTests/Config/AutoReloadTests.cs new file mode 100644 index 0000000000..31ede65d13 --- /dev/null +++ b/tests/NLog.UnitTests/Config/AutoReloadTests.cs @@ -0,0 +1,589 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + + +namespace NLog.UnitTests.Config +{ + using System; + using System.IO; + using System.Threading; + using NLog.Config; + using NLog.Targets; + using Xunit; + + public class AutoReloadTests + { + public AutoReloadTests() + { + LogManager.ThrowExceptions = true; + } + + [Fact] + public void TestNoAutoReload() + { + string config1 = @" + + + "; + string config2 = @" + + + "; + + string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + var logFactory = new LogFactory(); + + try + { + Directory.CreateDirectory(tempDir); + + string configFilePath = Path.Combine(tempDir, nameof(TestNoAutoReload) + ".nlog"); + WriteConfigFile(configFilePath, config1); + + var logger = logFactory.Setup().LoadConfigurationFromFile(configFilePath).GetCurrentClassLogger(); + + Assert.False(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + + logger.Debug("aaa"); + AssertDebugLastMessage("aaa", logFactory); + + ChangeAndReloadConfigFile(logFactory, configFilePath, config2, assertDidReload: false); + + logger.Debug("bbb"); + // Assert that config1 is still loaded. + AssertDebugLastMessage("bbb", logFactory); + } + finally + { + logFactory.Shutdown(); + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void TestAutoReloadOnFileChange() + { + string config1 = @" + + + "; + string config2 = @" + + + "; + string badConfig = @" + + "; + + string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + var logFactory = new LogFactory(); + + try + { + Directory.CreateDirectory(tempDir); + + string configFilePath = Path.Combine(tempDir, nameof(TestAutoReloadOnFileChange) + ".nlog"); + WriteConfigFile(configFilePath, config1); + + var logger = logFactory.Setup().LoadConfigurationFromFile(configFilePath).GetCurrentClassLogger(); + + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + + logger.Debug("aaa"); + AssertDebugLastMessage("aaa", logFactory); + + ChangeAndReloadConfigFile(logFactory, configFilePath, badConfig, assertDidReload: false); + + logger.Debug("bbb"); + // Assert that config1 is still loaded. + AssertDebugLastMessage("bbb", logFactory); + + ChangeAndReloadConfigFile(logFactory, configFilePath, config2); + + logger.Debug("ccc"); + // Assert that config2 is loaded. + AssertDebugLastMessage("[ccc]", logFactory); + } + finally + { + logFactory.Shutdown(); + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void TestAutoReloadOnFileMove() + { +#if !NETFRAMEWORK || MONO + if (IsLinux()) + { + Console.WriteLine("[SKIP] AutoReloadTests.TestAutoReloadOnFileMove because we are running in Travis"); + return; + } +#endif + + string config1 = @" + + + "; + string config2 = @" + + + "; + + string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + var logFactory = new LogFactory(); + + try + { + Directory.CreateDirectory(tempDir); + + string configFilePath = Path.Combine(tempDir, "reload.nlog"); + WriteConfigFile(configFilePath, config1); + string otherFilePath = Path.Combine(tempDir, "other.nlog"); + + var logger = logFactory.Setup().LoadConfigurationFromFile(configFilePath).GetCurrentClassLogger(); + + logger.Debug("aaa"); + AssertDebugLastMessage("aaa", logFactory); + + using (var reloadWaiter = new ConfigurationReloadWaiter(logFactory)) + { + File.Move(configFilePath, otherFilePath); + reloadWaiter.WaitForReload(); + } + + logger.Debug("bbb"); + // Assert that config1 is still loaded. + AssertDebugLastMessage("bbb", logFactory); + + WriteConfigFile(otherFilePath, config2); + using (var reloadWaiter = new ConfigurationReloadWaiter(logFactory)) + { + File.Move(otherFilePath, configFilePath); + + reloadWaiter.WaitForReload(); + Assert.True(reloadWaiter.DidReload); + } + + logger.Debug("ccc"); + // Assert that config2 is loaded. + AssertDebugLastMessage("[ccc]", logFactory); + } + finally + { + logFactory.Shutdown(); + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + protected static bool IsLinux() + { + var val = Environment.GetEnvironmentVariable("WINDIR"); + return string.IsNullOrEmpty(val); + } + + [Fact] + public void TestAutoReloadOnFileCopy() + { + string config1 = @" + + + "; + string config2 = @" + + + "; + + string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + var logFactory = new LogFactory(); + + try + { + Directory.CreateDirectory(tempPath); + + string configFilePath = Path.Combine(tempPath, "reload.nlog"); + WriteConfigFile(configFilePath, config1); + string otherFilePath = Path.Combine(tempPath, "other.nlog"); + + var logger = logFactory.Setup().LoadConfigurationFromFile(configFilePath).GetCurrentClassLogger(); + + logger.Debug("aaa"); + AssertDebugLastMessage("aaa", logFactory); + + using (var reloadWaiter = new ConfigurationReloadWaiter(logFactory)) + { + File.Delete(configFilePath); + reloadWaiter.WaitForReload(); + } + + logger.Debug("bbb"); + // Assert that config1 is still loaded. + AssertDebugLastMessage("bbb", logFactory); + + WriteConfigFile(otherFilePath, config2); + using (var reloadWaiter = new ConfigurationReloadWaiter(logFactory)) + { + File.Copy(otherFilePath, configFilePath); + File.Delete(otherFilePath); + + reloadWaiter.WaitForReload(); + Assert.True(reloadWaiter.DidReload); + } + + logger.Debug("ccc"); + // Assert that config2 is loaded. + AssertDebugLastMessage("[ccc]", logFactory); + } + finally + { + logFactory.Shutdown(); + + if (Directory.Exists(tempPath)) + Directory.Delete(tempPath, true); + } + } + + [Fact] + public void TestIncludedConfigNoReload() + { + string mainConfig1 = @" + + + "; + string mainConfig2 = @" + + + "; + string includedConfig1 = @" + + "; + string includedConfig2 = @" + + "; + + string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + var logFactory = new LogFactory(); + + try + { + Directory.CreateDirectory(tempDir); + + string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); + WriteConfigFile(mainConfigFilePath, mainConfig1); + + string includedConfigFilePath = Path.Combine(tempDir, "included.nlog"); + WriteConfigFile(includedConfigFilePath, includedConfig1); + + var logger = logFactory.Setup().LoadConfigurationFromFile(mainConfigFilePath).GetCurrentClassLogger(); + + logger.Debug("aaa"); + AssertDebugLastMessage("aaa", logFactory); + + ChangeAndReloadConfigFile(logFactory, mainConfigFilePath, mainConfig2, assertDidReload: false); + + logger.Debug("bbb"); + // Assert that mainConfig1 is still loaded. + AssertDebugLastMessage("bbb", logFactory); + + WriteConfigFile(mainConfigFilePath, mainConfig1); + ChangeAndReloadConfigFile(logFactory, includedConfigFilePath, includedConfig2, assertDidReload: false); + + logger.Debug("ccc"); + // Assert that includedConfig1 is still loaded. + AssertDebugLastMessage("ccc", logFactory); + } + finally + { + logFactory.Shutdown(); + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void TestIncludedConfigReload() + { + string mainConfig1 = @" + + + "; + string mainConfig2 = @" + + + "; + string includedConfig1 = @" + + "; + string includedConfig2 = @" + + "; + + string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + var logFactory = new LogFactory(); + + try + { + Directory.CreateDirectory(tempDir); + + string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); + WriteConfigFile(mainConfigFilePath, mainConfig1); + + string includedConfigFilePath = Path.Combine(tempDir, "included.nlog"); + WriteConfigFile(includedConfigFilePath, includedConfig1); + + var logger = logFactory.Setup().LoadConfigurationFromFile(mainConfigFilePath).GetCurrentClassLogger(); + + logger.Debug("aaa"); + AssertDebugLastMessage("aaa", logFactory); + + ChangeAndReloadConfigFile(logFactory, mainConfigFilePath, mainConfig2, assertDidReload: false); + + logger.Debug("bbb"); + // Assert that mainConfig1 is still loaded. + AssertDebugLastMessage("bbb", logFactory); + + WriteConfigFile(mainConfigFilePath, mainConfig1); + ChangeAndReloadConfigFile(logFactory, includedConfigFilePath, includedConfig2); + + logger.Debug("ccc"); + // Assert that includedConfig2 is loaded. + AssertDebugLastMessage("[ccc]", logFactory); + } + finally + { + logFactory.Shutdown(); + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void TestMainConfigReload() + { + string mainConfig1 = @" + + + "; + string mainConfig2 = @" + + + "; + string included1Config = @" + + "; + string included2Config1 = @" + + "; + string included2Config2 = @" + + "; + + string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + var logFactory = new LogFactory(); + + try + { + Directory.CreateDirectory(tempDir); + + string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); + WriteConfigFile(mainConfigFilePath, mainConfig1); + + string included1ConfigFilePath = Path.Combine(tempDir, "included.nlog"); + WriteConfigFile(included1ConfigFilePath, included1Config); + + string included2ConfigFilePath = Path.Combine(tempDir, "included2.nlog"); + WriteConfigFile(included2ConfigFilePath, included2Config1); + + var logger = logFactory.Setup().LoadConfigurationFromFile(mainConfigFilePath).GetCurrentClassLogger(); + + logger.Debug("aaa"); + AssertDebugLastMessage("aaa", logFactory); + + ChangeAndReloadConfigFile(logFactory, mainConfigFilePath, mainConfig2); + + logger.Debug("bbb"); + // Assert that mainConfig2 is loaded (which refers to included2.nlog). + AssertDebugLastMessage("[bbb]", logFactory); + + ChangeAndReloadConfigFile(logFactory, included2ConfigFilePath, included2Config2); + + logger.Debug("ccc"); + // Assert that included2Config2 is loaded. + AssertDebugLastMessage("(ccc)", logFactory); + } + finally + { + logFactory.Shutdown(); + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void TestMainConfigReloadIncludedConfigNoReload() + { + string mainConfig1 = @" + + + "; + string mainConfig2 = @" + + + "; + string included1Config = @" + + "; + string included2Config1 = @" + + "; + string included2Config2 = @" + + "; + + string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + var logFactory = new LogFactory(); + + try + { + Directory.CreateDirectory(tempDir); + + string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); + WriteConfigFile(mainConfigFilePath, mainConfig1); + + string included1ConfigFilePath = Path.Combine(tempDir, "included.nlog"); + WriteConfigFile(included1ConfigFilePath, included1Config); + + string included2ConfigFilePath = Path.Combine(tempDir, "included2.nlog"); + WriteConfigFile(included2ConfigFilePath, included2Config1); + + var logger = logFactory.Setup().LoadConfigurationFromFile(mainConfigFilePath).GetCurrentClassLogger(); + + logger.Debug("aaa"); + AssertDebugLastMessage("aaa", logFactory); + + ChangeAndReloadConfigFile(logFactory, mainConfigFilePath, mainConfig2); + + logger.Debug("bbb"); + // Assert that mainConfig2 is loaded (which refers to included2.nlog). + AssertDebugLastMessage("[bbb]", logFactory); + + ChangeAndReloadConfigFile(logFactory, included2ConfigFilePath, included2Config2, assertDidReload: false); + + logger.Debug("ccc"); + // Assert that included2Config1 is still loaded. + AssertDebugLastMessage("[ccc]", logFactory); + } + finally + { + logFactory.Shutdown(); + + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, true); + } + } + + private static void WriteConfigFile(string configFilePath, string config) + { + using (StreamWriter writer = File.CreateText(configFilePath)) + writer.Write(config); + } + + private static void ChangeAndReloadConfigFile(LogFactory logFactory, string configFilePath, string config, bool assertDidReload = true) + { + using (var reloadWaiter = new ConfigurationReloadWaiter(logFactory)) + { + WriteConfigFile(configFilePath, config); + reloadWaiter.WaitForReload(); + + if (assertDidReload) + Assert.True(reloadWaiter.DidReload, $"Config '{configFilePath}' did not reload."); + } + } + + protected static void AssertDebugLastMessage(string msg, LogFactory logFactory) + { + var debugTarget = logFactory.Configuration.FindTargetByName("debug"); + Assert.Equal(msg, debugTarget.LastMessage); + } + + private sealed class ConfigurationReloadWaiter : IDisposable + { + private readonly ManualResetEvent _counterEvent = new ManualResetEvent(false); + private readonly LogFactory _logFactory; + + public ConfigurationReloadWaiter(LogFactory logFactory) + { + _logFactory = logFactory; + _logFactory.ConfigurationChanged += SignalCounterEvent(_counterEvent); + } + + public bool DidReload => _counterEvent.WaitOne(0); + + public void Dispose() + { + _logFactory.ConfigurationChanged -= SignalCounterEvent(_counterEvent); + } + + public void WaitForReload() + { + _counterEvent.WaitOne(3000); // Handle Timer-delay of 1 sec + } + + private static EventHandler SignalCounterEvent(ManualResetEvent counterEvent) + { + return (sender, e) => + { + counterEvent.Set(); + }; + } + } + } +} diff --git a/tests/NLog.UnitTests/Config/ConfigApiTests.cs b/tests/NLog.UnitTests/Config/ConfigApiTests.cs index 21fad9420c..8e5aee7360 100644 --- a/tests/NLog.UnitTests/Config/ConfigApiTests.cs +++ b/tests/NLog.UnitTests/Config/ConfigApiTests.cs @@ -432,21 +432,21 @@ public void LoggerNameMatcher_Contains() public void LoggerNameMatcher_MultiplePattern_StarInternal() { var matcher = LoggerNameMatcher.Create("a*bc"); - Assert.Equal("logNamePattern: (^a.*bc$:MultiplePattern)", matcher.ToString()); + Assert.Equal("logNamePattern: (a*bc:MultiplePattern)", matcher.ToString()); } [Fact] public void LoggerNameMatcher_MultiplePattern_QuestionMark() { var matcher = LoggerNameMatcher.Create("a?bc"); - Assert.Equal("logNamePattern: (^a.bc$:MultiplePattern)", matcher.ToString()); + Assert.Equal("logNamePattern: (a?bc:MultiplePattern)", matcher.ToString()); } [Fact] public void LoggerNameMatcher_MultiplePattern_EscapedChars() { var matcher = LoggerNameMatcher.Create("a?b.c.foo.bar"); - Assert.Equal("logNamePattern: (^a.b\\.c\\.foo\\.bar$:MultiplePattern)", matcher.ToString()); + Assert.Equal("logNamePattern: (a?b.c.foo.bar:MultiplePattern)", matcher.ToString()); } [Theory] @@ -527,6 +527,13 @@ public void LoggerNameMatcher_Matches_MultiplePattern(string name, bool result) [InlineData("MultiplePattern", "server[*].connection[??].*", "server[].connection[2].reader", false)] [InlineData("MultiplePattern", "server[*].connection[??].*", "server[].connection[25].reader", true)] [InlineData("MultiplePattern", "server[*].connection[??].*", "server[].connection[254].reader", false)] + [InlineData("MultiplePattern", "*???*", "abcd", true)] + [InlineData("MultiplePattern", "*???*", "abc", true)] + [InlineData("MultiplePattern", "*???*", "ab", false)] + [InlineData("MultiplePattern", "?*???*", "abcd", true)] + [InlineData("MultiplePattern", "?*???*", "abc", false)] + [InlineData("MultiplePattern", "*???*?", "abcd", true)] + [InlineData("MultiplePattern", "*???*?", "abc", false)] public void LoggerNameMatcher_Matches(string matcherType, string pattern, string name, bool result) { var matcher = LoggerNameMatcher.Create(pattern); diff --git a/tests/NLog.UnitTests/Config/ConfigurationItemFactoryTests.cs b/tests/NLog.UnitTests/Config/ConfigurationItemFactoryTests.cs index 1e8a335287..d22bc4e455 100644 --- a/tests/NLog.UnitTests/Config/ConfigurationItemFactoryTests.cs +++ b/tests/NLog.UnitTests/Config/ConfigurationItemFactoryTests.cs @@ -34,7 +34,6 @@ namespace NLog.UnitTests.Config { using System; - using System.Collections.Generic; using NLog.Config; using NLog.Targets; using Xunit; @@ -45,7 +44,7 @@ public class ConfigurationItemFactoryTests : NLogTestBase public void ConfigurationItemFactoryTargetTest() { var itemFactory = new ConfigurationItemFactory(); - itemFactory.TargetFactory.RegisterType(nameof(MemoryTarget)); + itemFactory.GetTargetFactory().RegisterType(nameof(MemoryTarget)); itemFactory.TargetFactory.TryCreateInstance(nameof(MemoryTarget), out var result); Assert.IsType(result); } @@ -71,27 +70,5 @@ public void ConfigurationItemFactorySimpleTest() itemFactory.TargetFactory.TryCreateInstance("Debug", out var result); Assert.NotNull(result); } - - [Fact] - [Obsolete("Instead use LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] - public void ConfigurationItemFactoryDefaultTest() - { - var itemFactory = new ConfigurationItemFactory(); - Assert.IsType(itemFactory.CreateInstance(typeof(DebugTarget))); - } - - [Fact] - [Obsolete("Instead use LogManager.Setup().SetupExtensions(). Marked obsolete with NLog v5.2")] - public void ConfigurationItemFactoryUsesSuppliedDelegateToResolveObject() - { - var itemFactory = new ConfigurationItemFactory(); - itemFactory.RegisterType(typeof(DebugTarget), string.Empty); - List resolvedTypes = new List(); - itemFactory.CreateInstance = t => { resolvedTypes.Add(t); return Activator.CreateInstance(t); }; - itemFactory.TargetFactory.TryCreateInstance("Debug", out var target); - Assert.NotNull(target); - Assert.Single(resolvedTypes); - Assert.Equal(typeof(DebugTarget), resolvedTypes[0]); - } } } diff --git a/tests/NLog.UnitTests/Config/CultureInfoTests.cs b/tests/NLog.UnitTests/Config/CultureInfoTests.cs index 82f354f4ac..f598821c18 100644 --- a/tests/NLog.UnitTests/Config/CultureInfoTests.cs +++ b/tests/NLog.UnitTests/Config/CultureInfoTests.cs @@ -116,9 +116,13 @@ public void EventPropRendererCultureTest() var renderer = new EventPropertiesLayoutRenderer(); renderer.Item = "ADouble"; + renderer.Culture = null; string output = renderer.Render(logEventInfo); - Assert.Equal(expected, output); + + renderer.Culture = System.Globalization.CultureInfo.InvariantCulture; // Wins over LogEventInfo.FormatProvider + output = renderer.Render(logEventInfo); + Assert.Equal("1.23", output); } [Fact] @@ -139,6 +143,7 @@ public void ProcessInfoLayoutRendererCultureTest() var renderer = new ProcessInfoLayoutRenderer(); renderer.Property = ProcessInfoProperty.StartTime; renderer.Format = "d"; + renderer.Culture = null; output = renderer.Render(logEventInfo); Assert.Contains(expected, output); @@ -164,9 +169,13 @@ public void AllEventPropRendererCultureTest() logEventInfo.Properties["ADouble"] = 1.23; var renderer = new AllEventPropertiesLayoutRenderer(); + renderer.Culture = null; string output = renderer.Render(logEventInfo); - Assert.Equal(expected, output); + + renderer.Culture = System.Globalization.CultureInfo.InvariantCulture; // Wins over LogEventInfo.FormatProvider + output = renderer.Render(logEventInfo); + Assert.Equal("ADouble=1.23", output); } private static LogEventInfo CreateLogEventInfo(string cultureName) @@ -202,7 +211,7 @@ public void ExceptionTest() Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US", false); logger.Error(ex, ""); -#if !NETSTANDARD +#if NETFRAMEWORK Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE", false); Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE", false); #endif diff --git a/tests/NLog.UnitTests/Config/ExtensionTests.cs b/tests/NLog.UnitTests/Config/ExtensionTests.cs index bd3e84f0e9..f14e78a48c 100644 --- a/tests/NLog.UnitTests/Config/ExtensionTests.cs +++ b/tests/NLog.UnitTests/Config/ExtensionTests.cs @@ -37,7 +37,6 @@ namespace NLog.UnitTests.Config using System.IO; using System.Linq; using System.Reflection; - using System.Text.RegularExpressions; using MyExtensionNamespace; using NLog.Common; using NLog.Config; @@ -53,17 +52,19 @@ public class ExtensionTests : NLogTestBase private static string GetExtensionAssemblyFullPath() { -#if NETSTANDARD +#if NETFRAMEWORK + return extensionAssemblyFullPath1; +#else Assert.NotNull(typeof(FooLayout)); return typeof(FooLayout).GetTypeInfo().Assembly.Location; -#else - return extensionAssemblyFullPath1; #endif } [Fact] public void ExtensionTest1() { + ConfigurationItemFactory.Default = null; + Assert.NotNull(typeof(FooLayout)); var configuration = new LogFactory().Setup().LoadConfigurationFromXml(@" @@ -109,6 +110,8 @@ public void ExtensionTest1() [Fact] public void ExtensionTest2() { + ConfigurationItemFactory.Default = null; + var configuration = new LogFactory().Setup().LoadConfigurationFromXml(@" @@ -156,6 +159,8 @@ public void ExtensionTest2() [Fact] public void ExtensionWithPrefixLoadTwiceTest() { + ConfigurationItemFactory.Default = null; + var configuration = new LogFactory().Setup().SetupExtensions(ext => ext.RegisterAssembly(extensionAssemblyName1)) .LoadConfigurationFromXml(@" @@ -204,6 +209,8 @@ public void ExtensionWithPrefixLoadTwiceTest() [Fact] public void ExtensionWithPrefixTest() { + ConfigurationItemFactory.Default = null; + var configuration = new LogFactory().Setup().LoadConfigurationFromXml(@" @@ -247,6 +254,8 @@ public void ExtensionWithPrefixTest() [Fact] public void ExtensionTest4() { + ConfigurationItemFactory.Default = null; + Assert.NotNull(typeof(FooLayout)); var configuration = new LogFactory().Setup().LoadConfigurationFromXml(@" @@ -305,6 +314,8 @@ public void RegisterNamedTypeLessTest() [Fact] public void ExtensionTest_extensions_not_top_and_used() { + ConfigurationItemFactory.Default = null; + Assert.NotNull(typeof(FooLayout)); var configuration = new LogFactory().Setup().LoadConfigurationFromXml(@" @@ -352,6 +363,8 @@ public void ExtensionTest_extensions_not_top_and_used() [Fact] public void ExtensionShouldThrowNLogConfiguratonExceptionWhenRegisteringInvalidType() { + ConfigurationItemFactory.Default = null; + var configXml = @" @@ -376,6 +389,8 @@ public void ExtensionShouldThrowNLogConfiguratonExceptionWhenRegisteringInvalidA [Fact] public void ExtensionShouldThrowNLogConfiguratonExceptionWhenRegisteringInvalidAssemblyFile() { + ConfigurationItemFactory.Default = null; + var configXml = @" @@ -388,6 +403,8 @@ public void ExtensionShouldThrowNLogConfiguratonExceptionWhenRegisteringInvalidA [Fact] public void ExtensionShouldNotThrowWhenRegisteringInvalidTypeIfThrowConfigExceptionsFalse() { + ConfigurationItemFactory.Default = null; + var configXml = @" @@ -402,6 +419,8 @@ public void ExtensionShouldNotThrowWhenRegisteringInvalidTypeIfThrowConfigExcept [Fact] public void ExtensionShouldNotThrowWhenRegisteringInvalidAssemblyIfThrowConfigExceptionsFalse() { + ConfigurationItemFactory.Default = null; + var configXml = @" @@ -415,6 +434,8 @@ public void ExtensionShouldNotThrowWhenRegisteringInvalidAssemblyIfThrowConfigEx [Fact] public void ExtensionShouldNotThrowWhenRegisteringInvalidAssemblyFileIfThrowConfigExceptionsFalse() { + ConfigurationItemFactory.Default = null; + var configXml = @" @@ -428,6 +449,8 @@ public void ExtensionShouldNotThrowWhenRegisteringInvalidAssemblyFileIfThrowConf [Fact] public void CustomXmlNamespaceTest() { + ConfigurationItemFactory.Default = null; + var configuration = XmlLoggingConfiguration.CreateFromXmlString(@" @@ -443,6 +466,8 @@ public void CustomXmlNamespaceTest() [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] public void Extension_should_be_auto_loaded_when_following_NLog_dll_format() { + ConfigurationItemFactory.Default = null; + var fileLocations = AssemblyExtensionLoader.GetAutoLoadingFileLocations().ToArray(); Assert.NotEmpty(fileLocations); Assert.NotNull(fileLocations[0].Key); @@ -467,6 +492,8 @@ public void Extension_should_be_auto_loaded_when_following_NLog_dll_format() [Fact] public void ExtensionTypeWithAssemblyNameCanLoad() { + ConfigurationItemFactory.Default = null; + var logFactory = new LogFactory().Setup().LoadConfigurationFromXml(@" @@ -481,94 +508,11 @@ public void ExtensionTypeWithAssemblyNameCanLoad() Assert.Equal("NLogAutloadExtension.AutoLoadTarget", autoLoadedTarget.GetType().ToString()); } - [Theory] - [InlineData(true)] - [InlineData(false)] - [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] - public void Extension_loading_could_be_canceled(bool cancel) - { - ConfigurationItemFactory.Default = null; - - EventHandler onAssemblyLoading = (sender, e) => - { - if (e.Assembly.FullName.Contains("NLogAutoLoadExtension")) - { - e.Cancel = cancel; - } - }; - - try - { - ConfigurationItemFactory.AssemblyLoading += onAssemblyLoading; - - using (new NoThrowNLogExceptions()) - { - var logFactory = new LogFactory().Setup().LoadConfigurationFromXml(@" - - - - - - - - -").LogFactory; - - var autoLoadedTarget = logFactory.Configuration.FindTargetByName("t"); - - if (cancel) - { - Assert.Null(autoLoadedTarget); - } - else - { - Assert.Equal("NLogAutloadExtension.AutoLoadTarget", autoLoadedTarget.GetType().ToString()); - } - } - } - finally - { - //cleanup - ConfigurationItemFactory.AssemblyLoading -= onAssemblyLoading; - } - } - - [Fact] - public void Extensions_NLogPackageLoader_should_beCalled() - { - try - { - var writer = new StringWriter(); - InternalLogger.LogWriter = writer; - InternalLogger.LogLevel = LogLevel.Debug; - - var logFactory = new LogFactory().Setup().LoadConfigurationFromXml(@" - - - - - -"); - - - var logs = writer.ToString(); - Assert.Contains("Preload successfully invoked for 'LoaderTestInternal.NLogPackageLoader'", logs); - Assert.Contains("Preload successfully invoked for 'LoaderTestPublic.NLogPackageLoader'", logs); - Assert.Contains("Preload successfully invoked for 'LoaderTestPrivateNestedStatic.SomeType+NLogPackageLoader'", logs); - Assert.Contains("Preload successfully invoked for 'LoaderTestPrivateNested.SomeType+NLogPackageLoader'", logs); - - //4 times successful - Assert.Equal(4, Regex.Matches(logs, Regex.Escape("Preload successfully invoked for '")).Count); - } - finally - { - InternalLogger.Reset(); - } - } - [Fact] public void ImplicitConversionOperatorTest() { + ConfigurationItemFactory.Default = null; + var config = XmlLoggingConfiguration.CreateFromXmlString(@" @@ -593,6 +537,8 @@ public void LoadExtensionFromAppDomain() { try { + ConfigurationItemFactory.Default = null; + LoadManuallyLoadedExtensionDll(); InternalLogger.LogLevel = LogLevel.Trace; @@ -611,7 +557,7 @@ public void LoadExtensionFromAppDomain() "); // We get Exception for normal Assembly-Load only in net452. -#if !NETSTANDARD && !MONO +#if NETFRAMEWORK && !MONO var logs = writer.ToString(); Assert.Contains("Try find 'Manually-Loaded-Extension' in current domain", logs); #endif @@ -631,7 +577,7 @@ public void LoadExtensionFromAppDomain() public void FullyQualifiedExtensionTest() { // Arrange - + ConfigurationItemFactory.Default = null; LoadManuallyLoadedExtensionDll(); // Act @@ -658,19 +604,17 @@ public void NormalizeNameTest(string input, string expected) { // Arrange var assembly = LoadManuallyLoadedExtensionDll(); - var configFactory = new ConfigurationItemFactory(assembly); + var configFactory = new ConfigurationItemFactory(); + configFactory.AssemblyLoader.LoadAssembly(configFactory, assembly, string.Empty); // Act - var foundDefinition = configFactory.GetTargetFactory().TryGetDefinition(input, out var outputDefinition); var foundInstance = configFactory.TargetFactory.TryCreateInstance(input, out var outputInstance); - var instance = (foundDefinition || foundInstance || expected != null) ? configFactory.GetTargetFactory().CreateInstance(input) : null; + var instance = (foundInstance || expected != null) ? configFactory.GetTargetFactory().CreateInstance(input) : null; // Assert Assert.Equal(expected != null, foundInstance); - Assert.Equal(expected != null, foundDefinition); Assert.Equal(expected, instance?.GetType().ToString()); Assert.Equal(expected, outputInstance?.GetType().ToString()); - Assert.Equal(expected, outputDefinition?.ToString()); } [Obsolete("Instead use RegisterType, as dynamic Assembly loading will be moved out. Marked obsolete with NLog v5.2")] @@ -681,7 +625,7 @@ private static Assembly LoadManuallyLoadedExtensionDll() var configurationDirectory = nlogDirectory.Parent; var testsDirectory = configurationDirectory.Parent.Parent.Parent; var manuallyLoadedAssemblyPath = Path.Combine(testsDirectory.FullName, "ManuallyLoadedExtension", "bin", configurationDirectory.Name, -#if NETSTANDARD +#if !NETFRAMEWORK "netstandard2.0", #elif NET35 || NET40 || NET45 "net462", diff --git a/tests/NLog.UnitTests/Config/IncludeTests.cs b/tests/NLog.UnitTests/Config/IncludeTests.cs index 007367fc46..d306672d2a 100644 --- a/tests/NLog.UnitTests/Config/IncludeTests.cs +++ b/tests/NLog.UnitTests/Config/IncludeTests.cs @@ -69,6 +69,7 @@ private static void IncludeTest_inner(string includeAttrValue, string tempDir) Directory.CreateDirectory(tempDir); CreateConfigFile(tempDir, "included.nlog", @" + "); @@ -86,6 +87,8 @@ private static void IncludeTest_inner(string includeAttrValue, string tempDir) LogManager.Configuration = new XmlLoggingConfiguration(fileToLoad); LogManager.GetLogger("A").Debug("aaa"); + AssertDebugLastMessage("debug", ""); + LogManager.GetLogger("A").Info("aaa"); AssertDebugLastMessage("debug", "aaa"); } finally diff --git a/tests/NLog.UnitTests/Config/InternalLoggingTests.cs b/tests/NLog.UnitTests/Config/InternalLoggingTests.cs index 125d2bf046..f2ef9558e9 100644 --- a/tests/NLog.UnitTests/Config/InternalLoggingTests.cs +++ b/tests/NLog.UnitTests/Config/InternalLoggingTests.cs @@ -44,19 +44,19 @@ public class InternalLoggingTests : NLogTestBase [Fact] public void InternalLoggingConfigTest1() { - InternalLoggingConfigTest(LogLevel.Trace, true, true, LogLevel.Warn, true, true, @"c:\temp\nlog\file.txt", true, true); + InternalLoggingConfigTest(LogLevel.Trace, true, true, LogLevel.Warn, true, true, @"c:\temp\nlog\file.txt", true); } [Fact] public void InternalLoggingConfigTest2() { - InternalLoggingConfigTest(LogLevel.Error, false, false, LogLevel.Info, false, false, @"c:\temp\nlog\file2.txt", false, false); + InternalLoggingConfigTest(LogLevel.Error, false, false, LogLevel.Info, false, false, @"c:\temp\nlog\file2.txt", false); } [Fact] public void InternalLoggingConfigTes3() { - InternalLoggingConfigTest(LogLevel.Info, false, false, LogLevel.Trace, false, null, @"c:\temp\nlog\file3.txt", false, true); + InternalLoggingConfigTest(LogLevel.Info, false, false, LogLevel.Trace, false, null, @"c:\temp\nlog\file3.txt", true); } [Fact] @@ -67,9 +67,6 @@ public void InternalLoggingConfigTestDefaults() InternalLogger.LogLevel = LogLevel.Error; InternalLogger.LogToConsole = true; InternalLogger.LogToConsoleError = true; -#pragma warning disable CS0618 // Type or member is obsolete - InternalLogger.LogToTrace = true; -#pragma warning restore CS0618 // Type or member is obsolete LogManager.GlobalThreshold = LogLevel.Fatal; LogManager.ThrowExceptions = true; LogManager.ThrowConfigExceptions = null; @@ -82,9 +79,6 @@ public void InternalLoggingConfigTestDefaults() Assert.Same(LogLevel.Error, InternalLogger.LogLevel); Assert.True(InternalLogger.LogToConsole); Assert.True(InternalLogger.LogToConsoleError); -#pragma warning disable CS0618 // Type or member is obsolete - Assert.True(InternalLogger.LogToTrace); -#pragma warning restore CS0618 // Type or member is obsolete Assert.Same(LogLevel.Fatal, LogManager.GlobalThreshold); Assert.True(LogManager.ThrowExceptions); Assert.Null(LogManager.ThrowConfigExceptions); @@ -139,7 +133,7 @@ public void InternalLoggingInvalidFormatString() } } - private static void InternalLoggingConfigTest(LogLevel logLevel, bool logToConsole, bool logToConsoleError, LogLevel globalThreshold, bool throwExceptions, bool? throwConfigExceptions, string file, bool logToTrace, bool autoShutdown) + private static void InternalLoggingConfigTest(LogLevel logLevel, bool logToConsole, bool logToConsoleError, LogLevel globalThreshold, bool throwExceptions, bool? throwConfigExceptions, string file, bool autoShutdown) { var logLevelString = logLevel.ToString(); var internalLogToConsoleString = logToConsole.ToString().ToLower(); @@ -147,13 +141,12 @@ private static void InternalLoggingConfigTest(LogLevel logLevel, bool logToConso var globalThresholdString = globalThreshold.ToString(); var throwExceptionsString = throwExceptions.ToString().ToLower(); var throwConfigExceptionsString = throwConfigExceptions?.ToString().ToLower() ?? string.Empty; - var logToTraceString = logToTrace.ToString().ToLower(); var autoShutdownString = autoShutdown.ToString().ToLower(); using (new InternalLoggerScope(true)) { XmlLoggingConfiguration.CreateFromXmlString($@" - + "); Assert.Same(logLevel, InternalLogger.LogLevel); @@ -161,9 +154,6 @@ private static void InternalLoggingConfigTest(LogLevel logLevel, bool logToConso Assert.Equal(file, InternalLogger.LogFile); Assert.Equal(logToConsole, InternalLogger.LogToConsole); Assert.Equal(logToConsoleError, InternalLogger.LogToConsoleError); -#pragma warning disable CS0618 // Type or member is obsolete - Assert.Equal(logToTrace, InternalLogger.LogToTrace); -#pragma warning restore CS0618 // Type or member is obsolete Assert.Same(globalThreshold, LogManager.GlobalThreshold); diff --git a/tests/NLog.UnitTests/Config/LogFactorySetupTests.cs b/tests/NLog.UnitTests/Config/LogFactorySetupTests.cs index 8d0d0d44ed..a047dcc980 100644 --- a/tests/NLog.UnitTests/Config/LogFactorySetupTests.cs +++ b/tests/NLog.UnitTests/Config/LogFactorySetupTests.cs @@ -307,6 +307,36 @@ public void SetupExtensionsRegisterTargetTypeTest() Assert.NotNull(logFactory.Configuration.FindTargetByName("t")); } + [Fact] + public void SetupExtensionsRegisterTargetTypeOverrideTest() + { + try + { + // Arrange + NLog.Config.ConfigurationItemFactory.Default = null; + var logFactory = new LogFactory(); + + // Act + logFactory.Setup().SetupExtensions(ext => ext.RegisterTarget("Debug")); + logFactory.Configuration = new XmlLoggingConfiguration(@" + + + + + + + + ", null, logFactory); + + // Assert + Assert.NotNull(logFactory.Configuration.FindTargetByName("t")); + } + finally + { + NLog.Config.ConfigurationItemFactory.Default = null; + } + } + [Fact] public void SetupExtensionsRegisterLayoutTest() { @@ -394,7 +424,7 @@ public void SetupExtensionsRegisterLayoutMethodThreadUnsafeTest() logFactory.GetLogger("Hello").Info("World"); ConfigurationItemFactory.Default.LayoutRendererFactory.TryCreateInstance("mylayout", out var layoutRenderer); - var layout = new SimpleLayout(new LayoutRenderer[] { layoutRenderer }, "mylayout", ConfigurationItemFactory.Default); + var layout = new SimpleLayout(new LayoutRenderer[] { layoutRenderer }, "mylayout"); layout.Render(LogEventInfo.CreateNullEvent()); // Assert @@ -422,7 +452,7 @@ public void SetupExtensionsRegisterLayoutMethodThreadAgnosticTest() logFactory.GetLogger("Hello").Info("World"); ConfigurationItemFactory.Default.LayoutRendererFactory.TryCreateInstance("mylayout", out var layoutRenderer); - var layout = new SimpleLayout(new LayoutRenderer[] { layoutRenderer }, "mylayout", ConfigurationItemFactory.Default); + var layout = new SimpleLayout(new LayoutRenderer[] { layoutRenderer }, "mylayout"); layout.Render(LogEventInfo.CreateNullEvent()); // Assert @@ -544,7 +574,7 @@ public void SetupInternalLoggerLogToConsoleTest() } [Fact] - public void SetupInternalLoggerLogToTraceTest() + public void SetupInternalLoggerLogToWriterTest() { try { @@ -553,12 +583,10 @@ public void SetupInternalLoggerLogToTraceTest() var logFactory = new LogFactory(); // Act - logFactory.Setup().SetupInternalLogger(b => b.SetMinimumLogLevel(LogLevel.Fatal).LogToTrace(true)); + logFactory.Setup().SetupInternalLogger(b => b.SetMinimumLogLevel(LogLevel.Fatal).LogToWriter(Console.Out)); // Assert -#pragma warning disable CS0618 // Type or member is obsolete - Assert.True(InternalLogger.LogToTrace); -#pragma warning restore CS0618 // Type or member is obsolete + Assert.Equal(Console.Out, InternalLogger.LogWriter); } finally { @@ -567,19 +595,21 @@ public void SetupInternalLoggerLogToTraceTest() } [Fact] - public void SetupInternalLoggerLogToWriterTest() + public void SetupInternalLoggerSetupFromEnvironmentVariablesTest() { try { // Arrange InternalLogger.Reset(); var logFactory = new LogFactory(); + InternalLogger.IncludeTimestamp = false; // Act - logFactory.Setup().SetupInternalLogger(b => b.SetMinimumLogLevel(LogLevel.Fatal).LogToWriter(Console.Out)); + logFactory.Setup().SetupInternalLogger(b => b.SetupFromEnvironmentVariables().SetMinimumLogLevel(LogLevel.Fatal)); // Assert - Assert.Equal(Console.Out, InternalLogger.LogWriter); + Assert.True(InternalLogger.IncludeTimestamp); + Assert.Equal(LogLevel.Fatal, InternalLogger.LogLevel); } finally { @@ -588,7 +618,7 @@ public void SetupInternalLoggerLogToWriterTest() } [Fact] - public void SetupInternalLoggerSetupFromEnvironmentVariablesTest() + public void SetupInternalLoggerResetConfigTest() { try { @@ -598,7 +628,7 @@ public void SetupInternalLoggerSetupFromEnvironmentVariablesTest() InternalLogger.IncludeTimestamp = false; // Act - logFactory.Setup().SetupInternalLogger(b => b.SetupFromEnvironmentVariables().SetMinimumLogLevel(LogLevel.Fatal)); + logFactory.Setup().SetupInternalLogger(b => b.ResetConfig().SetMinimumLogLevel(LogLevel.Fatal)); // Assert Assert.True(InternalLogger.IncludeTimestamp); @@ -676,7 +706,7 @@ public void SetupBuilderLoadConfigurationFromFileTest() { // Arrange var xmlFile = new System.IO.StringReader(""); - var appEnv = new Mocks.AppEnvironmentMock(f => true, f => System.Xml.XmlReader.Create(xmlFile)); + var appEnv = new Mocks.AppEnvironmentMock(f => true, f => xmlFile); var configLoader = new LoggingConfigurationFileLoader(appEnv); var logFactory = new LogFactory(configLoader); @@ -706,7 +736,7 @@ public void SetupBuilderLoadConfigurationFromFileMissingButRequiredTest_Integrat public void SetupBuilderLoadConfigurationFromFileMissingTest() { // Arrange - var appEnv = new Mocks.AppEnvironmentMock(f => false, f => null); + var appEnv = new Mocks.AppEnvironmentMock(f => false); var configLoader = new LoggingConfigurationFileLoader(appEnv); var logFactory = new LogFactory(configLoader); @@ -722,7 +752,7 @@ public void SetupBuilderLoadNLogConfigFromFileNotExistsTest() { // Arrange var xmlFile = new System.IO.StringReader(""); - var appEnv = new Mocks.AppEnvironmentMock(f => false, f => System.Xml.XmlReader.Create(xmlFile)); + var appEnv = new Mocks.AppEnvironmentMock(f => false); var configLoader = new LoggingConfigurationFileLoader(appEnv); var logFactory = new LogFactory(configLoader); @@ -738,7 +768,7 @@ public void SetupBuilderLoadConfigurationFromFileOptionalFalseTest() { // Arrange var xmlFile = new System.IO.StringReader(""); - var appEnv = new Mocks.AppEnvironmentMock(f => false, f => System.Xml.XmlReader.Create(xmlFile)); + var appEnv = new Mocks.AppEnvironmentMock(f => false); var configLoader = new LoggingConfigurationFileLoader(appEnv); var logFactory = new LogFactory(configLoader); @@ -764,7 +794,7 @@ public void SetupBuilderLoadConfigurationFromXmlPatchTest() { // Arrange var xmlFile = new System.IO.StringReader(""); - var appEnv = new Mocks.AppEnvironmentMock(f => true, f => System.Xml.XmlReader.Create(xmlFile)); + var appEnv = new Mocks.AppEnvironmentMock(f => true); var configLoader = new LoggingConfigurationFileLoader(appEnv); var logFactory = new LogFactory(configLoader); diff --git a/tests/NLog.UnitTests/Config/ReloadTests.cs b/tests/NLog.UnitTests/Config/ReloadTests.cs index 5cc2c66f79..dc5d600b5f 100644 --- a/tests/NLog.UnitTests/Config/ReloadTests.cs +++ b/tests/NLog.UnitTests/Config/ReloadTests.cs @@ -31,14 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // - -#if !MONO - namespace NLog.UnitTests.Config { using System; using System.IO; - using System.Threading; + using System.Linq; using NLog.Config; using Xunit; @@ -52,60 +49,15 @@ public ReloadTests() } } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void TestNoAutoReload(bool useExplicitFileLoading) - { - string config1 = @" - - - "; - string config2 = @" - - - "; - - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempDir); - - string configFilePath = Path.Combine(tempDir, "noreload.nlog"); - WriteConfigFile(configFilePath, config1); - - try - { - SetLogManagerConfiguration(useExplicitFileLoading, configFilePath); - - Assert.False(((XmlLoggingConfiguration)LogManager.Configuration).AutoReload); - - var logger = LogManager.GetLogger("A"); - logger.Debug("aaa"); - AssertDebugLastMessage("debug", "aaa"); - - ChangeAndReloadConfigFile(configFilePath, config2, assertDidReload: false); - - logger.Debug("bbb"); - // Assert that config1 is still loaded. - AssertDebugLastMessage("debug", "bbb"); - } - finally - { - if (Directory.Exists(tempDir)) - Directory.Delete(tempDir, true); - } - } - - private static void SetLogManagerConfiguration(bool useExplicitFileLoading, string configFilePath) + private static void SetLogManagerConfiguration(LogFactory logFactory, bool useExplicitFileLoading, string configFilePath) { if (useExplicitFileLoading) { - LogManager.Configuration = new XmlLoggingConfiguration(configFilePath); + logFactory.Configuration = new XmlLoggingConfiguration(configFilePath); } else { -#pragma warning disable CS0618 // Type or member is obsolete - LogManager.LogFactory.SetCandidateConfigFilePaths(new string[] { configFilePath }); -#pragma warning restore CS0618 // Type or member is obsolete + logFactory.Setup().LoadConfigurationFromFile(new string[] { configFilePath }); } } @@ -114,13 +66,6 @@ private static void SetLogManagerConfiguration(bool useExplicitFileLoading, stri [InlineData(false)] public void TestAutoReloadOnFileChange(bool useExplicitFileLoading) { -#if NETSTANDARD || MONO - if (IsLinux()) - { - Console.WriteLine("[SKIP] ReloadTests.TestAutoReloadOnFileChange because we are running in Travis"); - return; - } -#endif string config1 = @" @@ -134,35 +79,47 @@ public void TestAutoReloadOnFileChange(bool useExplicitFileLoading) "; string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempDir); - string configFilePath = Path.Combine(tempDir, "reload.nlog"); - WriteConfigFile(configFilePath, config1); + var logFactory = new LogFactory(); try { - SetLogManagerConfiguration(useExplicitFileLoading, configFilePath); + Directory.CreateDirectory(tempDir); - Assert.True(((XmlLoggingConfiguration)LogManager.Configuration).AutoReload); + string configFilePath = Path.Combine(tempDir, "reload.nlog"); + WriteConfigFile(configFilePath, config1); - var logger = LogManager.GetLogger("A"); + SetLogManagerConfiguration(logFactory, useExplicitFileLoading, configFilePath); + + var logger = logFactory.GetCurrentClassLogger(); logger.Debug("aaa"); - AssertDebugLastMessage("debug", "aaa"); + AssertDebugLastMessage("debug", "aaa", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(configFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); - ChangeAndReloadConfigFile(configFilePath, badConfig, assertDidReload: false); + WriteConfigFileAndReload(logFactory, configFilePath, badConfig); logger.Debug("bbb"); // Assert that config1 is still loaded. - AssertDebugLastMessage("debug", "bbb"); + AssertDebugLastMessage("debug", "bbb", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(configFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); - ChangeAndReloadConfigFile(configFilePath, config2); + WriteConfigFileAndReload(logFactory, configFilePath, config2); logger.Debug("ccc"); // Assert that config2 is loaded. - AssertDebugLastMessage("debug", "[ccc]"); + AssertDebugLastMessage("debug", "[ccc]", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(configFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); } finally { + logFactory.Shutdown(); + if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } @@ -174,14 +131,6 @@ public void TestAutoReloadOnFileChange(bool useExplicitFileLoading) public void TestAutoReloadOnFileMove(bool useExplicitFileLoading) { -#if NETSTANDARD || MONO - if (IsLinux()) - { - Console.WriteLine("[SKIP] ReloadTests.TestAutoReloadOnFileMove because we are running in Travis"); - return; - } -#endif - string config1 = @" @@ -192,45 +141,50 @@ public void TestAutoReloadOnFileMove(bool useExplicitFileLoading) "; string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempDir); - string configFilePath = Path.Combine(tempDir, "reload.nlog"); - WriteConfigFile(configFilePath, config1); - string otherFilePath = Path.Combine(tempDir, "other.nlog"); + var logFactory = new LogFactory(); try { - SetLogManagerConfiguration(useExplicitFileLoading, configFilePath); + Directory.CreateDirectory(tempDir); - var logger = LogManager.GetLogger("A"); - logger.Debug("aaa"); - AssertDebugLastMessage("debug", "aaa"); + string configFilePath = Path.Combine(tempDir, "reload.nlog"); + WriteConfigFile(configFilePath, config1); + string otherFilePath = Path.Combine(tempDir, "other.nlog"); - using (var reloadWaiter = new ConfigurationReloadWaiter()) - { - File.Move(configFilePath, otherFilePath); - reloadWaiter.WaitForReload(); - } + SetLogManagerConfiguration(logFactory, useExplicitFileLoading, configFilePath); + + var logger = logFactory.GetCurrentClassLogger(); + logger.Debug("aaa"); + AssertDebugLastMessage("debug", "aaa", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(configFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); + File.Move(configFilePath, otherFilePath); + logFactory.Setup().ReloadConfiguration(); logger.Debug("bbb"); // Assert that config1 is still loaded. - AssertDebugLastMessage("debug", "bbb"); + AssertDebugLastMessage("debug", "bbb", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(configFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); WriteConfigFile(otherFilePath, config2); - using (var reloadWaiter = new ConfigurationReloadWaiter()) - { - File.Move(otherFilePath, configFilePath); - - reloadWaiter.WaitForReload(); - Assert.True(reloadWaiter.DidReload); - } + File.Move(otherFilePath, configFilePath); + logFactory.Setup().ReloadConfiguration(); logger.Debug("ccc"); // Assert that config2 is loaded. - AssertDebugLastMessage("debug", "[ccc]"); + AssertDebugLastMessage("debug", "[ccc]", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(configFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); } finally { + logFactory.Shutdown(); + if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } @@ -239,16 +193,8 @@ public void TestAutoReloadOnFileMove(bool useExplicitFileLoading) [Theory] [InlineData(true)] [InlineData(false)] - public void TestAutoReloadOnFileCopy(bool useExplicitFileLoading) { -#if NETSTANDARD || MONO - if (IsLinux()) - { - Console.WriteLine("[SKIP] ReloadTests.TestAutoReloadOnFileCopy because we are running in Travis"); - return; - } -#endif string config1 = @" @@ -259,46 +205,52 @@ public void TestAutoReloadOnFileCopy(bool useExplicitFileLoading) "; string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempPath); - string configFilePath = Path.Combine(tempPath, "reload.nlog"); - WriteConfigFile(configFilePath, config1); - string otherFilePath = Path.Combine(tempPath, "other.nlog"); + var logFactory = new LogFactory(); try { - SetLogManagerConfiguration(useExplicitFileLoading, configFilePath); + Directory.CreateDirectory(tempPath); + + string configFilePath = Path.Combine(tempPath, "reload.nlog"); + WriteConfigFile(configFilePath, config1); + string otherFilePath = Path.Combine(tempPath, "other.nlog"); - var logger = LogManager.GetLogger("A"); + SetLogManagerConfiguration(logFactory, useExplicitFileLoading, configFilePath); + + var logger = logFactory.GetCurrentClassLogger(); logger.Debug("aaa"); - AssertDebugLastMessage("debug", "aaa"); + AssertDebugLastMessage("debug", "aaa", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(configFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); - using (var reloadWaiter = new ConfigurationReloadWaiter()) - { - File.Delete(configFilePath); - reloadWaiter.WaitForReload(); - } + File.Delete(configFilePath); + logFactory.Setup().ReloadConfiguration(); logger.Debug("bbb"); // Assert that config1 is still loaded. - AssertDebugLastMessage("debug", "bbb"); + AssertDebugLastMessage("debug", "bbb", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(configFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); WriteConfigFile(otherFilePath, config2); - using (var reloadWaiter = new ConfigurationReloadWaiter()) - { - File.Copy(otherFilePath, configFilePath); - File.Delete(otherFilePath); - - reloadWaiter.WaitForReload(); - Assert.True(reloadWaiter.DidReload); - } + File.Copy(otherFilePath, configFilePath); + File.Delete(otherFilePath); + logFactory.Setup().ReloadConfiguration(); logger.Debug("ccc"); // Assert that config2 is loaded. - AssertDebugLastMessage("debug", "[ccc]"); + AssertDebugLastMessage("debug", "[ccc]", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(configFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); } finally { + logFactory.Shutdown(); + if (Directory.Exists(tempPath)) Directory.Delete(tempPath, true); } @@ -307,17 +259,8 @@ public void TestAutoReloadOnFileCopy(bool useExplicitFileLoading) [Theory] [InlineData(true)] [InlineData(false)] - public void TestIncludedConfigNoReload(bool useExplicitFileLoading) { -#if NETSTANDARD || MONO - if (IsLinux()) - { - Console.WriteLine("[SKIP] ReloadTests.TestIncludedConfigNoReload because we are running in Travis"); - return; - } -#endif - string mainConfig1 = @" @@ -334,37 +277,50 @@ public void TestIncludedConfigNoReload(bool useExplicitFileLoading) "; string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempDir); - - string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); - WriteConfigFile(mainConfigFilePath, mainConfig1); - string includedConfigFilePath = Path.Combine(tempDir, "included.nlog"); - WriteConfigFile(includedConfigFilePath, includedConfig1); + var logFactory = new LogFactory(); try { - SetLogManagerConfiguration(useExplicitFileLoading, mainConfigFilePath); + Directory.CreateDirectory(tempDir); - var logger = LogManager.GetLogger("A"); + string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); + WriteConfigFile(mainConfigFilePath, mainConfig1); + + string includedConfigFilePath = Path.Combine(tempDir, "included.nlog"); + WriteConfigFile(includedConfigFilePath, includedConfig1); + + SetLogManagerConfiguration(logFactory, useExplicitFileLoading, mainConfigFilePath); + + var logger = logFactory.GetCurrentClassLogger(); logger.Debug("aaa"); - AssertDebugLastMessage("debug", "aaa"); + AssertDebugLastMessage("debug", "aaa", logFactory); + Assert.False(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Empty(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); - ChangeAndReloadConfigFile(mainConfigFilePath, mainConfig2, assertDidReload: false); + WriteConfigFileAndReload(logFactory, mainConfigFilePath, mainConfig2); logger.Debug("bbb"); - // Assert that mainConfig1 is still loaded. - AssertDebugLastMessage("debug", "bbb"); + // Assert that mainConfig2 has been loaded. + AssertDebugLastMessage("debug", "", logFactory); + Assert.False(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Empty(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + logger.Info("bbb"); + AssertDebugLastMessage("debug", "bbb", logFactory); WriteConfigFile(mainConfigFilePath, mainConfig1); - ChangeAndReloadConfigFile(includedConfigFilePath, includedConfig2, assertDidReload: false); + WriteConfigFileAndReload(logFactory, includedConfigFilePath, includedConfig2); logger.Debug("ccc"); - // Assert that includedConfig1 is still loaded. - AssertDebugLastMessage("debug", "ccc"); + // Assert that includedConfig2 has been loaded. + AssertDebugLastMessage("debug", "[ccc]", logFactory); + Assert.False(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Empty(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); } finally { + logFactory.Shutdown(); + if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } @@ -373,25 +329,12 @@ public void TestIncludedConfigNoReload(bool useExplicitFileLoading) [Theory] [InlineData(true)] [InlineData(false)] - public void TestIncludedConfigReload(bool useExplicitFileLoading) { -#if NETSTANDARD || MONO - if (IsLinux()) - { - Console.WriteLine("[SKIP] ReloadTests.TestIncludedConfigNoReload because we are running in Travis"); - return; - } -#endif - string mainConfig1 = @" "; - string mainConfig2 = @" - - - "; string includedConfig1 = @" "; @@ -400,37 +343,41 @@ public void TestIncludedConfigReload(bool useExplicitFileLoading) "; string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempDir); - - string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); - WriteConfigFile(mainConfigFilePath, mainConfig1); - string includedConfigFilePath = Path.Combine(tempDir, "included.nlog"); - WriteConfigFile(includedConfigFilePath, includedConfig1); + var logFactory = new LogFactory(); try { - SetLogManagerConfiguration(useExplicitFileLoading, mainConfigFilePath); + Directory.CreateDirectory(tempDir); - var logger = LogManager.GetLogger("A"); - logger.Debug("aaa"); - AssertDebugLastMessage("debug", "aaa"); + string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); + WriteConfigFile(mainConfigFilePath, mainConfig1); - ChangeAndReloadConfigFile(mainConfigFilePath, mainConfig2, assertDidReload: false); + string includedConfigFilePath = Path.Combine(tempDir, "included.nlog"); + WriteConfigFile(includedConfigFilePath, includedConfig1); - logger.Debug("bbb"); - // Assert that mainConfig1 is still loaded. - AssertDebugLastMessage("debug", "bbb"); + SetLogManagerConfiguration(logFactory, useExplicitFileLoading, mainConfigFilePath); - WriteConfigFile(mainConfigFilePath, mainConfig1); - ChangeAndReloadConfigFile(includedConfigFilePath, includedConfig2); + var logger = logFactory.GetCurrentClassLogger(); + logger.Debug("aaa"); + AssertDebugLastMessage("debug", "aaa", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(includedConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); + + WriteConfigFileAndReload(logFactory, includedConfigFilePath, includedConfig2); logger.Debug("ccc"); // Assert that includedConfig2 is loaded. - AssertDebugLastMessage("debug", "[ccc]"); + AssertDebugLastMessage("debug", "[ccc]", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(includedConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); } finally { + logFactory.Shutdown(); + if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } @@ -439,17 +386,8 @@ public void TestIncludedConfigReload(bool useExplicitFileLoading) [Theory] [InlineData(true)] [InlineData(false)] - public void TestMainConfigReload(bool useExplicitFileLoading) { -#if NETSTANDARD || MONO - if (IsLinux()) - { - Console.WriteLine("[SKIP] ReloadTests.TestMainConfigReload because we are running in Travis"); - return; - } -#endif - string mainConfig1 = @" @@ -469,39 +407,56 @@ public void TestMainConfigReload(bool useExplicitFileLoading) "; string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempDir); - - string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); - WriteConfigFile(mainConfigFilePath, mainConfig1); - - string included1ConfigFilePath = Path.Combine(tempDir, "included.nlog"); - WriteConfigFile(included1ConfigFilePath, included1Config); - string included2ConfigFilePath = Path.Combine(tempDir, "included2.nlog"); - WriteConfigFile(included2ConfigFilePath, included2Config1); + var logFactory = new LogFactory(); try { - SetLogManagerConfiguration(useExplicitFileLoading, mainConfigFilePath); + Directory.CreateDirectory(tempDir); + + string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); + WriteConfigFile(mainConfigFilePath, mainConfig1); + + string included1ConfigFilePath = Path.Combine(tempDir, "included.nlog"); + WriteConfigFile(included1ConfigFilePath, included1Config); + + string included2ConfigFilePath = Path.Combine(tempDir, "included2.nlog"); + WriteConfigFile(included2ConfigFilePath, included2Config1); - var logger = LogManager.GetLogger("A"); + SetLogManagerConfiguration(logFactory, useExplicitFileLoading, mainConfigFilePath); + + var logger = logFactory.GetCurrentClassLogger(); logger.Debug("aaa"); - AssertDebugLastMessage("debug", "aaa"); + AssertDebugLastMessage("debug", "aaa", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.NotEmpty(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Contains(mainConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Contains(included1ConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); - ChangeAndReloadConfigFile(mainConfigFilePath, mainConfig2); + WriteConfigFileAndReload(logFactory, mainConfigFilePath, mainConfig2); logger.Debug("bbb"); // Assert that mainConfig2 is loaded (which refers to included2.nlog). - AssertDebugLastMessage("debug", "[bbb]"); + AssertDebugLastMessage("debug", "[bbb]", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.NotEmpty(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Contains(mainConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Contains(included2ConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); - ChangeAndReloadConfigFile(included2ConfigFilePath, included2Config2); + WriteConfigFileAndReload(logFactory, included2ConfigFilePath, included2Config2); logger.Debug("ccc"); // Assert that included2Config2 is loaded. - AssertDebugLastMessage("debug", "(ccc)"); + AssertDebugLastMessage("debug", "(ccc)", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.NotEmpty(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Contains(mainConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Contains(included2ConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); } finally { + logFactory.Shutdown(); + if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } @@ -510,17 +465,8 @@ public void TestMainConfigReload(bool useExplicitFileLoading) [Theory] [InlineData(true)] [InlineData(false)] - public void TestMainConfigReloadIncludedConfigNoReload(bool useExplicitFileLoading) { -#if NETSTANDARD || MONO - if (IsLinux()) - { - Console.WriteLine("[SKIP] ReloadTests.TestMainConfigReload because we are running in Travis"); - return; - } -#endif - string mainConfig1 = @" @@ -540,39 +486,53 @@ public void TestMainConfigReloadIncludedConfigNoReload(bool useExplicitFileLoadi "; string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempDir); - - string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); - WriteConfigFile(mainConfigFilePath, mainConfig1); - - string included1ConfigFilePath = Path.Combine(tempDir, "included.nlog"); - WriteConfigFile(included1ConfigFilePath, included1Config); - string included2ConfigFilePath = Path.Combine(tempDir, "included2.nlog"); - WriteConfigFile(included2ConfigFilePath, included2Config1); + var logFactory = new LogFactory(); try { - SetLogManagerConfiguration(useExplicitFileLoading, mainConfigFilePath); + Directory.CreateDirectory(tempDir); + + string mainConfigFilePath = Path.Combine(tempDir, "main.nlog"); + WriteConfigFile(mainConfigFilePath, mainConfig1); + + string included1ConfigFilePath = Path.Combine(tempDir, "included.nlog"); + WriteConfigFile(included1ConfigFilePath, included1Config); + + string included2ConfigFilePath = Path.Combine(tempDir, "included2.nlog"); + WriteConfigFile(included2ConfigFilePath, included2Config1); - var logger = LogManager.GetLogger("A"); + SetLogManagerConfiguration(logFactory, useExplicitFileLoading, mainConfigFilePath); + + var logger = logFactory.GetCurrentClassLogger(); logger.Debug("aaa"); - AssertDebugLastMessage("debug", "aaa"); + AssertDebugLastMessage("debug", "aaa", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.NotEmpty(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Contains(mainConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Contains(included1ConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); - ChangeAndReloadConfigFile(mainConfigFilePath, mainConfig2); + WriteConfigFileAndReload(logFactory, mainConfigFilePath, mainConfig2); logger.Debug("bbb"); // Assert that mainConfig2 is loaded (which refers to included2.nlog). - AssertDebugLastMessage("debug", "[bbb]"); + AssertDebugLastMessage("debug", "[bbb]", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(mainConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); - ChangeAndReloadConfigFile(included2ConfigFilePath, included2Config2, assertDidReload: false); + WriteConfigFileAndReload(logFactory, included2ConfigFilePath, included2Config2); logger.Debug("ccc"); - // Assert that included2Config1 is still loaded. - AssertDebugLastMessage("debug", "[ccc]"); + // Assert that included2Config2 has been loaded. + AssertDebugLastMessage("debug", "(ccc)", logFactory); + Assert.True(((XmlLoggingConfiguration)logFactory.Configuration).AutoReload); + Assert.Single(((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames); + Assert.Equal(mainConfigFilePath, ((XmlLoggingConfiguration)logFactory.Configuration).AutoReloadFileNames.FirstOrDefault()); } finally { + logFactory.Shutdown(); if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); @@ -580,7 +540,6 @@ public void TestMainConfigReloadIncludedConfigNoReload(bool useExplicitFileLoadi } [Fact] - [Obsolete("Replaced by ConfigurationChanged. Marked obsolete on NLog 5.2")] public void TestKeepVariablesOnReload() { string config = @" @@ -588,13 +547,12 @@ public void TestKeepVariablesOnReload() "; - var configLoader = new LoggingConfigurationWatchableFileLoader(LogFactory.DefaultAppEnvironment); - var logFactory = new LogFactory(configLoader); + var logFactory = new LogFactory(); var configuration = XmlLoggingConfigurationMock.CreateFromXml(logFactory, config); logFactory.Configuration = configuration; logFactory.Configuration.Variables["var1"] = "new_value"; logFactory.Configuration.Variables["var3"] = "new_value3"; - configLoader.ReloadConfigOnTimer(configuration); + var nullEvent = LogEventInfo.CreateNullEvent(); Assert.Equal("new_value", logFactory.Configuration.Variables["var1"].Render(nullEvent)); Assert.Equal("keep_value", logFactory.Configuration.Variables["var2"].Render(nullEvent)); @@ -641,7 +599,6 @@ public void TestKeepVariablesOnReloadAllowUpdate() } [Fact] - [Obsolete("Replaced by ConfigurationChanged. Marked obsolete on NLog 5.2")] public void TestResetVariablesOnReload() { string config = @" @@ -649,15 +606,14 @@ public void TestResetVariablesOnReload() "; - var configLoader = new LoggingConfigurationWatchableFileLoader(LogFactory.DefaultAppEnvironment); - var logFactory = new LogFactory(configLoader); + var logFactory = new LogFactory(); var configuration = XmlLoggingConfigurationMock.CreateFromXml(logFactory, config); logFactory.Configuration = configuration; logFactory.Configuration.Variables["var1"] = "new_value"; logFactory.Configuration.Variables["var3"] = "new_value3"; - configLoader.ReloadConfigOnTimer(configuration); + LogEventInfo nullEvent = LogEventInfo.CreateNullEvent(); - Assert.Equal("", logFactory.Configuration.Variables["var1"].Render(nullEvent)); + Assert.Equal("new_value", logFactory.Configuration.Variables["var1"].Render(nullEvent)); Assert.Equal("keep_value", logFactory.Configuration.Variables["var2"].Render(nullEvent)); logFactory.Configuration = configuration.Reload(); @@ -696,28 +652,25 @@ public void KeepVariablesOnReloadWithStaticMode() } [Fact] - [Obsolete("Replaced by ConfigurationChanged. Marked obsolete on NLog 5.2")] public void ReloadConfigOnTimer_When_No_Exception_Raises_ConfigurationReloadedEvent() { var called = false; - LoggingConfigurationReloadedEventArgs arguments = null; + LoggingConfigurationChangedEventArgs arguments = null; object calledBy = null; - var configLoader = new LoggingConfigurationWatchableFileLoader(LogFactory.DefaultAppEnvironment); - var logFactory = new LogFactory(configLoader); + var logFactory = new LogFactory(); var loggingConfiguration = XmlLoggingConfigurationMock.CreateFromXml(logFactory, ""); logFactory.Configuration = loggingConfiguration; - logFactory.ConfigurationReloaded += (sender, args) => { called = true; calledBy = sender; arguments = args; }; + logFactory.ConfigurationChanged += (sender, args) => { called = true; calledBy = sender; arguments = args; }; - configLoader.ReloadConfigOnTimer(loggingConfiguration); + logFactory.Setup().ReloadConfiguration(); Assert.True(called); Assert.Same(calledBy, logFactory); - Assert.True(arguments.Succeeded); + Assert.NotNull(arguments.ActivatedConfiguration); } [Fact] - [Obsolete("Replaced by LogFactory.Setup().LoadConfigurationFromFile(). Marked obsolete on NLog 5.2")] public void TestReloadingInvalidConfiguration() { var validXmlConfig = @" @@ -726,8 +679,8 @@ public void TestReloadingInvalidConfiguration() "; - var invalidXmlConfig = @" - + var invalidXmlConfig = @" + "; string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); @@ -739,7 +692,7 @@ public void TestReloadingInvalidConfiguration() { var nlogConfigFile = Path.Combine(tempDir, "NLog.config"); LogFactory logFactory = new LogFactory(); - logFactory.SetCandidateConfigFilePaths(new[] { nlogConfigFile }); + logFactory.Setup().LoadConfigurationFromFile(new[] { nlogConfigFile }); var config = logFactory.Configuration; Assert.Null(config); @@ -747,7 +700,9 @@ public void TestReloadingInvalidConfiguration() config = logFactory.Configuration; Assert.NotNull(config); Assert.Empty(config.AllTargets); // Failed to load +#pragma warning disable CS0618 // Type or member is obsolete Assert.Single(config.FileNamesToWatch); // But file-watcher is active +#pragma warning restore CS0618 // Type or member is obsolete WriteConfigFile(nlogConfigFile, validXmlConfig); config = logFactory.Configuration.Reload(); @@ -763,13 +718,11 @@ public void TestReloadingInvalidConfiguration() } } - [Fact] - [Obsolete("Replaced by LogFactory.Setup().LoadConfigurationFromFile(). Marked obsolete on NLog 5.2")] - public void TestThrowExceptionWhenInvalidXml() + [Theory] + [InlineData(@"")] + [InlineData(@"")] + public void TestThrowExceptionWhenInvalidXml(string invalidXmlConfig) { - var invalidXmlConfig = @" - "; - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDir); @@ -780,8 +733,7 @@ public void TestThrowExceptionWhenInvalidXml() var nlogConfigFile = Path.Combine(tempDir, "NLog.config"); WriteConfigFile(nlogConfigFile, invalidXmlConfig); LogFactory logFactory = new LogFactory(); - logFactory.SetCandidateConfigFilePaths(new[] { nlogConfigFile }); - Assert.Throws(() => logFactory.GetLogger("Hello")); + Assert.Throws(() => logFactory.Setup().LoadConfigurationFromFile(new[] { nlogConfigFile }).GetCurrentClassLogger()); } } finally @@ -799,45 +751,18 @@ private static void WriteConfigFile(string configFilePath, string config) writer.Write(config); } - private static void ChangeAndReloadConfigFile(string configFilePath, string config, bool assertDidReload = true) - { - using (var reloadWaiter = new ConfigurationReloadWaiter()) - { - WriteConfigFile(configFilePath, config); - reloadWaiter.WaitForReload(); - - if (assertDidReload) - Assert.True(reloadWaiter.DidReload, $"Config '{configFilePath}' did not reload."); - } - } - - private sealed class ConfigurationReloadWaiter : IDisposable + private static void WriteConfigFileAndReload(LogFactory logFactory, string configFilePath, string config) { - private ManualResetEvent counterEvent = new ManualResetEvent(false); - - public ConfigurationReloadWaiter() - { - LogManager.ConfigurationChanged += SignalCounterEvent(counterEvent); - } - - public bool DidReload => counterEvent.WaitOne(0); - - public void Dispose() - { - LogManager.ConfigurationChanged -= SignalCounterEvent(counterEvent); - } + using (StreamWriter writer = File.CreateText(configFilePath)) + writer.Write(config); - public void WaitForReload() + try { - counterEvent.WaitOne(3000); + logFactory.Setup().ReloadConfiguration(); } - - private static EventHandler SignalCounterEvent(ManualResetEvent counterEvent) + catch { - return (sender, e) => - { - counterEvent.Set(); - }; + // Swallow issues from loading bad config } } } @@ -871,4 +796,3 @@ public static XmlLoggingConfigurationMock CreateFromXml(LogFactory logFactory, s } } } -#endif diff --git a/tests/NLog.UnitTests/Config/RuleConfigurationTests.cs b/tests/NLog.UnitTests/Config/RuleConfigurationTests.cs index 3ab2bf87c2..47c56c3228 100644 --- a/tests/NLog.UnitTests/Config/RuleConfigurationTests.cs +++ b/tests/NLog.UnitTests/Config/RuleConfigurationTests.cs @@ -544,8 +544,8 @@ public void MultipleRulesSameTargetTest() ").LogFactory; - var loggerConfig = logFactory.BuildLoggerConfiguration("AAA", logFactory.Configuration?.GetLoggingRulesThreadSafe()); - var targets = loggerConfig[LogLevel.Warn.Ordinal]; + var loggerConfig = logFactory.BuildLoggerConfiguration("AAA"); + var targets = loggerConfig[LogLevel.Warn.Ordinal] as TargetWithFilterChain; Assert.Equal("d1", targets.Target.Name); Assert.Equal("d2", targets.NextInChain.Target.Name); Assert.Equal("d3", targets.NextInChain.NextInChain.Target.Name); @@ -1012,7 +1012,7 @@ public void LoggingRule_FinalMinLevelLayoutAsVar_EnablesExpectedLevels(string in [InlineData("Off", "Fatal", null)] [InlineData("Error", "Debug", null)] [InlineData(" ", "", null)] - [InlineData(" ", "Fatal", null)] + [InlineData(" ", "Fatal", new[] { nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Info), nameof(LogLevel.Warn), nameof(LogLevel.Error), nameof(LogLevel.Fatal) })] [InlineData("", "", null)] [InlineData("", "Off", new[] { nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Info), nameof(LogLevel.Warn), nameof(LogLevel.Error), nameof(LogLevel.Fatal) })] [InlineData("", "Fatal", new[] { nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Info), nameof(LogLevel.Warn), nameof(LogLevel.Error), nameof(LogLevel.Fatal) })] @@ -1027,7 +1027,7 @@ public void LoggingRule_FinalMinLevelLayoutAsVar_EnablesExpectedLevels(string in [InlineData(" error", "", new[] { nameof(LogLevel.Error), nameof(LogLevel.Fatal) })] [InlineData("Error", "", new[] { nameof(LogLevel.Error), nameof(LogLevel.Fatal) })] [InlineData("Fatal", "", new[] { nameof(LogLevel.Fatal) })] - [InlineData("Trace", " ", null)] + [InlineData("Trace", " ", new[] { nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Info), nameof(LogLevel.Warn), nameof(LogLevel.Error), nameof(LogLevel.Fatal) })] [InlineData("Trace", "", new[] { nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Info), nameof(LogLevel.Warn), nameof(LogLevel.Error), nameof(LogLevel.Fatal) })] [InlineData("Trace", "Debug", new[] { nameof(LogLevel.Trace), nameof(LogLevel.Debug) })] [InlineData("Trace", "Trace", new[] { nameof(LogLevel.Trace), nameof(LogLevel.Trace) })] diff --git a/tests/NLog.UnitTests/Config/ServiceRepositoryTests.cs b/tests/NLog.UnitTests/Config/ServiceRepositoryTests.cs index d0827bc2f0..17c7a12f94 100644 --- a/tests/NLog.UnitTests/Config/ServiceRepositoryTests.cs +++ b/tests/NLog.UnitTests/Config/ServiceRepositoryTests.cs @@ -36,9 +36,7 @@ namespace NLog.UnitTests.Config { using System; using System.Text; - using JetBrains.Annotations; using NLog.Config; - using NLog.Internal; using NLog.Targets; using Xunit; @@ -86,52 +84,6 @@ public void SideBySideLogFactoryInternalInterfaceTest() Assert.Equal("Kenny" + "_" + name2, target2.LastMessage); } - [Fact] - public void ResolveShouldInjectDependencies() - { - // Arrange - var logFactory = new LogFactory(); - - // Act - var target = logFactory.ServiceRepository.ResolveService(); - - // Assert - Assert.NotNull(target.JsonConverter); - } - - [Fact] - public void ResolveShouldInjectNestedDependencies() - { - // Arrange - var logFactory = new LogFactory(); - - // Act - var target = logFactory.ServiceRepository.ResolveService(); - - // Assert - Assert.NotNull(target.Helper.JsonConverter); - } - - [Fact] - public void ResolveWithDirectCycleShouldThrow() - { - // Arrange - var logFactory = new LogFactory(); - - // Act & Assert - AssertCycleException(logFactory); - } - - [Fact] - public void ResolveWithIndirectCycleShouldThrow() - { - // Arrange - var logFactory = new LogFactory(); - - // Act & Assert - AssertCycleException(logFactory); - } - [Fact] public void HandleDelayedInjectDependenciesFailure() { @@ -216,13 +168,6 @@ public void ResolveShouldCheckExternalServiceProvider() Assert.NotNull(otherDependency); } - private static void AssertCycleException(LogFactory logFactory) where T : class - { - var ex = Assert.Throws(() => logFactory.ServiceRepository.ResolveService()); - Assert.Contains("cycle", ex.Message, StringComparison.OrdinalIgnoreCase); - Assert.Equal(typeof(T), ex.ServiceType); - } - private static void InitializeLogFactoryJsonConverter(LogFactory logFactory, string testValue, out Logger logger, out DebugTarget target) { logFactory.ServiceRepository.RegisterService(typeof(IJsonConverter), new MySimpleJsonConverter { Test = testValue }); @@ -258,70 +203,6 @@ public bool SerializeObject(object value, StringBuilder builder) } } - private class TargetWithInjection : Target - { - public TargetWithInjection([NotNull] IJsonConverter jsonConverter) - { - JsonConverter = Guard.ThrowIfNull(jsonConverter); - } - - public IJsonConverter JsonConverter { get; } - } - - private class TargetWithNestedInjection : Target - { - public ClassWithInjection Helper { get; } - - /// - public TargetWithNestedInjection([NotNull] ClassWithInjection helper) - { - Helper = Guard.ThrowIfNull(helper); - } - } - - private class TargetWithDirectCycleInjection : Target - { - /// - public TargetWithDirectCycleInjection(TargetWithDirectCycleInjection cycle1) - { - } - } - - private class TargetWithIndirectCycleInjection : Target - { - /// - public TargetWithIndirectCycleInjection(CycleHelperClass1 helper) - { - } - } - - private class CycleHelperClass1 - { - /// - public CycleHelperClass1(CycleHelperClass2 helper) - { - } - } - - private class CycleHelperClass2 - { - /// - public CycleHelperClass2(CycleHelperClass1 helper) - { - } - } - - private class ClassWithInjection - { - public IJsonConverter JsonConverter { get; } - - /// - public ClassWithInjection(IJsonConverter jsonConverter) - { - JsonConverter = jsonConverter; - } - } - private class TargetWithMissingDependency : Target { public LogEventInfo LastLogEvent { get; private set; } diff --git a/tests/NLog.UnitTests/Config/TargetConfigurationTests.cs b/tests/NLog.UnitTests/Config/TargetConfigurationTests.cs index e09a4ecbb1..4cc3d7025b 100644 --- a/tests/NLog.UnitTests/Config/TargetConfigurationTests.cs +++ b/tests/NLog.UnitTests/Config/TargetConfigurationTests.cs @@ -498,7 +498,7 @@ public void DefaultWrapperTest() var retryingTargetWrapper = bufferingTargetWrapper.WrappedTarget as RetryingTargetWrapper; Assert.NotNull(retryingTargetWrapper); - Assert.Null(retryingTargetWrapper.Name); + Assert.Empty(retryingTargetWrapper.Name); var debugTarget = retryingTargetWrapper.WrappedTarget as DebugTarget; Assert.NotNull(debugTarget); @@ -521,7 +521,6 @@ public void DontThrowExceptionWhenArchiveEverySetByDefaultParameters() - - - - - - - - ").LogFactory.Configuration; - - var myTarget = c.FindTargetByName("myTarget") as MyRequiredTarget; - Assert.NotNull(myTarget); - - var missingStringValue = Assert.Throws(() => new LogFactory().Setup().LoadConfigurationFromXml(@" - - - - - - - - - ")); - Assert.Contains(nameof(MyRequiredTarget.StringProperty), missingStringValue.Message); - - var missingEnumValue = Assert.Throws(() => new LogFactory().Setup().LoadConfigurationFromXml(@" - - - - - - - - - ")); - Assert.Contains(nameof(MyRequiredTarget.EnumProperty), missingEnumValue.Message); - - var emptyEnumValue = Assert.Throws(() => new LogFactory().Setup().LoadConfigurationFromXml(@" - - - - - - - - - ")); - Assert.Contains(nameof(MyRequiredTarget.EnumProperty), emptyEnumValue.Message); - } - [Fact] public void DataTypesTest() { @@ -855,9 +790,13 @@ public MyNullableTarget(string name) : this() [Target("MyRequiredTarget")] public class MyRequiredTarget : Target { +#pragma warning disable CS0618 // Type or member is obsolete [RequiredParameter] +#pragma warning restore CS0618 // Type or member is obsolete public string StringProperty { get; set; } +#pragma warning disable CS0618 // Type or member is obsolete [RequiredParameter] +#pragma warning restore CS0618 // Type or member is obsolete public MyEnum? EnumProperty { get; set; } } diff --git a/tests/NLog.UnitTests/Config/VariableTests.cs b/tests/NLog.UnitTests/Config/VariableTests.cs index ffacb7a5a0..59b9acb23f 100644 --- a/tests/NLog.UnitTests/Config/VariableTests.cs +++ b/tests/NLog.UnitTests/Config/VariableTests.cs @@ -82,15 +82,15 @@ public void VariablesTest_string_expanding() - + "); var target = configuration.FindTargetByName("test") as FileTarget; Assert.NotNull(target); //dont change the ${test} as it isn't a Layout - Assert.NotEqual(typeof(Layout), target.ArchiveDateFormat.GetType()); - Assert.Equal("hello", target.ArchiveDateFormat); + Assert.Equal(typeof(string), target.ArchiveSuffixFormat.GetType()); + Assert.Equal("hello", target.ArchiveSuffixFormat); } [Fact] diff --git a/tests/NLog.UnitTests/Config/XmlConfigTests.cs b/tests/NLog.UnitTests/Config/XmlConfigTests.cs index f62b93f4bd..13bd47b681 100644 --- a/tests/NLog.UnitTests/Config/XmlConfigTests.cs +++ b/tests/NLog.UnitTests/Config/XmlConfigTests.cs @@ -52,8 +52,7 @@ public void ParseNLogOptionsDefaultTest() var config = XmlLoggingConfiguration.CreateFromXmlString(xml); Assert.False(config.AutoReload); - Assert.True(config.InitializeSucceeded); - Assert.Equal("", InternalLogger.LogFile); + Assert.Null(InternalLogger.LogFile); Assert.False(InternalLogger.LogToConsole); Assert.False(InternalLogger.LogToConsoleError); Assert.True(InternalLogger.IncludeTimestamp); @@ -73,8 +72,7 @@ public void ParseNLogOptionsTest() var config = XmlLoggingConfiguration.CreateFromXmlString(xml); Assert.False(config.AutoReload); - Assert.True(config.InitializeSucceeded); - Assert.Equal("", InternalLogger.LogFile); + Assert.Null(InternalLogger.LogFile); Assert.True(InternalLogger.LogToConsole); Assert.True(InternalLogger.LogToConsoleError); Assert.False(InternalLogger.IncludeTimestamp); @@ -108,16 +106,13 @@ public void ParseNLogInternalLoggerPathTest() Assert.Contains(System.IO.Path.GetTempPath(), InternalLogger.LogFile); } -#if !NETSTANDARD1_3 using (new InternalLoggerScope()) { var xml = ""; var config = XmlLoggingConfiguration.CreateFromXmlString(xml); Assert.Contains(Path.GetDirectoryName(CurrentProcessPath), InternalLogger.LogFile); } -#endif -#if !NETSTANDARD1_3 && !NETSTANDARD1_5 using (new InternalLoggerScope()) { var xml = ""; @@ -138,7 +133,6 @@ public void ParseNLogInternalLoggerPathTest() var config = XmlLoggingConfiguration.CreateFromXmlString(xml); Assert.Contains(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), InternalLogger.LogFile); } -#endif using (new InternalLoggerScope()) { @@ -252,7 +246,7 @@ public void XmlConfig_ParseFilter_WithoutAttributes() // Arrange var xml = @" - + diff --git a/tests/NLog.UnitTests/ConfigFileLocatorTests.cs b/tests/NLog.UnitTests/ConfigFileLocatorTests.cs index 8f1c6737de..ec82ba0201 100644 --- a/tests/NLog.UnitTests/ConfigFileLocatorTests.cs +++ b/tests/NLog.UnitTests/ConfigFileLocatorTests.cs @@ -113,7 +113,7 @@ public void ResetCandidateConfigTest() public void GetConfigFile_absolutePath_loads(string filename, string accepts, string expected, string baseDir) { // Arrange - var appEnvMock = new AppEnvironmentMock(f => f == accepts, f => System.Xml.XmlReader.Create(new StringReader(@""))) { AppDomainBaseDirectory = baseDir }; + var appEnvMock = new AppEnvironmentMock(f => f == accepts, f => new StringReader(@"")) { AppDomainBaseDirectory = baseDir }; var fileLoader = new LoggingConfigurationFileLoader(appEnvMock); var logFactory = new LogFactory(fileLoader); @@ -121,7 +121,9 @@ public void GetConfigFile_absolutePath_loads(string filename, string accepts, st var result = fileLoader.Load(logFactory, filename); // Assert +#pragma warning disable CS0618 // Type or member is obsolete Assert.Equal(expected, result?.FileNamesToWatch.First()); +#pragma warning restore CS0618 // Type or member is obsolete } public static IEnumerable GetConfigFile_absolutePath_loads_testData() @@ -143,7 +145,7 @@ public static IEnumerable GetConfigFile_absolutePath_loads_testData() public void LoadConfigFile_EmptyEnvironment_UseCurrentDirectory() { // Arrange - var appEnvMock = new AppEnvironmentMock(f => true, f => null); + var appEnvMock = new AppEnvironmentMock(f => true); var fileLoader = new LoggingConfigurationFileLoader(appEnvMock); // Act @@ -161,13 +163,13 @@ public void LoadConfigFile_NetCoreUnpublished_UseEntryDirectory() { // Arrange var tmpDir = Path.GetTempPath(); - var appEnvMock = new AppEnvironmentMock(f => true, f => null) + var appEnvMock = new AppEnvironmentMock(f => true) { AppDomainBaseDirectory = Path.Combine(tmpDir, "BaseDir"), -#if NETSTANDARD - AppDomainConfigurationFile = string.Empty, // NetCore style -#else +#if NETFRAMEWORK AppDomainConfigurationFile = Path.Combine(tmpDir, "EntryDir", "Entry.exe.config"), +#else + AppDomainConfigurationFile = string.Empty, // NetCore style #endif CurrentProcessFilePath = Path.Combine(tmpDir, "ProcessDir", "dotnet.exe"), // NetCore dotnet.exe EntryAssemblyLocation = Path.Combine(tmpDir, "EntryDir"), @@ -188,13 +190,14 @@ public void LoadConfigFile_NetCorePublished_UseBaseDirectory() { // Arrange var tmpDir = Path.GetTempPath(); - var appEnvMock = new AppEnvironmentMock(f => true, f => null) + var appEnvMock = new AppEnvironmentMock(f => true) { AppDomainBaseDirectory = Path.Combine(tmpDir, "BaseDir"), -#if NETSTANDARD - AppDomainConfigurationFile = string.Empty, // .NET 6 single-publish-style -#else +#if NETFRAMEWORK AppDomainConfigurationFile = Path.Combine(tmpDir, "BaseDir", "Entry.exe.config"), +#else + AppDomainConfigurationFile = string.Empty, // .NET 6 single-publish-style + #endif CurrentProcessFilePath = Path.Combine(tmpDir, "ProcessDir", "Entry.exe"), EntryAssemblyLocation = string.Empty, // .NET 6 single-publish-style @@ -215,13 +218,13 @@ public void LoadConfigFile_NetCorePublished_UseProcessDirectory() { // Arrange var tmpDir = Path.GetTempPath(); - var appEnvMock = new AppEnvironmentMock(f => true, f => null) + var appEnvMock = new AppEnvironmentMock(f => true) { AppDomainBaseDirectory = Path.Combine(tmpDir, "BaseDir"), -#if NETSTANDARD - AppDomainConfigurationFile = string.Empty, // NetCore style -#else +#if NETFRAMEWORK AppDomainConfigurationFile = Path.Combine(tmpDir, "ProcessDir", "Entry.exe.config"), +#else + AppDomainConfigurationFile = string.Empty, // NetCore style #endif CurrentProcessFilePath = Path.Combine(tmpDir, "ProcessDir", "Entry.exe"), // NetCore published exe EntryAssemblyLocation = Path.Combine(tmpDir, "ProcessDir"), @@ -242,13 +245,13 @@ public void LoadConfigFile_NetCoreSingleFilePublish_IgnoreTempDirectory() { // Arrange var tmpDir = Path.GetTempPath(); - var appEnvMock = new AppEnvironmentMock(f => true, f => null) + var appEnvMock = new AppEnvironmentMock(f => true) { AppDomainBaseDirectory = Path.Combine(tmpDir, "BaseDir"), -#if NETSTANDARD - AppDomainConfigurationFile = string.Empty, // NetCore style -#else +#if NETFRAMEWORK AppDomainConfigurationFile = Path.Combine(tmpDir, "TempProcessDir", "Entry.exe.config"), +#else + AppDomainConfigurationFile = string.Empty, // NetCore style #endif CurrentProcessFilePath = Path.Combine(tmpDir, "ProcessDir", "Entry.exe"), // NetCore published exe EntryAssemblyLocation = Path.Combine(tmpDir, "TempProcessDir"), @@ -262,7 +265,7 @@ public void LoadConfigFile_NetCoreSingleFilePublish_IgnoreTempDirectory() var result = fileLoader.GetDefaultCandidateConfigFilePaths().ToList(); // Assert base-directory + process-directory + nlog-assembly-directory -#if NETSTANDARD +#if !NETFRAMEWORK Assert.Equal(Path.Combine(tmpDir, "ProcessDir", "Entry.exe.nlog"), result.First(), StringComparer.OrdinalIgnoreCase); #endif AssertResult(tmpDir, "TempProcessDir", "ProcessDir", "Entry", result); @@ -273,13 +276,13 @@ public void LoadConfigFile_NetCoreSingleFilePublish_IgnoreTmpDirectory() { // Arrange var tmpDir = "/var/tmp/"; - var appEnvMock = new AppEnvironmentMock(f => true, f => null) + var appEnvMock = new AppEnvironmentMock(f => true) { AppDomainBaseDirectory = Path.Combine(tmpDir, "BaseDir"), -#if NETSTANDARD - AppDomainConfigurationFile = string.Empty, // NetCore style -#else +#if NETFRAMEWORK AppDomainConfigurationFile = Path.Combine(tmpDir, "TempProcessDir", "Entry.exe.config"), +#else + AppDomainConfigurationFile = string.Empty, // NetCore style #endif CurrentProcessFilePath = Path.Combine(tmpDir, "ProcessDir", "Entry.exe"), // NetCore published exe EntryAssemblyLocation = Path.Combine(tmpDir, "TempProcessDir"), @@ -293,7 +296,7 @@ public void LoadConfigFile_NetCoreSingleFilePublish_IgnoreTmpDirectory() var result = fileLoader.GetDefaultCandidateConfigFilePaths().ToList(); // Assert base-directory + process-directory + nlog-assembly-directory -#if NETSTANDARD +#if !NETFRAMEWORK Assert.Equal(Path.Combine(tmpDir, "ProcessDir", "Entry.exe.nlog"), result.First(), StringComparer.OrdinalIgnoreCase); #endif AssertResult(tmpDir, "TempProcessDir", "ProcessDir", "Entry", result); @@ -314,7 +317,7 @@ public void ValueWithVariableMustNotCauseInfiniteRecursion() "; // Arrange - var appEnvMock = new AppEnvironmentMock(f => true, f => throw new NLogConfigurationException("Never allow loading config")); + var appEnvMock = new AppEnvironmentMock(f => true, f => throw new NLogConfigurationException("Never allow loading config file")); var fileLoader = new LoggingConfigurationFileLoader(appEnvMock); var logFactory = new LogFactory(fileLoader).Setup().LoadConfigurationFromXml(nlogConfigXml).LogFactory; @@ -349,7 +352,16 @@ public void CandidateConfigurationFileOnlyLoadedInitially() private static void AssertResult(string tmpDir, string appDir, string processDir, string appName, List result) { -#if NETSTANDARD +#if NETFRAMEWORK + Assert.Equal(Path.Combine(tmpDir, appDir, appName + ".exe.nlog"), result.First(), StringComparer.OrdinalIgnoreCase); + if (NLog.Internal.PlatformDetector.IsWin32) + { + if (processDir is null) + Assert.Equal(3, result.Count); // Case insensitive - No Assembly/Process-Directory + else + Assert.Equal(4, result.Count); // Case insensitive + } +#else Assert.Contains(Path.Combine(tmpDir, processDir ?? appDir, appName + ".exe.nlog"), result, StringComparer.OrdinalIgnoreCase); Assert.Contains(Path.Combine(tmpDir, appDir, "Entry.dll.nlog"), result, StringComparer.OrdinalIgnoreCase); if (NLog.Internal.PlatformDetector.IsWin32) @@ -365,22 +377,13 @@ private static void AssertResult(string tmpDir, string appDir, string processDir var priorityIndexExe = result.FindIndex(s => s.EndsWith(appName + ".exe.nlog")); var priorityIndexDll = result.FindIndex(s => s.EndsWith(appName + ".dll.nlog")); Assert.True(priorityIndexExe < priorityIndexDll, $"{appName + ".exe.nlog"}={priorityIndexExe} < {appName + ".dll.nlog"}={priorityIndexDll}"); // Always scan for exe.nlog first -#else - Assert.Equal(Path.Combine(tmpDir, appDir, appName + ".exe.nlog"), result.First(), StringComparer.OrdinalIgnoreCase); - if (NLog.Internal.PlatformDetector.IsWin32) - { - if (processDir is null) - Assert.Equal(3, result.Count); // Case insensitive - No Assembly/Process-Directory - else - Assert.Equal(4, result.Count); // Case insensitive - } #endif Assert.Contains(Path.Combine(tmpDir, "BaseDir", "NLog.config"), result, StringComparer.OrdinalIgnoreCase); Assert.Contains(Path.Combine(tmpDir, appDir, "NLog.config"), result, StringComparer.OrdinalIgnoreCase); Assert.Contains("NLog.dll.nlog", result.Last(), StringComparison.OrdinalIgnoreCase); } -#if !NETSTANDARD +#if NETFRAMEWORK private string appConfigContents = @" diff --git a/tests/NLog.UnitTests/Fluent/LogBuilderTests.cs b/tests/NLog.UnitTests/Fluent/LogBuilderTests.cs deleted file mode 100644 index 20bfda8296..0000000000 --- a/tests/NLog.UnitTests/Fluent/LogBuilderTests.cs +++ /dev/null @@ -1,582 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.UnitTests.Fluent -{ - using System; - using System.Collections.Generic; - using System.IO; - using NLog.Config; - using NLog.Fluent; - using NLog.Targets; - using Xunit; - - [Obsolete("Obsoleted since it allocates unnecessary. Instead use ILogger.ForLogEvent and LogEventBuilder. Obsoleted in NLog 5.0")] - public class LogBuilderTests : NLogTestBase - { - private static readonly Logger _logger = LogManager.GetLogger("logger1"); - - private LogEventInfo _lastLogEventInfo; - - public LogBuilderTests() - { - var configuration = new LoggingConfiguration(); - - var t1 = new MethodCallTarget("t1", (l, parms) => _lastLogEventInfo = l); - t1.Parameters.Add(new MethodCallParameter("CallSite", "${callsite}")); - var t2 = new DebugTarget { Name = "t2", Layout = "${message}" }; - configuration.AddTarget(t1); - configuration.AddTarget(t2); - configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, t1)); - configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, t2)); - - LogManager.Configuration = configuration; - } - - [Fact] - public void TraceWrite() - { - TraceWrite_internal(() => _logger.Trace()); - } - -#if !NET35 && !NET40 - [Fact] - public void TraceWrite_static_builder() - { - TraceWrite_internal(() => Log.Trace(), true); - } -#endif - - /// - /// func because 1 logbuilder creates 1 message - /// - /// Caution: don't use overloading, that will break xUnit: - /// CATASTROPHIC ERROR OCCURRED: - /// System.ArgumentException: Ambiguous method named TraceWrite in type NLog.UnitTests.Fluent.LogBuilderTests - /// - private void TraceWrite_internal(Func logBuilder, bool isStatic = false) - { - logBuilder() - .Message("This is a test fluent message.") - .Property("Test", "TraceWrite") - .Write(); - - - var loggerName = isStatic ? "LogBuilderTests" : "logger1"; - { - var expectedEvent = new LogEventInfo(LogLevel.Trace, loggerName, "This is a test fluent message."); - expectedEvent.Properties["Test"] = "TraceWrite"; - AssertLastLogEventTarget(expectedEvent); - } - - var ticks = DateTime.Now.Ticks; - logBuilder() - .Message("This is a test fluent message '{0}'.", ticks) - .Property("Test", "TraceWrite") - .Write(); - - { - var rendered = $"This is a test fluent message '{ticks}'."; - var expectedEvent = new LogEventInfo(LogLevel.Trace, loggerName, "This is a test fluent message '{0}'."); - expectedEvent.Properties["Test"] = "TraceWrite"; - AssertLastLogEventTarget(expectedEvent); - AssertDebugLastMessage("t2", rendered); - } - } - - [Fact] - public void TraceWriteProperties() - { - var props = new Dictionary - { - {"prop1", "1"}, - {"prop2", "2"}, - - }; - - _logger.Trace() - .Message("This is a test fluent message.") - .Properties(props).Write(); - - { - var expectedEvent = new LogEventInfo(LogLevel.Trace, "logger1", "This is a test fluent message."); - expectedEvent.Properties["prop1"] = "1"; - expectedEvent.Properties["prop2"] = "2"; - AssertLastLogEventTarget(expectedEvent); - } - } - - [Fact] - public void WarnWriteProperties() - { - var props = new Dictionary - { - {"prop1", "1"}, - {"prop2", "2"}, - - }; - - _logger.Warn() - .Message("This is a test fluent message.") - .Properties(props).Write(); - - { - var expectedEvent = new LogEventInfo(LogLevel.Warn, "logger1", "This is a test fluent message."); - expectedEvent.Properties["prop1"] = "1"; - expectedEvent.Properties["prop2"] = "2"; - AssertLastLogEventTarget(expectedEvent); - } - } - - [Fact] - public void LogWriteProperties() - { - var props = new Dictionary - { - {"prop1", "1"}, - {"prop2", "2"}, - }; - - // Loop to verify caller-attribute-caching-lookup - for (int i = 0; i < 2; ++i) - { - _logger.Log(LogLevel.Fatal) - .Message("This is a test fluent message.") - .Properties(props).Write(); - - var expectedEvent = new LogEventInfo(LogLevel.Fatal, "logger1", "This is a test fluent message."); - expectedEvent.Properties["prop1"] = "1"; - expectedEvent.Properties["prop2"] = "2"; - AssertLastLogEventTarget(expectedEvent); - -#if !NET35 && !NET40 - Assert.Equal(GetType().ToString(), _lastLogEventInfo.CallerClassName); -#endif - } - } - - [Fact] - public void LogOffWriteProperties() - { - var props = new Dictionary - { - {"prop1", "1"}, - {"prop2", "2"}, - - }; - var props2 = new Dictionary - { - {"prop1", "4"}, - {"prop2", "5"}, - - }; - - _logger.Log(LogLevel.Fatal) - .Message("This is a test fluent message.") - .Properties(props).Write(); - - _logger.Log(LogLevel.Off) - .Message("dont log this.") - .Properties(props2).Write(); - - { - var expectedEvent = new LogEventInfo(LogLevel.Fatal, "logger1", "This is a test fluent message."); - expectedEvent.Properties["prop1"] = "1"; - expectedEvent.Properties["prop2"] = "2"; - AssertLastLogEventTarget(expectedEvent); - } - } - -#if !NET35 && !NET40 - [Fact] - public void LevelWriteProperties() - { - var props = new Dictionary - { - {"prop1", "1"}, - {"prop2", "2"}, - - }; - - Log.Level(LogLevel.Fatal) - .Message("This is a test fluent message.") - .Properties(props).Write(); - - { - var expectedEvent = new LogEventInfo(LogLevel.Fatal, "LogBuilderTests", "This is a test fluent message."); - expectedEvent.Properties["prop1"] = "1"; - expectedEvent.Properties["prop2"] = "2"; - AssertLastLogEventTarget(expectedEvent); - } - } -#endif - - [Fact] - public void TraceIfWrite() - { - _logger.Trace() - .Message("This is a test fluent message.1") - .Property("Test", "TraceWrite") - .Write(); - - { - var expectedEvent = new LogEventInfo(LogLevel.Trace, "logger1", "This is a test fluent message.1"); - expectedEvent.Properties["Test"] = "TraceWrite"; - AssertLastLogEventTarget(expectedEvent); - } - - int v = 1; - _logger.Trace() - .Message("This is a test fluent WriteIf message '{0}'.", DateTime.Now.Ticks) - .Property("Test", "TraceWrite") - .WriteIf(() => v == 1); - - - { - var expectedEvent = new LogEventInfo(LogLevel.Trace, "logger1", "This is a test fluent WriteIf message '{0}'."); - expectedEvent.Properties["Test"] = "TraceWrite"; - AssertLastLogEventTarget(expectedEvent); - AssertDebugLastMessageContains("t2", "This is a test fluent WriteIf message "); - } - - _logger.Trace() - .Message("dont write this! '{0}'.", DateTime.Now.Ticks) - .Property("Test", "TraceWrite") - .WriteIf(() => { return false; }); - - { - var expectedEvent = new LogEventInfo(LogLevel.Trace, "logger1", "This is a test fluent WriteIf message '{0}'."); - expectedEvent.Properties["Test"] = "TraceWrite"; - AssertLastLogEventTarget(expectedEvent); - AssertDebugLastMessageContains("t2", "This is a test fluent WriteIf message "); - } - - _logger.Trace() - .Message("This is a test fluent WriteIf message '{0}'.", DateTime.Now.Ticks) - .Property("Test", "TraceWrite") - .WriteIf(v == 1); - - - { - var expectedEvent = new LogEventInfo(LogLevel.Trace, "logger1", "This is a test fluent WriteIf message '{0}'."); - expectedEvent.Properties["Test"] = "TraceWrite"; - AssertLastLogEventTarget(expectedEvent); - AssertDebugLastMessageContains("t2", "This is a test fluent WriteIf message "); - } - - _logger.Trace() - .Message("Should Not WriteIf message '{0}'.", DateTime.Now.Ticks) - .Property("Test", "TraceWrite") - .WriteIf(v > 1); - - - { - //previous - var expectedEvent = new LogEventInfo(LogLevel.Trace, "logger1", "This is a test fluent WriteIf message '{0}'."); - expectedEvent.Properties["Test"] = "TraceWrite"; - AssertLastLogEventTarget(expectedEvent); - AssertDebugLastMessageContains("t2", "This is a test fluent WriteIf message "); - } - } - - [Fact] - public void InfoWrite() - { - InfoWrite_internal(() => _logger.Info()); - } - -#if !NET35 && !NET40 - [Fact] - public void InfoWrite_static_builder() - { - InfoWrite_internal(() => Log.Info(), true); - } -#endif - - /// - /// func because 1 logbuilder creates 1 message - /// - /// Caution: don't use overloading, that will break xUnit: - /// CATASTROPHIC ERROR OCCURRED: - /// System.ArgumentException: Ambiguous method named TraceWrite in type NLog.UnitTests.Fluent.LogBuilderTests - /// - private void InfoWrite_internal(Func logBuilder, bool isStatic = false) - { - logBuilder() - .Message("This is a test fluent message.") - .Property("Test", "InfoWrite") - .Write(); - - var loggerName = isStatic ? "LogBuilderTests" : "logger1"; - { - //previous - var expectedEvent = new LogEventInfo(LogLevel.Info, loggerName, "This is a test fluent message."); - expectedEvent.Properties["Test"] = "InfoWrite"; - AssertLastLogEventTarget(expectedEvent); - } - - logBuilder() - .Message("This is a test fluent message '{0}'.", DateTime.Now.Ticks) - .Property("Test", "InfoWrite") - .Write(); - - { - //previous - var expectedEvent = new LogEventInfo(LogLevel.Info, loggerName, "This is a test fluent message '{0}'."); - expectedEvent.Properties["Test"] = "InfoWrite"; - AssertLastLogEventTarget(expectedEvent); - AssertDebugLastMessageContains("t2", "This is a test fluent message '"); - } - } - - [Fact] - public void DebugWrite() - { - ErrorWrite_internal(() => _logger.Debug(), LogLevel.Debug); - } - -#if !NET35 && !NET40 - [Fact] - public void DebugWrite_static_builder() - { - ErrorWrite_internal(() => Log.Debug(), LogLevel.Debug, true); - } -#endif - - [Fact] - public void FatalWrite() - { - ErrorWrite_internal(() => _logger.Fatal(), LogLevel.Fatal); - } - -#if !NET35 && !NET40 - [Fact] - public void FatalWrite_static_builder() - { - ErrorWrite_internal(() => Log.Fatal(), LogLevel.Fatal, true); - } -#endif - - [Fact] - public void ErrorWrite() - { - ErrorWrite_internal(() => _logger.Error(), LogLevel.Error); - } - -#if !NET35 && !NET40 - [Fact] - public void ErrorWrite_static_builder() - { - ErrorWrite_internal(() => Log.Error(), LogLevel.Error, true); - } -#endif - - [Fact] - public void LogBuilder_null_lead_to_ArgumentNullException() - { - var logger = LogManager.GetLogger("a"); - Assert.Throws(() => new LogBuilder(null, LogLevel.Debug)); - Assert.Throws(() => new LogBuilder(null)); - Assert.Throws(() => new LogBuilder(logger, null)); - - var logBuilder = new LogBuilder(logger); - Assert.Throws(() => logBuilder.Properties(null)); - Assert.Throws(() => logBuilder.Property(null, "b")); - } - - [Fact] - public void LogBuilder_nLogEventInfo() - { - var d = new DateTime(2015, 01, 30, 14, 30, 5); - var logEventInfo = new LogBuilder(LogManager.GetLogger("a")).LoggerName("b").Level(LogLevel.Fatal).TimeStamp(d).LogEventInfo; - - Assert.Equal("b", logEventInfo.LoggerName); - Assert.Equal(LogLevel.Fatal, logEventInfo.Level); - Assert.Equal(d, logEventInfo.TimeStamp); - } - - [Fact] - public void LogBuilder_exception_only() - { - var ex = new Exception("Exception message1"); - - _logger.Error() - .Exception(ex) - .Write(); - - var expectedEvent = new LogEventInfo(LogLevel.Error, "logger1", null) { Exception = ex }; - AssertLastLogEventTarget(expectedEvent); - } - - [Fact] - public void LogBuilder_null_logLevel() - { - Assert.Throws(() => _logger.Error().Level(null)); - } - - [Fact] - public void LogBuilder_message_overloadsTest() - { - LogManager.ThrowExceptions = true; - - _logger.Debug() - .Message("Message with {0} arg", 1) - .Write(); - AssertDebugLastMessage("t2", "Message with 1 arg"); - - _logger.Debug() - .Message("Message with {0} args. {1}", 2, "YES") - .Write(); - AssertDebugLastMessage("t2", "Message with 2 args. YES"); - - _logger.Debug() - .Message("Message with {0} args. {1} {2}", 3, ":) ", 2) - .Write(); - AssertDebugLastMessage("t2", "Message with 3 args. :) 2"); - - _logger.Debug() - .Message("Message with {0} args. {1} {2}{3}", "more", ":) ", 2, "b") - .Write(); - AssertDebugLastMessage("t2", "Message with more args. :) 2b"); - } - - [Fact] - public void LogBuilder_message_cultureTest() - { - if (IsLinux()) - { - Console.WriteLine("[SKIP] LogBuilderTests.LogBuilder_message_cultureTest because we are running in Travis"); - return; - } - - LogManager.Configuration.DefaultCultureInfo = GetCultureInfo("en-US"); - - _logger.Debug() - .Message("Message with {0} {1} {2} {3}", 4.1, 4.001, new DateTime(2016, 12, 31), true) - .Write(); - AssertDebugLastMessage("t2", "Message with 4.1 4.001 12/31/2016 12:00:00 AM True"); - - _logger.Debug() - .Message(GetCultureInfo("nl-nl"), "Message with {0} {1} {2} {3}", 4.1, 4.001, new DateTime(2016, 12, 31), true) - .Write(); - AssertDebugLastMessage("t2", "Message with 4,1 4,001 31-12-2016 00:00:00 True"); - } - - [Fact] - public void LogBuilder_Structured_Logging_Test() - { - var logEvent = _logger.Info().Property("Property1Key", "Property1Value").Message("{@message}", "My custom message").LogEventInfo; - Assert.NotEmpty(logEvent.Properties); - Assert.Contains("message", logEvent.Properties.Keys); - Assert.Contains("Property1Key", logEvent.Properties.Keys); - } - - /// - /// func because 1 logbuilder creates 1 message - /// - /// Caution: don't use overloading, that will break xUnit: - /// CATASTROPHIC ERROR OCCURRED: - /// System.ArgumentException: Ambiguous method named TraceWrite in type NLog.UnitTests.Fluent.LogBuilderTests - /// - private void ErrorWrite_internal(Func logBuilder, LogLevel logLevel, bool isStatic = false) - { - Exception catchedException = null; - string path = "blah.txt"; - - try - { - string text = File.ReadAllText(path); - } - catch (Exception ex) - { - catchedException = ex; - logBuilder() - .Message("Error reading file '{0}'.", path) - .Exception(ex) - .Property("Test", "ErrorWrite") - .Write(); - } - - var loggerName = isStatic ? "LogBuilderTests" : "logger1"; - { - var expectedEvent = new LogEventInfo(logLevel, loggerName, "Error reading file '{0}'."); - expectedEvent.Properties["Test"] = "ErrorWrite"; - expectedEvent.Exception = catchedException; - AssertLastLogEventTarget(expectedEvent); - AssertDebugLastMessageContains("t2", "Error reading file '"); - } - logBuilder() - .Message("This is a test fluent message.") - .Property("Test", "ErrorWrite") - .Write(); - - { - var expectedEvent = new LogEventInfo(logLevel, loggerName, "This is a test fluent message."); - expectedEvent.Properties["Test"] = "ErrorWrite"; - AssertLastLogEventTarget(expectedEvent); - } - - logBuilder() - .Message("This is a test fluent message '{0}'.", DateTime.Now.Ticks) - .Property("Test", "ErrorWrite") - .Write(); - - { - var expectedEvent = new LogEventInfo(logLevel, loggerName, "This is a test fluent message '{0}'."); - expectedEvent.Properties["Test"] = "ErrorWrite"; - AssertLastLogEventTarget(expectedEvent); - AssertDebugLastMessageContains("t2", "This is a test fluent message '"); - } - } - - /// - /// Test the written logevent - /// - /// exptected event to be logged. - void AssertLastLogEventTarget(LogEventInfo expected) - { - Assert.NotNull(_lastLogEventInfo); - Assert.Equal(expected.Message, _lastLogEventInfo.Message); - - Assert.NotNull(_lastLogEventInfo.Properties); - Assert.Equal(expected.Properties.Count, _lastLogEventInfo.Properties.Count); -#if !MONO - Assert.Equal(expected.Properties, _lastLogEventInfo.Properties); -#endif - Assert.Equal(expected.LoggerName, _lastLogEventInfo.LoggerName); - Assert.Equal(expected.Level, _lastLogEventInfo.Level); - Assert.Equal(expected.Exception, _lastLogEventInfo.Exception); - Assert.Equal(expected.FormatProvider, _lastLogEventInfo.FormatProvider); - } - } -} diff --git a/tests/NLog.UnitTests/Fluent/LogEventBuilderTests.cs b/tests/NLog.UnitTests/Fluent/LogEventBuilderTests.cs index d08617e7ee..2efbfbf6a1 100644 --- a/tests/NLog.UnitTests/Fluent/LogEventBuilderTests.cs +++ b/tests/NLog.UnitTests/Fluent/LogEventBuilderTests.cs @@ -311,7 +311,7 @@ public void LogBuilder_null_lead_to_ArgumentNullException() Assert.Throws(() => new LogEventBuilder(logger, null)); var logBuilder = new LogEventBuilder(logger); - Assert.Throws(() => logBuilder.Properties(null)); + Assert.Throws(() => logBuilder.Properties((KeyValuePair[])null)); Assert.Throws(() => logBuilder.Property(null, "b")); } diff --git a/tests/NLog.UnitTests/GetLoggerTests.cs b/tests/NLog.UnitTests/GetLoggerTests.cs index 1cbb2910c4..6131a8ba9e 100644 --- a/tests/NLog.UnitTests/GetLoggerTests.cs +++ b/tests/NLog.UnitTests/GetLoggerTests.cs @@ -41,14 +41,14 @@ public class GetLoggerTests : NLogTestBase [Fact] public void GetCurrentClassLoggerTest() { - var logger = LogManager.GetCurrentClassLogger(); + var logger = new LogFactory().GetCurrentClassLogger(); Assert.Equal("NLog.UnitTests.GetLoggerTests", logger.Name); } [Fact] public void GetCurrentClassLoggerLambdaTest() { - System.Linq.Expressions.Expression> sum = () => LogManager.GetCurrentClassLogger(); + System.Linq.Expressions.Expression> sum = () => new LogFactory().GetCurrentClassLogger(); var logger = sum.Compile().Invoke(); Assert.Equal("NLog.UnitTests.GetLoggerTests", logger.Name); } @@ -133,14 +133,13 @@ private InvalidLogger() } } - [Fact] [Obsolete("Replaced by GetCurrentClassLogger(). Marked obsolete on NLog 5.2")] public void InvalidLoggerConfiguration_NotThrowsThrowExceptions_NotThrows() { using (new NoThrowNLogExceptions()) { - var result = LogManager.GetCurrentClassLogger(typeof(InvalidLogger)); + var result = new LogFactory().GetCurrentClassLogger(typeof(InvalidLogger)); Assert.NotNull(result); } } @@ -152,7 +151,7 @@ public void InvalidLoggerConfiguration_ThrowsThrowExceptions_Throws() LogManager.ThrowExceptions = true; Assert.Throws(() => { - LogManager.GetCurrentClassLogger(typeof(InvalidLogger)); + new LogFactory().GetCurrentClassLogger(typeof(InvalidLogger)); }); } diff --git a/tests/NLog.UnitTests/Internal/AppDomainPartialTrustTests.cs b/tests/NLog.UnitTests/Internal/AppDomainPartialTrustTests.cs index e382002738..e9c730810b 100644 --- a/tests/NLog.UnitTests/Internal/AppDomainPartialTrustTests.cs +++ b/tests/NLog.UnitTests/Internal/AppDomainPartialTrustTests.cs @@ -31,7 +31,7 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD && !MONO +#if NETFRAMEWORK && !MONO using System; using System.IO; @@ -141,7 +141,7 @@ public void PartialTrustSuccess(int times, string fileWritePath, bool autoShutdo // NOTE Using BufferingWrapper to validate that DomainUnload remembers to perform flush var configXml = $@" - + @@ -157,10 +157,8 @@ public void PartialTrustSuccess(int times, string fileWritePath, bool autoShutdo { LogManager.Configuration = XmlLoggingConfiguration.CreateFromXmlString(configXml); -#pragma warning disable CS0618 // Type or member is obsolete //this method gave issues LogFactory.LogNLogAssemblyVersion(); -#pragma warning restore CS0618 // Type or member is obsolete var logger = LogManager.GetLogger("NLog.UnitTests.Targets.FileTargetTests"); diff --git a/tests/NLog.UnitTests/Internal/ExceptionHelperTests.cs b/tests/NLog.UnitTests/Internal/ExceptionHelperTests.cs index 72da5a2703..4ffef6f4ed 100644 --- a/tests/NLog.UnitTests/Internal/ExceptionHelperTests.cs +++ b/tests/NLog.UnitTests/Internal/ExceptionHelperTests.cs @@ -44,10 +44,8 @@ namespace NLog.UnitTests.Internal public class ExceptionHelperTests : NLogTestBase { [Theory] -#if !NETSTANDARD1_5 [InlineData(typeof(StackOverflowException), true)] [InlineData(typeof(ThreadAbortException), true)] -#endif [InlineData(typeof(NLogConfigurationException), false)] [InlineData(typeof(Exception), false)] [InlineData(typeof(ArgumentException), false)] @@ -62,12 +60,10 @@ public void TestMustBeRethrowImmediately(Type t, bool result) } [Theory] -#if !NETSTANDARD1_5 [InlineData(typeof(StackOverflowException), true, false, false)] [InlineData(typeof(StackOverflowException), true, true, false)] [InlineData(typeof(ThreadAbortException), true, false, false)] [InlineData(typeof(ThreadAbortException), true, true, false)] -#endif [InlineData(typeof(NLogConfigurationException), true, true, true)] [InlineData(typeof(NLogConfigurationException), false, true, false)] [InlineData(typeof(NLogConfigurationException), true, true, null)] diff --git a/tests/NLog.UnitTests/Internal/FileInfoHelperTests.cs b/tests/NLog.UnitTests/Internal/FileInfoHelperTests.cs new file mode 100644 index 0000000000..629f2cc5cd --- /dev/null +++ b/tests/NLog.UnitTests/Internal/FileInfoHelperTests.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.UnitTests.Internal +{ + using NLog.Internal; + using Xunit; + + public class FileInfoHelperTests // Not needed as not using NLog-Core -> : NLogTestBase + { + [Theory] + [InlineData(@"", false)] + [InlineData(@" ", false)] + [InlineData(null, false)] + [InlineData(@"/ test\a", false)] + [InlineData(@"test.log", true)] + [InlineData(@"test", true)] + [InlineData(@" test.log ", true)] + [InlineData(@" a/test.log ", true)] + [InlineData(@".test.log ", true)] + [InlineData(@"..test.log ", true)] + [InlineData(@" .. test.log ", true)] + [InlineData(@"dir$/test ", true)] + public void DetectFilePathKind(string path, bool expected) + { + var result = FileInfoHelper.IsRelativeFilePath(path); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(@"d:\test.log", false)] + [InlineData(@"d:\test", false)] + [InlineData(@" d:\test", false)] + [InlineData(@" d:\ test", false)] + [InlineData(@" d:\ test\a", false)] + [InlineData(@"\\test\a", false)] + [InlineData(@"\\test/a", false)] + [InlineData(@"\ test\a", false)] + [InlineData(@" a\test.log ", true)] + public void DetectFilePathKindWindowsPath(string path, bool expected) + { + if (System.IO.Path.DirectorySeparatorChar != '\\') + return; //no backward-slash on linux + + var result = FileInfoHelper.IsRelativeFilePath(path); + Assert.Equal(expected, result); + } + } +} diff --git a/tests/NLog.UnitTests/Internal/PlatformDetectorTests.cs b/tests/NLog.UnitTests/Internal/PlatformDetectorTests.cs index 189cbf3e05..3c2a9a7ff0 100644 --- a/tests/NLog.UnitTests/Internal/PlatformDetectorTests.cs +++ b/tests/NLog.UnitTests/Internal/PlatformDetectorTests.cs @@ -38,7 +38,7 @@ namespace NLog.UnitTests.Internal { public class PlatformDetectorTests { -#if !NETSTANDARD +#if NETFRAMEWORK [Fact] public void IsMonoTest() { diff --git a/tests/NLog.UnitTests/Internal/SimpleStringReaderTests.cs b/tests/NLog.UnitTests/Internal/SimpleStringReaderTests.cs index 13f9858105..d8455b3db0 100644 --- a/tests/NLog.UnitTests/Internal/SimpleStringReaderTests.cs +++ b/tests/NLog.UnitTests/Internal/SimpleStringReaderTests.cs @@ -33,11 +33,11 @@ namespace NLog.UnitTests.Internal { +#if DEBUG using System; using NLog.Internal; using Xunit; -#if DEBUG public class SimpleStringReaderTests : NLogTestBase { [Theory] diff --git a/tests/NLog.UnitTests/Internal/SortHelpersTests.cs b/tests/NLog.UnitTests/Internal/SortHelpersTests.cs index 88239c6a35..ae0db17e2e 100644 --- a/tests/NLog.UnitTests/Internal/SortHelpersTests.cs +++ b/tests/NLog.UnitTests/Internal/SortHelpersTests.cs @@ -44,7 +44,7 @@ public class SortHelpersTests [Fact] public void SingleBucketDictionary_NoBucketTest() { - SortHelpers.ReadOnlySingleBucketDictionary> dict = new SortHelpers.ReadOnlySingleBucketDictionary>(); + SortHelpers.ReadOnlySingleBucketGroupBy> dict = new SortHelpers.ReadOnlySingleBucketGroupBy>(); Assert.Empty(dict); Assert.Empty(dict); @@ -52,43 +52,19 @@ public void SingleBucketDictionary_NoBucketTest() foreach (var _ in dict) Assert.False(true); - - Assert.Empty(dict.Keys); - foreach (var _ in dict.Keys) - Assert.False(true); - - Assert.Empty(dict.Values); - foreach (var _ in dict.Values) - Assert.False(true); - - IList bucket; - Assert.False(dict.TryGetValue("Bucket1", out bucket) || bucket != null); - Assert.False(dict.TryGetValue(string.Empty, out bucket) || bucket != null); - Assert.False(dict.TryGetValue(null, out bucket) || bucket != null); - - Assert.Throws(() => dict[string.Empty] = ArrayHelper.Empty()); } [Fact] public void SingleBucketDictionary_OneBucketEmptyTest() { IList bucket = ArrayHelper.Empty(); - SortHelpers.ReadOnlySingleBucketDictionary> dict = new SortHelpers.ReadOnlySingleBucketDictionary>(new KeyValuePair>("Bucket1", bucket)); + SortHelpers.ReadOnlySingleBucketGroupBy> dict = new SortHelpers.ReadOnlySingleBucketGroupBy>(new KeyValuePair>("Bucket1", bucket)); Assert.Single(dict); Assert.Contains(new KeyValuePair>("Bucket1", bucket), dict); Assert.DoesNotContain(new KeyValuePair>("Bucket1", null), dict); Assert.DoesNotContain(new KeyValuePair>(string.Empty, bucket), dict); - Assert.True(dict.ContainsKey("Bucket1")); - Assert.False(dict.ContainsKey(string.Empty)); - - KeyValuePair>[] copyToResult = new KeyValuePair>[10]; - dict.CopyTo(copyToResult, 0); - Assert.Equal("Bucket1", copyToResult[0].Key); - Assert.Empty(copyToResult[0].Value); - - Assert.Single(dict); Assert.Equal(1, dict.Count(val => val.Key == "Bucket1")); foreach (var item in dict) @@ -96,47 +72,19 @@ public void SingleBucketDictionary_OneBucketEmptyTest() Assert.Equal("Bucket1", item.Key); Assert.Empty(item.Value); } - - Assert.Single(dict.Keys); - foreach (var key in dict.Keys) - { - Assert.Equal("Bucket1", key); - } - - Assert.Single(dict.Values); - foreach (var val in dict.Values) - { - Assert.Empty(val); - } - - Assert.Empty(dict["Bucket1"]); - - Assert.True(dict.TryGetValue("Bucket1", out bucket) && bucket.Count == 0); - Assert.False(dict.TryGetValue(string.Empty, out bucket) || bucket != null); - Assert.False(dict.TryGetValue(null, out bucket) || bucket != null); - Assert.Throws(() => dict[string.Empty] = ArrayHelper.Empty()); } [Fact] public void SingleBucketDictionary_OneBucketOneItem() { IList bucket = new string[] { "Bucket1Item1" }; - SortHelpers.ReadOnlySingleBucketDictionary> dict = new SortHelpers.ReadOnlySingleBucketDictionary>(new KeyValuePair>("Bucket1", bucket)); + SortHelpers.ReadOnlySingleBucketGroupBy> dict = new SortHelpers.ReadOnlySingleBucketGroupBy>(new KeyValuePair>("Bucket1", bucket)); Assert.Single(dict); Assert.Contains(new KeyValuePair>("Bucket1", bucket), dict); Assert.DoesNotContain(new KeyValuePair>("Bucket1", null), dict); Assert.DoesNotContain(new KeyValuePair>(string.Empty, bucket), dict); - Assert.True(dict.ContainsKey("Bucket1")); - Assert.False(dict.ContainsKey(string.Empty)); - - KeyValuePair>[] copyToResult = new KeyValuePair>[10]; - dict.CopyTo(copyToResult, 0); - Assert.Equal("Bucket1", copyToResult[0].Key); - Assert.Single(copyToResult[0].Value); - Assert.Equal("Bucket1Item1", copyToResult[0].Value[0]); - Assert.Single(dict); Assert.Equal(1, dict.Count(val => val.Key == "Bucket1")); @@ -146,48 +94,19 @@ public void SingleBucketDictionary_OneBucketOneItem() Assert.Single(item.Value); Assert.Equal("Bucket1Item1", item.Value[0]); } - - Assert.Single(dict.Keys); - foreach (var key in dict.Keys) - { - Assert.Equal("Bucket1", key); - } - - Assert.Single(dict.Values); - foreach (var val in dict.Values) - { - Assert.Single(val); - Assert.Equal("Bucket1Item1", val[0]); - } - - Assert.Single(dict["Bucket1"]); - Assert.True(dict.TryGetValue("Bucket1", out bucket) && bucket.Count == 1); - Assert.False(dict.TryGetValue(string.Empty, out bucket) || bucket != null); - Assert.False(dict.TryGetValue(null, out bucket) || bucket != null); - Assert.Throws(() => dict[string.Empty] = ArrayHelper.Empty()); } [Fact] public void SingleBucketDictionary_OneBucketTwoItemsTest() { IList bucket = new string[] { "Bucket1Item1", "Bucket1Item2" }; - SortHelpers.ReadOnlySingleBucketDictionary> dict = new SortHelpers.ReadOnlySingleBucketDictionary>(new KeyValuePair>("Bucket1", bucket)); + SortHelpers.ReadOnlySingleBucketGroupBy> dict = new SortHelpers.ReadOnlySingleBucketGroupBy>(new KeyValuePair>("Bucket1", bucket)); Assert.Single(dict); Assert.Contains(new KeyValuePair>("Bucket1", bucket), dict); Assert.DoesNotContain(new KeyValuePair>("Bucket1", null), dict); Assert.DoesNotContain(new KeyValuePair>(string.Empty, bucket), dict); - Assert.True(dict.ContainsKey("Bucket1")); - Assert.False(dict.ContainsKey(string.Empty)); - - KeyValuePair>[] copyToResult = new KeyValuePair>[10]; - dict.CopyTo(copyToResult, 0); - Assert.Equal("Bucket1", copyToResult[0].Key); - Assert.Equal(2, copyToResult[0].Value.Count); - Assert.Equal("Bucket1Item1", copyToResult[0].Value[0]); - Assert.Equal("Bucket1Item2", copyToResult[0].Value[1]); - Assert.Single(dict); Assert.Equal(1, dict.Count(val => val.Key == "Bucket1")); @@ -198,26 +117,6 @@ public void SingleBucketDictionary_OneBucketTwoItemsTest() Assert.Equal("Bucket1Item1", item.Value[0]); Assert.Equal("Bucket1Item2", item.Value[1]); } - - Assert.Single(dict.Keys); - foreach (var key in dict.Keys) - { - Assert.Equal("Bucket1", key); - } - - Assert.Single(dict.Values); - foreach (var val in dict.Values) - { - Assert.Equal(2, val.Count); - Assert.Equal("Bucket1Item1", val[0]); - Assert.Equal("Bucket1Item2", val[1]); - } - - Assert.Equal(2, dict["Bucket1"].Count); - Assert.True(dict.TryGetValue("Bucket1", out bucket) && bucket.Count == 2); - Assert.False(dict.TryGetValue(string.Empty, out bucket) || bucket != null); - Assert.False(dict.TryGetValue(null, out bucket) || bucket != null); - Assert.Throws(() => dict[string.Empty] = ArrayHelper.Empty()); } [Fact] @@ -229,7 +128,7 @@ public void SingleBucketDictionary_TwoBucketEmptyTest() buckets["Bucket1"] = bucket1; buckets["Bucket2"] = bucket2; - SortHelpers.ReadOnlySingleBucketDictionary> dict = new SortHelpers.ReadOnlySingleBucketDictionary>(buckets); + SortHelpers.ReadOnlySingleBucketGroupBy> dict = new SortHelpers.ReadOnlySingleBucketGroupBy>(buckets); Assert.Equal(2, dict.Count); Assert.Contains(new KeyValuePair>("Bucket1", bucket1), dict); @@ -240,17 +139,6 @@ public void SingleBucketDictionary_TwoBucketEmptyTest() Assert.DoesNotContain(new KeyValuePair>("Bucket2", null), dict); Assert.DoesNotContain(new KeyValuePair>(string.Empty, bucket2), dict); - Assert.True(dict.ContainsKey("Bucket1")); - Assert.True(dict.ContainsKey("Bucket2")); - Assert.False(dict.ContainsKey(string.Empty)); - - KeyValuePair>[] copyToResult = new KeyValuePair>[10]; - dict.CopyTo(copyToResult, 0); - Assert.Equal("Bucket1", copyToResult[0].Key); - Assert.Equal("Bucket2", copyToResult[1].Key); - Assert.Empty(copyToResult[0].Value); - Assert.Empty(copyToResult[1].Value); - Assert.Equal(2, dict.Count()); Assert.Equal(1, dict.Count(val => val.Key == "Bucket1")); Assert.Equal(1, dict.Count(val => val.Key == "Bucket2")); @@ -260,24 +148,6 @@ public void SingleBucketDictionary_TwoBucketEmptyTest() Assert.True(item.Key == "Bucket1" || item.Key == "Bucket2"); Assert.Empty(item.Value); } - - Assert.Equal(2, dict.Keys.Count); - foreach (var key in dict.Keys) - { - Assert.True(key == "Bucket1" || key == "Bucket2"); - } - - Assert.Equal(2, dict.Values.Count); - foreach (var val in dict.Values) - { - Assert.Empty(val); - } - - Assert.Empty(dict["Bucket1"]); - Assert.Empty(dict["Bucket2"]); - Assert.True(dict.TryGetValue("Bucket1", out bucket1) && bucket1.Count == 0); - Assert.True(dict.TryGetValue("Bucket2", out bucket2) && bucket2.Count == 0); - Assert.Throws(() => dict[string.Empty] = ArrayHelper.Empty()); } [Fact] @@ -289,7 +159,7 @@ public void SingleBucketDictionary_TwoBuckettOneItemTest() buckets["Bucket1"] = bucket1; buckets["Bucket2"] = bucket2; - SortHelpers.ReadOnlySingleBucketDictionary> dict = new SortHelpers.ReadOnlySingleBucketDictionary>(buckets); + SortHelpers.ReadOnlySingleBucketGroupBy> dict = new SortHelpers.ReadOnlySingleBucketGroupBy>(buckets); Assert.Equal(2, dict.Count); Assert.Contains(new KeyValuePair>("Bucket1", bucket1), dict); @@ -300,17 +170,6 @@ public void SingleBucketDictionary_TwoBuckettOneItemTest() Assert.DoesNotContain(new KeyValuePair>("Bucket2", null), dict); Assert.DoesNotContain(new KeyValuePair>(string.Empty, bucket2), dict); - Assert.True(dict.ContainsKey("Bucket1")); - Assert.True(dict.ContainsKey("Bucket2")); - Assert.False(dict.ContainsKey(string.Empty)); - - KeyValuePair>[] copyToResult = new KeyValuePair>[10]; - dict.CopyTo(copyToResult, 0); - Assert.Equal("Bucket1", copyToResult[0].Key); - Assert.Equal("Bucket2", copyToResult[1].Key); - Assert.Single(copyToResult[0].Value); - Assert.Single(copyToResult[1].Value); - Assert.Equal(2, dict.Count()); Assert.Equal(1, dict.Count(val => val.Key == "Bucket1")); Assert.Equal(1, dict.Count(val => val.Key == "Bucket2")); @@ -321,25 +180,6 @@ public void SingleBucketDictionary_TwoBuckettOneItemTest() Assert.Single(item.Value); Assert.Equal("Bucket1Item1", item.Value[0]); } - - Assert.Equal(2, dict.Keys.Count); - foreach (var key in dict.Keys) - { - Assert.True(key == "Bucket1" || key == "Bucket2"); - } - - Assert.Equal(2, dict.Values.Count); - foreach (var val in dict.Values) - { - Assert.Single(val); - Assert.Equal("Bucket1Item1", val[0]); - } - - Assert.Single(dict["Bucket1"]); - Assert.Single(dict["Bucket2"]); - Assert.True(dict.TryGetValue("Bucket1", out bucket1) && bucket1.Count == 1); - Assert.True(dict.TryGetValue("Bucket2", out bucket2) && bucket2.Count == 1); - Assert.Throws(() => dict[string.Empty] = ArrayHelper.Empty()); } } } diff --git a/tests/NLog.UnitTests/Internal/XmlParserTests.cs b/tests/NLog.UnitTests/Internal/XmlParserTests.cs new file mode 100644 index 0000000000..b01e8b1c35 --- /dev/null +++ b/tests/NLog.UnitTests/Internal/XmlParserTests.cs @@ -0,0 +1,344 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.UnitTests.Internal +{ + using System.Linq; + using NLog.Config; + using NLog.Internal; + using Xunit; + + public class XmlParserTests + { + [Fact] + public void XmlConvertIsXmlCharTest() + { + for (char ch = '\0'; ch < char.MaxValue; ++ch) + { + var expected = System.Xml.XmlConvert.IsXmlChar(ch); + var actual = XmlHelper.XmlConvertIsXmlChar(ch); + if (expected != actual) + Assert.True(expected == actual, $"{ch} ({(int)ch})"); + } + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData("\n")] + [InlineData("\t")] + [InlineData(">")] + [InlineData("<")] + [InlineData(" >")] + [InlineData(" <")] + [InlineData(" > ")] + [InlineData(" < ")] + [InlineData(" <> ")] + [InlineData(" < > ")] + [InlineData(" ")] + [InlineData(" < /> ")] + [InlineData(" <\n> ")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(" \n ")] + [InlineData(" ")] + [InlineData(" \n ")] + [InlineData(" ")] + [InlineData(" \n ")] + [InlineData("n")] + [InlineData(" n")] + [InlineData(" n ")] + [InlineData("nlog")] + [InlineData("")] + [InlineData("\n")] + [InlineData("")] + [InlineData("\n")] + [InlineData("")] + [InlineData("\n")] + [InlineData("")] + [InlineData("\n")] + [InlineData("")] + [InlineData("\nnl")] + [InlineData("\nnl>g\n")] + [InlineData("")] + [InlineData("\n")] + [InlineData("")] + [InlineData("\n")] + [InlineData("")] + [InlineData("\n\n")] + [InlineData("")] + [InlineData("\n\n")] + [InlineData("")] + [InlineData("\n\n")] + [InlineData("")] + [InlineData("\n\n")] + [InlineData(" nlog")] + [InlineData("\n\nnlog")] + [InlineData("nlog")] + [InlineData("")] + [InlineData(">")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("Exceptions='true' />")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData(" Z;")] + [InlineData("")] + [InlineData("Z;")] + [InlineData("")] + [InlineData("&quop;")] + [InlineData(""")] + [InlineData("")] + [InlineData("")] + public void XmlParse_InvalidDocument(string xmlSource) + { + Assert.Throws(() => new XmlParser(xmlSource).LoadDocument(out var _)); + } + + [Theory] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData("\n\n\n\n")] + [InlineData("\n\n\n\n")] + [InlineData("")] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData("\n\n\n\n")] + [InlineData("\n\n\n\n")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData("\n\n")] + [InlineData("\n\n")] + [InlineData("")] + [InlineData("\n")] + [InlineData("")] + [InlineData("\n\n")] + [InlineData("\n\n\n")] + [InlineData("")] + [InlineData("\n")] + [InlineData("")] + [InlineData("")] + public void XmlParse_EmptyDocument(string xmlSource) + { + var xmlDocument = new XmlParser(xmlSource).LoadDocument(out var _); + Assert.NotNull(xmlDocument); + Assert.Equal("nlog", xmlDocument.Name.ToLower()); + Assert.Empty(xmlDocument.Children); + Assert.Empty(xmlDocument.Attributes); + } + + [Theory] + [InlineData(@"")] + [InlineData(@"")] + [InlineData("")] + [InlineData(@"")] + [InlineData(@"")] + [InlineData("")] + [InlineData(@"")] + [InlineData(@"")] + [InlineData(@"")] + [InlineData("")] + [InlineData(@"")] + [InlineData("")] + [InlineData("")] + public void XmlParse_Attributes(string xmlSource) + { + var xmlDocument = new XmlParser(xmlSource).LoadDocument(out var _); + Assert.NotNull(xmlDocument); + Assert.Equal("nlog", xmlDocument.Name); + Assert.Empty(xmlDocument.Children); + Assert.Single(xmlDocument.Attributes); + Assert.Equal("throwExceptions", xmlDocument.Attributes[0].Key); + Assert.Equal("false", xmlDocument.Attributes[0].Value); + } + + [Theory] + [InlineData(@"")] + [InlineData(@"")] + [InlineData(@"")] + [InlineData(@"")] + public void XmlParse_Attributes_Multiple(string xmlSource) + { + var xmlDocument = new XmlParser(xmlSource).LoadDocument(out var _); + Assert.NotNull(xmlDocument); + Assert.Equal("nlog", xmlDocument.Name); + Assert.Empty(xmlDocument.Children); + Assert.Equal(2, xmlDocument.Attributes.Count); + Assert.Equal("throwExceptions", xmlDocument.Attributes[0].Key); + Assert.Equal("false", xmlDocument.Attributes[0].Value); + Assert.Equal("internalLogLevel", xmlDocument.Attributes[1].Key); + Assert.Equal("Debug", xmlDocument.Attributes[1].Value); + } + + [Theory] + [InlineData("", "")] + [InlineData("", ".\\logfile.txt")] + [InlineData("", "C:\\logfile.txt")] + [InlineData("", "./logfile.txt")] + [InlineData("", "./%HOSTNAME%.txt")] + [InlineData("", "http://example.com")] + [InlineData("", " < > ? \" & ")] + [InlineData("", " < > ? \" &")] + [InlineData("", " < > ? \" & ")] + [InlineData("", " < > ? \" &")] + [InlineData("", " < > ? \" & ")] + [InlineData("", " < > ? \" &")] + [InlineData("", " < > ? \" & ")] + [InlineData("", " < > ? \" &")] + public void XmlParse_Attributes_Tokens(string xmlSource, string value) + { + var xmlDocument = new XmlParser(xmlSource).LoadDocument(out var _); + Assert.NotNull(xmlDocument); + Assert.Equal("nlog", xmlDocument.Name); + Assert.Empty(xmlDocument.Children); + Assert.Single(xmlDocument.Attributes); + Assert.Equal("internalLogFile", xmlDocument.Attributes[0].Key); + Assert.Equal(value, xmlDocument.Attributes[0].Value); + } + + [Theory] + [InlineData("", null)] + [InlineData(" ", null)] + [InlineData("\n", null)] + [InlineData("\n\n", null)] + [InlineData("< > ? " &", "< > ? \" &")] + [InlineData(" < > ? " & ", "< > ? \" &")] + [InlineData("\n< > ? " &\n", "< > ? \" &")] + [InlineData("< > ? " &", "< > ? \" &")] + [InlineData(" < > ? " & ", "< > ? \" &")] + [InlineData("\n< > ? " &\n", "< > ? \" &")] + [InlineData("< > ? " &", "< > ? \" &")] + [InlineData(" < > ? " & ", "< > ? \" &")] + [InlineData("\n< > ? " &\n", "< > ? \" &")] + [InlineData("", "\n\n")] + [InlineData(" ", "\n\n")] + [InlineData("\n\n", "\n\n")] + [InlineData("\n]]>\n", "")] + [InlineData("\n\n]]>\n", "")] + [InlineData("A]]>", "")] + [InlineData("]]]]>", "")] + public void XmlParse_InnerText_Tokens(string xmlSource, string value) + { + var xmlDocument = new XmlParser(xmlSource).LoadDocument(out var _); + Assert.NotNull(xmlDocument); + Assert.Equal("nlog", xmlDocument.Name); + Assert.Empty(xmlDocument.Children); + Assert.Empty(xmlDocument.Attributes); + Assert.Equal(value, xmlDocument.InnerText); + } + + [Theory] + [InlineData("")] + [InlineData("\n\n")] + [InlineData("\n\n")] + [InlineData("\n\n\n")] + [InlineData("\n= LogLevel.Warn'/>\n")] + [InlineData("abc${message}")] + [InlineData("\n\nabc\n${message}\n\n")] + [InlineData("\n\nabc\nlevel >= LogLevel.Warn\n\n")] + public void XmlParse_Children(string xmlSource) + { + var xmlDocument = new XmlParser(xmlSource).LoadDocument(out var _); + Assert.NotNull(xmlDocument); + Assert.Equal("nlog", xmlDocument.Name); + Assert.Single(xmlDocument.Children); + Assert.Equal("variable", xmlDocument.Children[0].Name); + } + + [Theory] + [InlineData("")] + [InlineData("\n\n\n")] + [InlineData("${message}")] + [InlineData("\n\n\n${message}\n\n")] + [InlineData("abc${message}123${message}")] + [InlineData("\n\nabc\n${message}\n\n\n123\n${message}\n\n")] + public void XmlParse_Children_Multiple(string xmlSource) + { + var xmlDocument = new XmlParser(xmlSource).LoadDocument(out var _); + Assert.NotNull(xmlDocument); + Assert.Equal("nlog", xmlDocument.Name); + Assert.Equal(2, xmlDocument.Children.Count); + Assert.Equal("variable", xmlDocument.Children[0].Name); + Assert.Equal("variable", xmlDocument.Children[1].Name); + } + + [Fact] + public void XmlParse_DeeplyNestedXml_DoesNotThrowStackOverflowException() + { + string deeplyNestedXml = "" + string.Join("", Enumerable.Repeat("", 10000).ToArray()) + string.Join("", Enumerable.Repeat("", 10000).ToArray()) + ""; + var xmlDocument = new XmlParser(deeplyNestedXml).LoadDocument(out var _); + Assert.NotNull(xmlDocument); + Assert.Equal("root", xmlDocument.Name); + Assert.Single(xmlDocument.Children); + } + } +} diff --git a/tests/NLog.UnitTests/LayoutRenderers/AllEventPropertiesTests.cs b/tests/NLog.UnitTests/LayoutRenderers/AllEventPropertiesTests.cs index 8b879e405e..82cb748bf8 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/AllEventPropertiesTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/AllEventPropertiesTests.cs @@ -110,7 +110,7 @@ public void StructuredLoggingProperties() var ev = new LogEventInfo(LogLevel.Info, null, null, "Hello Planet {@planet}", new object[] { planetProperties }); var result = renderer.Render(ev); - Assert.Equal(@"planet=""Name""=""Earth"", ""PlanetType""=""Water-world""", result); + Assert.Equal(@"planet=Name=Earth, PlanetType=Water-world", result); } [Fact] @@ -127,7 +127,6 @@ public void IncludeScopeProperties() using (ScopeContext.PushProperty("Figure", "Circle")) { var logger = logFactory.GetLogger("B"); - logger.Debug(ev); } diff --git a/tests/NLog.UnitTests/LayoutRenderers/AppSettingTests.cs b/tests/NLog.UnitTests/LayoutRenderers/AppSettingTests.cs index 4ef3d2ee28..cece4c1f61 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/AppSettingTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/AppSettingTests.cs @@ -31,7 +31,7 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !NETSTANDARD +#if NETFRAMEWORK namespace NLog.UnitTests.LayoutRenderers { diff --git a/tests/NLog.UnitTests/LayoutRenderers/AssemblyVersionTests.cs b/tests/NLog.UnitTests/LayoutRenderers/AssemblyVersionTests.cs index 94b11b499f..3f555025f8 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/AssemblyVersionTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/AssemblyVersionTests.cs @@ -44,7 +44,7 @@ public class AssemblyVersionTests : NLogTestBase { private readonly ITestOutputHelper _testOutputHelper; -#if !NETSTANDARD +#if NETFRAMEWORK private static Lazy TestAssembly = new Lazy(() => GenerateTestAssembly()); #endif @@ -109,7 +109,7 @@ public void AssemblyVersionFormatTest(string format, string expected) AssertLayoutRendererOutput($"${{assembly-version:name=NLogAutoLoadExtension:format={format}}}", expected); } -#if !NETSTANDARD +#if NETFRAMEWORK private const string AssemblyVersionTest = "1.2.3.4"; private const string AssemblyFileVersionTest = "1.1.1.2"; private const string AssemblyInformationalVersionTest = "Version 1"; diff --git a/tests/NLog.UnitTests/LayoutRenderers/BaseDirTests.cs b/tests/NLog.UnitTests/LayoutRenderers/BaseDirTests.cs index 9c11383f6c..2b09d74fb1 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/BaseDirTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/BaseDirTests.cs @@ -41,7 +41,7 @@ namespace NLog.UnitTests.LayoutRenderers public class BaseDirTests : NLogTestBase { - private string baseDir = AppDomain.CurrentDomain.BaseDirectory; + private readonly string baseDir = AppDomain.CurrentDomain.BaseDirectory; [Fact] public void BaseDirTest() @@ -85,30 +85,15 @@ public void BaseDirDirFileCombineTest() } [Fact] - [Obsolete("For unit testing only. Marked obsolete on NLog 5.0")] public void InjectBaseDirAndCheckConfigPathsTest() { string fakeBaseDir = @"y:\root\"; - var old = LogFactory.CurrentAppDomain; - try - { - var currentAppDomain = new Mocks.AppDomainMock(fakeBaseDir); - LogFactory.CurrentAppDomain = currentAppDomain; - - //test 1 - AssertLayoutRendererOutput("${basedir}", fakeBaseDir); - - //test 2 - var paths = LogManager.LogFactory.GetCandidateConfigFilePaths().ToList(); - var count = paths.Count(p => p.StartsWith(fakeBaseDir)); - - Assert.True(count > 0, $"At least one path should start with '{fakeBaseDir}'"); - } - finally - { - //restore - LogFactory.CurrentAppDomain = old; - } + var appEnvironment = new Mocks.AppEnvironmentMock(null); + appEnvironment.AppDomainBaseDirectory = fakeBaseDir; + var baseLayoutRenderer = new NLog.LayoutRenderers.BaseDirLayoutRenderer(appEnvironment); + + // test1 + Assert.Equal(fakeBaseDir, baseLayoutRenderer.Render(LogEventInfo.CreateNullEvent())); } [Fact] @@ -117,18 +102,29 @@ public void BaseDir_FixTempDir_ChoosesProcessDir() var tempDir = System.IO.Path.GetTempPath(); var processPath = CurrentProcessPath; - var appEnvironment = new Mocks.AppEnvironmentMock(null, null); + var appEnvironment = new Mocks.AppEnvironmentMock(null); appEnvironment.AppDomainBaseDirectory = tempDir; appEnvironment.UserTempFilePath = tempDir; appEnvironment.CurrentProcessFilePath = processPath; - var baseLayoutRenderer = new NLog.LayoutRenderers.BaseDirLayoutRenderer(appEnvironment); // test1 - Assert.Equal(tempDir, baseLayoutRenderer.Render(LogEventInfo.CreateNullEvent())); + var baseLayoutRenderer1 = new NLog.LayoutRenderers.BaseDirLayoutRenderer(appEnvironment); + Assert.Equal(tempDir, baseLayoutRenderer1.Render(LogEventInfo.CreateNullEvent())); // test2 - baseLayoutRenderer.FixTempDir = true; - Assert.Equal(Path.GetDirectoryName(processPath), baseLayoutRenderer.Render(LogEventInfo.CreateNullEvent())); + var baseLayoutRenderer2 = new NLog.LayoutRenderers.BaseDirLayoutRenderer(appEnvironment); + baseLayoutRenderer2.FixTempDir = true; + Assert.Equal(Path.GetDirectoryName(processPath), baseLayoutRenderer2.Render(LogEventInfo.CreateNullEvent())); + } + + [Fact] + public void BaseDir_FixFilePathForNet9_WhenLongUNC() + { + var appEnvironment = new Mocks.AppEnvironmentMock(null); + appEnvironment.AppDomainBaseDirectory = @"\\?\UNC\major\tom\groundcontrol\bin\Development\net9.0\NLog.config"; + var baseLayoutRenderer = new NLog.LayoutRenderers.BaseDirLayoutRenderer(appEnvironment); + + Assert.Equal(@"\\major\tom\groundcontrol\bin\Development\net9.0\NLog.config", baseLayoutRenderer.Render(LogEventInfo.CreateNullEvent())); } } } diff --git a/tests/NLog.UnitTests/LayoutRenderers/CallSiteFileNameLayoutTests.cs b/tests/NLog.UnitTests/LayoutRenderers/CallSiteFileNameLayoutTests.cs index 914e8f183b..5bb7fe3d1a 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/CallSiteFileNameLayoutTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/CallSiteFileNameLayoutTests.cs @@ -40,10 +40,10 @@ namespace NLog.UnitTests.LayoutRenderers public class CallSiteFileNameLayoutTests : NLogTestBase { -#if !MONO - [Fact] -#else +#if MONO [Fact(Skip = "MONO is not good with callsite line numbers")] +#else + [Fact] #endif public void ShowFileNameOnlyTest() { @@ -64,10 +64,10 @@ public void ShowFileNameOnlyTest() Assert.Equal("msg", lastMessageArray[1]); } -#if !MONO - [Fact] -#else +#if MONO [Fact(Skip = "MONO is not good with callsite line numbers")] +#else + [Fact] #endif public void CallSiteFileNameNoCaptureStackTraceTest() { @@ -87,10 +87,10 @@ public void CallSiteFileNameNoCaptureStackTraceTest() logFactory.AssertDebugLastMessage("|msg"); } -#if !MONO - [Fact] -#else +#if MONO [Fact(Skip = "MONO is not good with callsite line numbers")] +#else + [Fact] #endif public void CallSiteFileNameNoCaptureStackTraceWithStackTraceTest() { @@ -105,7 +105,7 @@ public void CallSiteFileNameNoCaptureStackTraceWithStackTraceTest() // Act var logEvent = new LogEventInfo(LogLevel.Info, null, "msg"); - logEvent.SetStackTrace(new System.Diagnostics.StackTrace(true), 0); + logEvent.SetStackTrace(new System.Diagnostics.StackTrace(true)); logFactory.GetLogger("A").Log(logEvent); // Assert @@ -115,10 +115,10 @@ public void CallSiteFileNameNoCaptureStackTraceWithStackTraceTest() Assert.Equal("msg", lastMessageArray[1]); } -#if !MONO - [Fact] -#else +#if MONO [Fact(Skip = "MONO is not good with callsite line numbers")] +#else + [Fact] #endif public void ShowFullPathTest() { @@ -141,10 +141,10 @@ public void ShowFullPathTest() Assert.Equal("msg", lastMessageArray[1]); } -#if !MONO - [Fact] -#else +#if MONO [Fact(Skip = "MONO is not good with callsite line numbers")] +#else + [Fact] #endif public void ShowFileNameOnlyAsyncTest() { @@ -164,10 +164,10 @@ public void ShowFileNameOnlyAsyncTest() Assert.Equal("msg", lastMessageArray[1]); } -#if !MONO - [Fact] -#else +#if MONO [Fact(Skip = "MONO is not good with callsite line numbers")] +#else + [Fact] #endif public void ShowFullPathAsyncTest() { diff --git a/tests/NLog.UnitTests/LayoutRenderers/CallSiteLineNumberTests.cs b/tests/NLog.UnitTests/LayoutRenderers/CallSiteLineNumberTests.cs index ea29af019e..4cee18920f 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/CallSiteLineNumberTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/CallSiteLineNumberTests.cs @@ -33,7 +33,6 @@ using System; using System.Threading.Tasks; -using NLog.Config; using Xunit; namespace NLog.UnitTests.LayoutRenderers @@ -71,10 +70,11 @@ public void LineNumberOnlyTest() #endif } -#if !MONO - [Fact] -#else + +#if MONO [Fact(Skip = "MONO is not good with callsite line numbers")] +#else + [Fact] #endif public void LineNumberOnlyAsyncTest() { @@ -127,7 +127,7 @@ public void LineNumberNoCaptureStackTraceWithStackTraceTest() // Act var logEvent = new LogEventInfo(LogLevel.Info, null, "msg"); - logEvent.SetStackTrace(new System.Diagnostics.StackTrace(true), 0); + logEvent.SetStackTrace(new System.Diagnostics.StackTrace(true)); logFactory.GetLogger("A").Log(logEvent); // Assert diff --git a/tests/NLog.UnitTests/LayoutRenderers/CallSiteTests.cs b/tests/NLog.UnitTests/LayoutRenderers/CallSiteTests.cs index 16dda54973..c95673422a 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/CallSiteTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/CallSiteTests.cs @@ -60,7 +60,7 @@ public static void LogDebug(this ILogger logger) public class CallSiteTests : NLogTestBase { -#if !NETSTANDARD +#if NETFRAMEWORK [Fact] public void HiddenAssemblyTest() { @@ -253,7 +253,7 @@ public void MethodNameNoCaptureStackTraceWithStackTraceTest() // Act var logEvent = new LogEventInfo(LogLevel.Info, null, "msg"); - logEvent.SetStackTrace(new System.Diagnostics.StackTrace(true), 0); + logEvent.SetStackTrace(new System.Diagnostics.StackTrace(true)); logFactory.GetLogger("A").Log(logEvent); // Assert @@ -893,11 +893,10 @@ public void CallSiteShouldWorkForAsyncMethodsWithReturnValue() public async Task GetAsyncCallSite() { var logEvent = new LogEventInfo(LogLevel.Error, "logger1", "message1"); -#if !NETSTANDARD1_5 Type loggerType = typeof(Logger); var stacktrace = new System.Diagnostics.StackTrace(); logEvent.GetCallSiteInformationInternal().SetStackTrace(stacktrace, null, loggerType); -#endif + await Task.Delay(0); Layout l = "${callsite}"; var callSite = l.Render(logEvent); @@ -1142,7 +1141,6 @@ public NLogLogger Create(string name) } } -#if !NETSTANDARD1_5 /// /// If some calls got inlined, we can't find LoggerType anymore. We should fallback if loggerType can be found /// @@ -1163,7 +1161,6 @@ public void CallSiteShouldWorkEvenInlined() var callSite = l.Render(logEvent); Assert.Equal("NLog.UnitTests.LayoutRenderers.CallSiteTests.CallSiteShouldWorkEvenInlined", callSite); } -#endif #if NET35 [Fact(Skip = "NET35 not supporting async callstack")] diff --git a/tests/NLog.UnitTests/LayoutRenderers/CounterTests.cs b/tests/NLog.UnitTests/LayoutRenderers/CounterTests.cs index 73101266d9..d8330e2c87 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/CounterTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/CounterTests.cs @@ -100,13 +100,13 @@ public void PresetCounterTest() var logger = LogManager.GetLogger("A"); logger.Debug("a"); logger.Info("a"); - AssertDebugLastMessage("debug", "a 1 1"); + AssertDebugLastMessage("debug", "a 4 1"); logger.Warn("a"); - AssertDebugLastMessage("debug", "a 4 2"); + AssertDebugLastMessage("debug", "a 7 2"); logger.Error("a"); - AssertDebugLastMessage("debug", "a 7 3"); + AssertDebugLastMessage("debug", "a 10 3"); logger.Fatal("a"); - AssertDebugLastMessage("debug", "a 10 4"); + AssertDebugLastMessage("debug", "a 13 4"); } [Fact] diff --git a/tests/NLog.UnitTests/LayoutRenderers/ExceptionDataLayoutRendererTests.cs b/tests/NLog.UnitTests/LayoutRenderers/ExceptionDataLayoutRendererTests.cs index c3ae3e6fad..6582b95545 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/ExceptionDataLayoutRendererTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/ExceptionDataLayoutRendererTests.cs @@ -130,50 +130,6 @@ public void BadDataForItemResultsInEmptyValueTest() logFactory.AssertDebugLastMessage(""); } - [Fact] - public void NoDatkeyTest() - { - const string exceptionMessage = "I don't like nullref exception!"; - const string exceptionDataKey = "testkey"; - const string exceptionDataValue = "testvalue"; - var logFactory = new LogFactory().Setup().LoadConfigurationFromXml(@" - - - - - - - - ").LogFactory; - - Exception ex = new ArgumentException(exceptionMessage); - ex.Data.Add(exceptionDataKey, exceptionDataValue); - logFactory.GetCurrentClassLogger().Error(ex); - logFactory.AssertDebugLastMessage(""); - } - - [Fact] - public void NoDatkeyUingParamTest() - { - const string exceptionMessage = "I don't like nullref exception!"; - const string exceptionDataKey = "testkey"; - const string exceptionDataValue = "testvalue"; - var logFactory = new LogFactory().Setup().LoadConfigurationFromXml(@" - - - - - - - - ").LogFactory; - - Exception ex = new ArgumentException(exceptionMessage); - ex.Data.Add(exceptionDataKey, exceptionDataValue); - logFactory.GetCurrentClassLogger().Error(ex); - logFactory.AssertDebugLastMessage(""); - } - [Fact] public void BaseExceptionFlippedTest() { diff --git a/tests/NLog.UnitTests/LayoutRenderers/ExceptionTests.cs b/tests/NLog.UnitTests/LayoutRenderers/ExceptionTests.cs index 1e3bc0f69e..f0d9b5e72d 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/ExceptionTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/ExceptionTests.cs @@ -35,6 +35,7 @@ namespace NLog.UnitTests.LayoutRenderers { using System; using System.Collections.Generic; + using System.Linq; using NLog.Config; using NLog.Internal; using NLog.LayoutRenderers; @@ -346,13 +347,13 @@ public void InnerExceptionTest() Exception ex = GetNestedExceptionWithStackTrace(exceptionMessage); logFactory.GetCurrentClassLogger().Error(ex, "msg"); - logFactory.AssertDebugLastMessage("ApplicationException Wrapper2" + EnvironmentHelper.NewLine + - "ArgumentException Wrapper1" + EnvironmentHelper.NewLine + + logFactory.AssertDebugLastMessage("ApplicationException Wrapper2" + Environment.NewLine + + "ArgumentException Wrapper1" + Environment.NewLine + "CustomArgumentException Test exception"); } [Fact] - public void InnerExceptionTest_Serialize() + public void InnerExceptionTest_JsonSerialize() { var logFactory = new LogFactory().Setup().LoadConfigurationFromXml(@" @@ -852,15 +853,15 @@ public void ExcpetionTestAPI() Exception ex = GetNestedExceptionWithStackTrace(exceptionMessage); logFactory.GetCurrentClassLogger().Error(ex, "msg"); - logFactory.AssertDebugLastMessage("ApplicationException Wrapper2" + EnvironmentHelper.NewLine + - "ArgumentException Wrapper1" + EnvironmentHelper.NewLine + + logFactory.AssertDebugLastMessage("ApplicationException Wrapper2" + Environment.NewLine + + "ArgumentException Wrapper1" + Environment.NewLine + "CustomArgumentException Test exception"); var t = (DebugTarget)logFactory.Configuration.AllTargets[0]; var elr = ((SimpleLayout)t.Layout).Renderers[0] as ExceptionLayoutRenderer; - Assert.Equal(ExceptionRenderingFormat.ShortType, elr.Formats[0]); - Assert.Equal(ExceptionRenderingFormat.Message, elr.Formats[1]); + Assert.Equal(ExceptionRenderingFormat.ShortType, elr.Formats.First()); + Assert.Equal(ExceptionRenderingFormat.Message, elr.Formats.Last()); } [Fact] @@ -880,17 +881,9 @@ public void InnerExceptionTestAPI() Exception ex = GetNestedExceptionWithStackTrace(exceptionMessage); logFactory.GetCurrentClassLogger().Error(ex, "msg"); - logFactory.AssertDebugLastMessage("ApplicationException Wrapper2" + EnvironmentHelper.NewLine + - "Wrapper1" + EnvironmentHelper.NewLine + + logFactory.AssertDebugLastMessage("ApplicationException Wrapper2" + Environment.NewLine + + "Wrapper1" + Environment.NewLine + "Test exception"); - - var t = (DebugTarget)logFactory.Configuration.AllTargets[0]; - var elr = ((SimpleLayout)t.Layout).Renderers[0] as ExceptionLayoutRenderer; - - Assert.Equal(ExceptionRenderingFormat.ShortType, elr.Formats[0]); - Assert.Equal(ExceptionRenderingFormat.Message, elr.Formats[1]); - - Assert.Equal(ExceptionRenderingFormat.Message, elr.InnerFormats[0]); } [Fact] diff --git a/tests/NLog.UnitTests/LayoutRenderers/HostNameLayoutRendererTests.cs b/tests/NLog.UnitTests/LayoutRenderers/HostNameLayoutRendererTests.cs index 6675638a87..53a1fbbea1 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/HostNameLayoutRendererTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/HostNameLayoutRendererTests.cs @@ -51,8 +51,9 @@ public void HostNameTest() // Get the actual hostname that the code would use string h = Environment.GetEnvironmentVariable("HOSTNAME") - ?? System.Net.Dns.GetHostName() - ?? Environment.GetEnvironmentVariable("COMPUTERNAME"); + ?? Environment.GetEnvironmentVariable("COMPUTERNAME") + ?? Environment.GetEnvironmentVariable("MACHINENAME") + ?? Environment.MachineName; logFactory.GetLogger("A").Debug("a log message"); logFactory.AssertDebugLastMessage(h + " a log message"); } diff --git a/tests/NLog.UnitTests/LayoutRenderers/Log4JXmlTests.cs b/tests/NLog.UnitTests/LayoutRenderers/Log4JXmlTests.cs deleted file mode 100644 index c8aec1fa7e..0000000000 --- a/tests/NLog.UnitTests/LayoutRenderers/Log4JXmlTests.cs +++ /dev/null @@ -1,330 +0,0 @@ -// -// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * Neither the name of Jaroslaw Kowalski nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. -// - -namespace NLog.UnitTests.LayoutRenderers -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Reflection; - using System.Xml; - using NLog.Internal; - using NLog.Layouts; - using NLog.Targets; - using Xunit; - - public class Log4JXmlTests : NLogTestBase - { - [Fact] - public void Log4JXmlTest() - { - var logFactory = new LogFactory().Setup().LoadConfigurationFromXml(@" - - - - - - - - ").LogFactory; - - ScopeContext.Clear(); - - ScopeContext.PushProperty("foo1", "bar1"); - ScopeContext.PushProperty("foo2", "bar2"); - ScopeContext.PushProperty("foo3", "bar3"); - - ScopeContext.PushNestedState("baz1"); - ScopeContext.PushNestedState("baz2"); - ScopeContext.PushNestedState("baz3"); - - var logger = logFactory.GetLogger("A"); - var logEventInfo = LogEventInfo.Create(LogLevel.Debug, "A", new Exception("Hello Exception", new Exception("Goodbye Exception")), null, "some message \u0014"); - logEventInfo.Properties["nlogPropertyKey"] = "nlogPropertyValue"; - logger.Log(logEventInfo); - string result = GetDebugLastMessage("debug", logFactory); - Assert.DoesNotContain("dummy", result); - - string wrappedResult = "" + result + ""; - - Assert.NotEqual("", result); - // make sure the XML can be read back and verify some fields - StringReader stringReader = new StringReader(wrappedResult); - - var foundsChilds = new Dictionary(); - - var requiredChilds = new List - { - "log4j.event", - "log4j.message", - "log4j.NDC", - "log4j.locationInfo", - "nlog.locationInfo", - "log4j.properties", - "nlog.properties", - "log4j.throwable", - "log4j.data", - "nlog.data", - }; - - using (XmlReader reader = XmlReader.Create(stringReader)) - { - - while (reader.Read()) - { - var key = reader.LocalName; - var fullKey = reader.Prefix + "." + key; - if (!foundsChilds.ContainsKey(fullKey)) - { - foundsChilds[fullKey] = 0; - } - foundsChilds[fullKey]++; - - if (reader.NodeType == XmlNodeType.Element && reader.Prefix == "log4j") - { - switch (reader.LocalName) - { - case "dummyRoot": - break; - - case "event": - Assert.Equal("DEBUG", reader.GetAttribute("level")); - Assert.Equal("A", reader.GetAttribute("logger")); - - var epochStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - long timestamp = Convert.ToInt64(reader.GetAttribute("timestamp")); - var time = epochStart.AddMilliseconds(timestamp); - var now = DateTime.UtcNow; - Assert.True(now.Ticks - time.Ticks < TimeSpan.FromSeconds(3).Ticks); - - Assert.Equal(CurrentManagedThreadId.ToString(), reader.GetAttribute("thread")); - break; - - case "message": - reader.Read(); - Assert.Equal("some message ", reader.Value); - break; - - case "NDC": - reader.Read(); - Assert.Equal("baz1::baz2::baz3", reader.Value); - break; - - case "locationInfo": - Assert.Equal(MethodBase.GetCurrentMethod().DeclaringType.FullName, reader.GetAttribute("class")); - Assert.Equal(MethodBase.GetCurrentMethod().ToString(), reader.GetAttribute("method")); - break; - - case "properties": - break; - - case "throwable": - reader.Read(); - Assert.Contains("Hello Exception", reader.Value); - Assert.Contains("Goodbye Exception", reader.Value); - break; - case "data": - string name = reader.GetAttribute("name"); - string value = reader.GetAttribute("value"); - - switch (name) - { - case "log4japp": - Assert.Equal(AppDomain.CurrentDomain.FriendlyName + "(" + CurrentProcessId + ")", value); - break; - - case "log4jmachinename": - Assert.Equal(Environment.MachineName, value); - break; - - case "foo1": - Assert.Equal("bar1", value); - break; - - case "foo2": - Assert.Equal("bar2", value); - break; - - case "foo3": - Assert.Equal("bar3", value); - break; - - case "nlogPropertyKey": - Assert.Equal("nlogPropertyValue", value); - break; - - default: - Assert.Fail("Unknown : " + name); - break; - } - break; - - default: - throw new NotSupportedException("Unknown element: " + key); - } - continue; - } - - if (reader.NodeType == XmlNodeType.Element && reader.Prefix == "nlog") - { - switch (key) - { - case "eventSequenceNumber": - break; - - case "locationInfo": - Assert.Equal(GetType().Assembly.FullName, reader.GetAttribute("assembly")); - break; - - case "properties": - break; - - case "data": - var name = reader.GetAttribute("name"); - var value = reader.GetAttribute("value"); - Assert.Equal("nlogPropertyKey", name); - Assert.Equal("nlogPropertyValue", value); - break; - - default: - throw new NotSupportedException("Unknown element: " + key); - } - } - } - } - - foreach (var required in requiredChilds) - { - Assert.True(foundsChilds.ContainsKey(required), $"{required} not found!"); - } - } - - [Fact] - public void Log4JXmlEventLayoutParameterTest() - { - var log4jLayout = new Log4JXmlEventLayout() - { - Parameters = - { - new NLogViewerParameterInfo - { - Name = "mt", - Layout = "${message:raw=true}", - } - }, - }; - log4jLayout.Renderer.AppInfo = "MyApp"; - var logEventInfo = new LogEventInfo - { - LoggerName = "MyLOgger", - TimeStamp = new DateTime(2010, 01, 01, 12, 34, 56, DateTimeKind.Utc), - Level = LogLevel.Info, - Message = "hello, <{0}>", - Parameters = new[] { "world" }, - }; - - var threadid = CurrentManagedThreadId; - var machinename = Environment.MachineName; - Assert.Equal($"hello, <world>", log4jLayout.Render(logEventInfo)); - } - - [Fact] - public void BadXmlValueTest() - { - var sb = new System.Text.StringBuilder(); - - var forbidden = new HashSet(); - int start = 64976; int end = 65007; - - for (int i = start; i <= end; i++) - { - forbidden.Add(i); - } - - forbidden.Add(0xFFFE); - forbidden.Add(0xFFFF); - - for (int i = char.MinValue; i <= char.MaxValue; i++) - { - char c = Convert.ToChar(i); - if (char.IsSurrogate(c)) - { - continue; // skip surrogates - } - - if (forbidden.Contains(c)) - { - continue; - } - - sb.Append(c); - } - - var badString = sb.ToString(); - - var settings = new XmlWriterSettings - { - Indent = true, - ConformanceLevel = ConformanceLevel.Fragment, - IndentChars = " ", - }; - - sb.Length = 0; - using (XmlWriter xtw = XmlWriter.Create(sb, settings)) - { - xtw.WriteStartElement("log4j", "event", "http:://hello/"); - xtw.WriteElementSafeString("log4j", "message", "http:://hello/", badString); - xtw.WriteEndElement(); - xtw.Flush(); - } - - string goodString = null; - using (XmlReader reader = XmlReader.Create(new StringReader(sb.ToString()))) - { - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Text) - { - if (reader.Value.Contains("abc")) - goodString = reader.Value; - } - } - } - - Assert.NotNull(goodString); - Assert.NotEqual(badString.Length, goodString.Length); - Assert.Contains("abc", badString); - Assert.Contains("abc", goodString); - } - } -} diff --git a/tests/NLog.UnitTests/LayoutRenderers/MdlcLayoutRendererTests.cs b/tests/NLog.UnitTests/LayoutRenderers/MdlcLayoutRendererTests.cs index 7a4426f481..6fd4079054 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/MdlcLayoutRendererTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/MdlcLayoutRendererTests.cs @@ -54,8 +54,7 @@ public MdlcLayoutRendererTests() "; - var element = XElement.Parse(configXml); - var config = new XmlLoggingConfiguration(element.CreateReader(), null); + var config = XmlLoggingConfiguration.CreateFromXmlString(configXml); LogManager.Configuration = config; _target = LogManager.Configuration.FindTargetByName("debug") as DebugTarget; diff --git a/tests/NLog.UnitTests/LayoutRenderers/ProcessTimeLayoutRendererTests.cs b/tests/NLog.UnitTests/LayoutRenderers/ProcessTimeLayoutRendererTests.cs index b34dd7cde0..68ae0fe10e 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/ProcessTimeLayoutRendererTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/ProcessTimeLayoutRendererTests.cs @@ -64,7 +64,7 @@ public void RenderTimeSpanTest(int day, int hour, int min, int sec, int milisec, [Fact] public void RenderProcessTimeLayoutRenderer() { - var layout = "${processtime}"; + var layout = "${processtime:Culture=InvariantCulture}"; var timestamp = LogEventInfo.ZeroDate; System.Threading.Thread.Sleep(16); var logEvent = new LogEventInfo(LogLevel.Debug, "logger1", "message1"); diff --git a/tests/NLog.UnitTests/LayoutRenderers/ScopeNestedTests.cs b/tests/NLog.UnitTests/LayoutRenderers/ScopeNestedTests.cs index 0c2ead34fe..66ecfda3db 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/ScopeNestedTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/ScopeNestedTests.cs @@ -395,7 +395,7 @@ public void ScopeNestedTimingTest() measurements = messageFirstScopeSleep.Split(new[] { '|' }, System.StringSplitOptions.RemoveEmptyEntries); Assert.Equal(7, measurements.Length); Assert.Equal("ala", measurements[0]); - Assert.InRange(double.Parse(measurements[1], System.Globalization.CultureInfo.InvariantCulture), 10, 999); + Assert.InRange(double.Parse(measurements[1], System.Globalization.CultureInfo.InvariantCulture), 9, 999); Assert.InRange(int.Parse(measurements[3]), 10, 999); Assert.InRange(int.Parse(measurements[5]), 100000, 9999999); Assert.Equal("b", measurements[measurements.Length - 1]); @@ -403,7 +403,7 @@ public void ScopeNestedTimingTest() measurements = messageSecondScope.Split(new[] { '|' }, System.StringSplitOptions.RemoveEmptyEntries); Assert.Equal(7, measurements.Length); Assert.Equal("ala ma", measurements[0]); - Assert.InRange(double.Parse(measurements[1], System.Globalization.CultureInfo.InvariantCulture), 10, 999); + Assert.InRange(double.Parse(measurements[1], System.Globalization.CultureInfo.InvariantCulture), 9, 999); Assert.InRange(int.Parse(measurements[3]), 10, 999); Assert.InRange(int.Parse(measurements[5]), 0, 9999999); Assert.Equal("a", measurements[measurements.Length - 1]); @@ -411,7 +411,7 @@ public void ScopeNestedTimingTest() measurements = messageSecondScopeSleep.Split(new[] { '|' }, System.StringSplitOptions.RemoveEmptyEntries); Assert.Equal(7, measurements.Length); Assert.Equal("ala ma", measurements[0]); - Assert.InRange(double.Parse(measurements[1], System.Globalization.CultureInfo.InvariantCulture), 20, 999); + Assert.InRange(double.Parse(measurements[1], System.Globalization.CultureInfo.InvariantCulture), 19, 999); Assert.InRange(int.Parse(measurements[3]), 20, 999); Assert.InRange(int.Parse(measurements[5]), 100000, 9999999); Assert.Equal("b", measurements[measurements.Length - 1]); @@ -419,7 +419,7 @@ public void ScopeNestedTimingTest() measurements = messageFirstScopeExit.Split(new[] { '|' }, System.StringSplitOptions.RemoveEmptyEntries); Assert.Equal(7, measurements.Length); Assert.Equal("ala", measurements[0]); - Assert.InRange(double.Parse(measurements[1], System.Globalization.CultureInfo.InvariantCulture), 20, 999); + Assert.InRange(double.Parse(measurements[1], System.Globalization.CultureInfo.InvariantCulture), 19, 999); Assert.InRange(int.Parse(measurements[3]), 20, 999); Assert.InRange(int.Parse(measurements[5]), 200000, 9999999); Assert.Equal("c", measurements[measurements.Length - 1]); diff --git a/tests/NLog.UnitTests/LayoutRenderers/SequenceIdLayoutRendererTest.cs b/tests/NLog.UnitTests/LayoutRenderers/SequenceIdLayoutRendererTest.cs index 163b568b07..066e93d524 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/SequenceIdLayoutRendererTest.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/SequenceIdLayoutRendererTest.cs @@ -33,12 +33,13 @@ namespace NLog.UnitTests.LayoutRenderers { + using System; using System.Globalization; using Xunit; + [Obsolete("Use ${counter:sequence=global} instead of ${sequenceid}. Marked obsolete with NLog 6.0")] public class SequenceIdLayoutRendererTest : NLogTestBase { - [Fact] public void RenderSequenceIdLayoutRenderer() { diff --git a/tests/NLog.UnitTests/LayoutRenderers/SpecialFolderTests.cs b/tests/NLog.UnitTests/LayoutRenderers/SpecialFolderTests.cs index c0b2b3f881..718bb9f96c 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/SpecialFolderTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/SpecialFolderTests.cs @@ -33,7 +33,6 @@ namespace NLog.UnitTests.LayoutRenderers { -#if !NETSTANDARD1_5 using System; using System.IO; using Xunit; @@ -93,5 +92,4 @@ public void SpecialFolderUserLocalApplicationDataTest() AssertLayoutRendererOutput("${UserLocalApplicationDataDir}", Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)); } } -#endif } diff --git a/tests/NLog.UnitTests/LayoutRenderers/StackTraceRendererTests.cs b/tests/NLog.UnitTests/LayoutRenderers/StackTraceRendererTests.cs index ec616bf614..624a297dbb 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/StackTraceRendererTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/StackTraceRendererTests.cs @@ -111,7 +111,7 @@ public void RenderStackTraceNoCaptureStackTraceWithStackTrace() ").LogFactory; var logEvent = new LogEventInfo(LogLevel.Info, null, "I am:"); - logEvent.SetStackTrace(new System.Diagnostics.StackTrace(true), 0); + logEvent.SetStackTrace(new System.Diagnostics.StackTrace(true)); logFactory.GetCurrentClassLogger().Log(logEvent); logFactory.AssertDebugLastMessageContains($" => {nameof(StackTraceRendererTests)}.{nameof(RenderStackTraceNoCaptureStackTraceWithStackTrace)}"); } diff --git a/tests/NLog.UnitTests/LayoutRenderers/VariableLayoutRendererTests.cs b/tests/NLog.UnitTests/LayoutRenderers/VariableLayoutRendererTests.cs index 4f80fff2fb..0f3be19574 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/VariableLayoutRendererTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/VariableLayoutRendererTests.cs @@ -137,7 +137,7 @@ public void Var_with_layout(string variableName, string layoutStyle) logger.Debug("msg"); var lastMessage = GetDebugLastMessage("debug", logFactory); - Assert.Equal("{ \"short date\": \"Debug\", \"message\": \"msg\" }", lastMessage); + Assert.Equal("{\"short date\":\"Debug\",\"message\":\"msg\"}", lastMessage); } [Fact] diff --git a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/JsonEncodeTests.cs b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/JsonEncodeTests.cs index e95f3217ad..48d5ced4bf 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/JsonEncodeTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/JsonEncodeTests.cs @@ -43,9 +43,9 @@ public class JsonEncodeTests : NLogTestBase public void JsonEncodeTest1() { ScopeContext.PushProperty("foo", " abc\"\n\b\r\f\t/\u1234\u5432\\xyz "); - SimpleLayout l = "${json-encode:${scopeproperty:foo}:escapeForwardSlash=true}"; + SimpleLayout l = "${json-encode:${scopeproperty:foo}}"; - Assert.Equal(@" abc\""\n\b\r\f\t\/\u1234\u5432\\xyz ", l.Render(LogEventInfo.CreateNullEvent())); + Assert.Equal(@" abc\""\n\b\r\f\t/\u1234\u5432\\xyz ", l.Render(LogEventInfo.CreateNullEvent())); } [Fact] diff --git a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/ReplaceNewLinesTests.cs b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/ReplaceNewLinesTests.cs index d7db7a82d5..4fde146c78 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/ReplaceNewLinesTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/ReplaceNewLinesTests.cs @@ -135,5 +135,89 @@ public void ReplaceNewLineWithNewLineSeparationStringTest() // Assert Assert.Equal("bar\r\n123\r\n", result); } + + [Fact] + public void ReplaceCarriageReturnWithSpecifiedSeparationStringTest() + { + // Arrange + var foo = "bar\r123"; + SimpleLayout l = "${replace-newlines:replacement=|:${event-properties:foo}}"; + // Act + var result = l.Render(LogEventInfo.Create(LogLevel.Info, null, null, "{foo}", new[] { foo })); + // Assert + Assert.Equal("bar|123", result); + } + + [Fact] + public void ReplaceUnicodeLineEndingsWithSpecifiedSeparationStringTest() + { + // Arrange + var foo = "line1\nline2\r\nline3\rline4\u0085line5\u2028line6\u000Cline7\u2029line8"; + SimpleLayout l = "${replace-newlines:replacement=|:${event-properties:foo}}"; + // Act + var result = l.Render(LogEventInfo.Create(LogLevel.Info, null, null, "{foo}", new[] { foo })); + // Assert + Assert.Equal("line1|line2|line3|line4|line5|line6|line7|line8", result); + } + + [Fact] + public void ReplaceUnicodeLineEndingsWithSpecifiedMulticharacterSeparationStringTest() + { + // Arrange + var foo = "line1\nline2\r\nline3\rline4\u0085line5\u2028line6\u000Cline7\u2029line8\r\n"; + SimpleLayout l = "${replace-newlines:replacement=||||:${event-properties:foo}}"; + // Act + var result = l.Render(LogEventInfo.Create(LogLevel.Info, null, null, "{foo}", new[] { foo })); + // Assert + Assert.Equal("line1||||line2||||line3||||line4||||line5||||line6||||line7||||line8||||", result); + } + + [Fact] + public void ReplaceUnicodeConsecutiveLineEndingsWithSpecifiedMulticharacterSeparationStringTest() + { + // Arrange + var foo = "line1\r\r\n\nline2"; + SimpleLayout l = "${replace-newlines:replacement=||:${event-properties:foo}}"; + // Act + var result = l.Render(LogEventInfo.Create(LogLevel.Info, null, null, "{foo}", new[] { foo })); + // Assert + Assert.Equal("line1||||||line2", result); + } + + [Fact] + public void ReplaceWindowsLineEndingsEndOfTextWithSpecifiedSeparationStringTest() + { + // Arrange + var foo = "line1\r\n"; + SimpleLayout l = "${replace-newlines:replacement=|:${event-properties:foo}}"; + // Act + var result = l.Render(LogEventInfo.Create(LogLevel.Info, null, null, "{foo}", new[] { foo })); + // Assert + Assert.Equal("line1|", result); + } + + [Fact] + public void ReplaceUnicodeLineEndingsWithDefault() + { + // Arrange + var foo = "line1\nline2\r\nline3\rline4\u0085line5\u2028line6\u000Cline7\u2029line8"; + SimpleLayout l = "${replace-newlines:${event-properties:foo}}"; + // Act + var result = l.Render(LogEventInfo.Create(LogLevel.Info, null, null, "{foo}", new[] { foo })); + // Assert + Assert.Equal("line1 line2 line3 line4 line5 line6 line7 line8", result); + } + + [Fact] + public void ReplaceSingleLineEndingWithDefault() + { + // Arrange + var foo = "\n"; + SimpleLayout l = "${replace-newlines:${event-properties:foo}}"; + // Act + var result = l.Render(LogEventInfo.Create(LogLevel.Info, null, null, "{foo}", new[] { foo })); + // Assert + Assert.Equal(" ", result); + } } } diff --git a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/ReplaceTests.cs b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/ReplaceTests.cs index 32b5bc1ece..62fe5b1255 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/ReplaceTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/ReplaceTests.cs @@ -33,13 +33,8 @@ namespace NLog.UnitTests.LayoutRenderers.Wrappers { - using System; - using System.Collections.Generic; using NLog; - using NLog.Config; - using NLog.LayoutRenderers.Wrappers; using NLog.Layouts; - using NLog.Targets; using Xunit; public class ReplaceTests : NLogTestBase @@ -71,134 +66,28 @@ public void ReplaceTestIgnoreCaseWithoutRegEx() } [Fact] - public void ReplaceTestWithSimpleRegEx() + public void ReplaceTestWholeWordsWithoutRegEx() { // Arrange - SimpleLayout layout = @"${replace:inner=${message}:searchFor=\\r\\n|\\s:replaceWith= :regex=true}"; + SimpleLayout layout = @"${replace:inner=${message}:searchFor=foo:replaceWith=BAR:ignorecase=true:WholeWords=true}"; // Act - var result = layout.Render(new LogEventInfo(LogLevel.Info, "Test", "\r\nfoo\rbar\nbar\tbar bar \n bar")); + var result = layout.Render(new LogEventInfo(LogLevel.Info, "Test", "foo bar bar foobar barfoo bar FOO")); // Assert - Assert.Equal(" foo bar bar bar bar bar", result); + Assert.Equal("BAR bar bar foobar barfoo bar BAR", result); } [Fact] - public void ReplaceTestWithSimpleRegExFromConfig() + public void ReplaceTestWithSecretVariable() { - var configuration = XmlLoggingConfiguration.CreateFromXmlString(@" - - - - - - - -"); + var memoryTarget = new NLog.Targets.MemoryTarget() { Layout = "${replace:inner=${message}:searchFor=${var:secret}:replaceWith=******" }; + var logFactory = new LogFactory().Setup().LoadConfigurationFromXml(""). + LoadConfiguration(cfg => cfg.ForLogger().WriteTo(memoryTarget)).LogFactory; - var d1 = configuration.FindTargetByName("d1") as DebugTarget; - Assert.NotNull(d1); - var layout = d1.Layout as SimpleLayout; - Assert.NotNull(layout); - - var result = layout.Render(new LogEventInfo(LogLevel.Info, "Test", "\r\nfoo\rbar\nbar\tbar bar \n bar")); - Assert.Equal(" foo bar bar bar bar bar", result); - } - - [Fact] - public void ReplaceTestWithSimpleRegExFromConfig2() - { - var configuration = XmlLoggingConfiguration.CreateFromXmlString(@" - - - - - - - - - -"); - - var d1 = configuration.FindTargetByName("d1") as DebugTarget; - Assert.NotNull(d1); - var layout = d1.Layout as SimpleLayout; - Assert.NotNull(layout); - - var result = layout.Render(new LogEventInfo(LogLevel.Info, "Test", "\r\nfoo\rbar\nbar\tbar bar \n bar")); - Assert.Equal(" foo bar bar bar bar bar", result); - } - - [Fact] - public void ReplaceTestWithComplexRegEx() - { - var configuration = XmlLoggingConfiguration.CreateFromXmlString(@" - - - - - - - - - - - - -"); - - var d1 = configuration.FindTargetByName("d1") as DebugTarget; - Assert.NotNull(d1); - var layout = d1.Layout as SimpleLayout; - Assert.NotNull(layout); - - var testCases = new List> - { - Tuple.Create("1234", "1234"), - Tuple.Create("1234-5678-1234-5678", "XXXX-XXXX-XXXX-5678"), - }; - - foreach (var testCase in testCases) - { - var result = layout.Render(new LogEventInfo(LogLevel.Info, "Test", testCase.Item1)); - Assert.Equal(testCase.Item2, result); - } - } - - [Fact] - public void ReplaceNamedGroupTests() - { - var pattern = @"(?\d)[ -]*){8,16}(?=(\d[ -]*){3}(\d)(?![ -]*\d))"; - var groupName = @"digits"; - - var regex = new System.Text.RegularExpressions.Regex( - pattern, - System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.IgnoreCase); - - var testCases = new List> - { - Tuple.Create("1234", "1234", "X"), - Tuple.Create("1234-5678-1234-5678", "XXXX-XXXX-XXXX-5678", "X"), - Tuple.Create("1234 5678 1234 5678", "XXXX XXXX XXXX 5678", "X"), - Tuple.Create("1234567812345678", "XXXXXXXXXXXX5678", "X"), - Tuple.Create("ABCD-1234-5678-1234-5678", "ABCD-XXXX-XXXX-XXXX-5678", "X"), - Tuple.Create("1234-5678-1234-5678-ABCD", "XXXX-XXXX-XXXX-5678-ABCD", "X"), - Tuple.Create("ABCD-1234-5678-1234-5678-ABCD", "ABCD-XXXX-XXXX-XXXX-5678-ABCD", "X"), - Tuple.Create("ABCD-1234-5678-1234-5678-ABCD", "ABCD-XXXXXXXX-XXXXXXXX-XXXXXXXX-5678-ABCD", "XX"), - }; - - foreach (var testCase in testCases) - { - var input = testCase.Item1; - var replacement = testCase.Item3; - - var result = regex.Replace( - input, m => ReplaceLayoutRendererWrapper.ReplaceNamedGroup(groupName, replacement, m)); - - Assert.Equal(testCase.Item2, result); - } + logFactory.GetLogger(nameof(ReplaceTestWithSecretVariable)).Info("My name is secret"); + Assert.Single(memoryTarget.Logs); + Assert.Equal("My name is ******", memoryTarget.Logs[0]); } } } diff --git a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/WhenEmptyTests.cs b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/WhenEmptyTests.cs index 69aa158826..fc201c6363 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/WhenEmptyTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/WhenEmptyTests.cs @@ -73,10 +73,10 @@ public void WhenEmpty_MissingInner_ShouldNotThrow() { using (new NoThrowNLogExceptions()) { - SimpleLayout l = @"${whenEmpty:whenEmpty=${literal:text=c:\logs\}:inner=${environment:LOG_DIR_XXX}}api.log"; + SimpleLayout l = @"${whenEmpty:whenEmpty=${literal:text=c:\logs\}:inner=${environment:LOG_DIR_XXX}}_api.log"; var le = LogEventInfo.Create(LogLevel.Info, "logger", "message"); LogManager.ThrowExceptions = true; - Assert.Equal("api.log", l.Render(le)); + Assert.Equal("LOG_DIR_XXX_api.log", l.Render(le)); } } @@ -111,16 +111,15 @@ public void GetStringValueShouldWork(string message, string expected) { // Arrange SimpleLayout layout = @"${message:whenEmpty=default}"; + layout.Initialize(null); var stringValueRenderer = (IStringValueRenderer)layout.Renderers[0]; - var logEvent = LogEventInfo.Create(LogLevel.Info, "logger", message); // Act + var logEvent = LogEventInfo.Create(LogLevel.Info, "logger", message); var result = stringValueRenderer.GetFormattedString(logEvent); // Assert Assert.Equal(expected, result); - - } } } diff --git a/tests/NLog.UnitTests/Layouts/CompoundLayoutTests.cs b/tests/NLog.UnitTests/Layouts/CompoundLayoutTests.cs index 441c24c440..6f9a161be8 100644 --- a/tests/NLog.UnitTests/Layouts/CompoundLayoutTests.cs +++ b/tests/NLog.UnitTests/Layouts/CompoundLayoutTests.cs @@ -52,6 +52,7 @@ public void CodeCompoundLayoutIsRenderedCorrectly() new SimpleLayout("|Before| "), new JsonLayout { + SuppressSpaces = false, Attributes = { new JsonAttribute("short_date", "${shortdate}"), @@ -153,7 +154,7 @@ public void XmlCompoundLayoutIsRenderedCorrectly() - + diff --git a/tests/NLog.UnitTests/Layouts/CsvLayoutTests.cs b/tests/NLog.UnitTests/Layouts/CsvLayoutTests.cs index 5c16f1e356..66b7ea5c7f 100644 --- a/tests/NLog.UnitTests/Layouts/CsvLayoutTests.cs +++ b/tests/NLog.UnitTests/Layouts/CsvLayoutTests.cs @@ -319,6 +319,9 @@ public void CsvLayoutCachingTest() Message = "hello, world" }; + csvLayout.Precalculate(e1); + csvLayout.Precalculate(e2); + var r11 = csvLayout.Render(e1); var r12 = csvLayout.Render(e1); var r21 = csvLayout.Render(e2); diff --git a/tests/NLog.UnitTests/Layouts/JsonArrayLayoutTests.cs b/tests/NLog.UnitTests/Layouts/JsonArrayLayoutTests.cs index 45b8b441d8..1d3a70c461 100644 --- a/tests/NLog.UnitTests/Layouts/JsonArrayLayoutTests.cs +++ b/tests/NLog.UnitTests/Layouts/JsonArrayLayoutTests.cs @@ -59,7 +59,7 @@ public void JsonArrayLayoutRendering() Message = "hello\n world", }; - Assert.Equal("[ \"2010-01-01T12:34:56Z\", \"Info\", \"hello\\n world\" ]", jsonLayout.Render(logEventInfo)); + Assert.Equal("[\"2010-01-01T12:34:56Z\",\"Info\",\"hello\\n world\"]", jsonLayout.Render(logEventInfo)); } [Fact] @@ -90,7 +90,7 @@ public void JsonArrayLayoutRenderingFromXml() }; logFactory.GetCurrentClassLogger().Log(logEventInfo); - logFactory.AssertDebugLastMessage("[ \"2010-01-01T12:34:56Z\", \"Info\", \"hello\\n world\" ]"); + logFactory.AssertDebugLastMessage("[\"2010-01-01T12:34:56Z\",\"Info\",\"hello\\n world\"]"); } [Fact] @@ -135,7 +135,7 @@ public void JsonArrayLayoutRenderingNotEmpty() Message = "hello\n world", }; - Assert.Equal("[ ]", jsonLayout.Render(logEventInfo)); + Assert.Equal("[]", jsonLayout.Render(logEventInfo)); } [Fact] @@ -180,7 +180,7 @@ public void JsonArrayLayoutObjectRendering() Message = "hello\n world", }; - Assert.Equal("[ { \"date\": \"2010-01-01 12:34:56.0000\" }, { \"level\": \"Info\" }, { \"message\": \"hello\\n world\" } ]", jsonLayout.Render(logEventInfo)); + Assert.Equal("[{\"date\":\"2010-01-01 12:34:56.0000\"},{\"level\":\"Info\"},{\"message\":\"hello\\n world\"}]", jsonLayout.Render(logEventInfo)); } } } diff --git a/tests/NLog.UnitTests/Layouts/JsonLayoutTests.cs b/tests/NLog.UnitTests/Layouts/JsonLayoutTests.cs index 7b2c7f1711..539ab75b23 100644 --- a/tests/NLog.UnitTests/Layouts/JsonLayoutTests.cs +++ b/tests/NLog.UnitTests/Layouts/JsonLayoutTests.cs @@ -43,8 +43,8 @@ namespace NLog.UnitTests.Layouts public class JsonLayoutTests : NLogTestBase { - private const string ExpectedIncludeAllPropertiesWithExcludes = "{ \"StringProp\": \"ValueA\", \"IntProp\": 123, \"DoubleProp\": 123.123, \"DecimalProp\": 123.123, \"BoolProp\": true, \"NullProp\": null, \"DateTimeProp\": \"2345-01-23T12:34:56Z\" }"; - private const string ExpectedExcludeEmptyPropertiesWithExcludes = "{ \"StringProp\": \"ValueA\", \"IntProp\": 123, \"DoubleProp\": 123.123, \"DecimalProp\": 123.123, \"BoolProp\": true, \"DateTimeProp\": \"2345-01-23T12:34:56Z\", \"NoEmptyProp4\": \"hello\\\"\" }"; + private const string ExpectedIncludeAllPropertiesWithExcludes = "{\"StringProp\":\"ValueA\",\"IntProp\":123,\"DoubleProp\":123.123,\"DecimalProp\":123.123,\"BoolProp\":true,\"NullProp\":null,\"DateTimeProp\":\"2345-01-23T12:34:56Z\"}"; + private const string ExpectedExcludeEmptyPropertiesWithExcludes = "{\"StringProp\":\"ValueA\",\"IntProp\":123,\"DoubleProp\":123.123,\"DecimalProp\":123.123,\"BoolProp\":true,\"DateTimeProp\":\"2345-01-23T12:34:56Z\",\"NoEmptyProp4\":\"hello\\\"\"}"; [Fact] public void JsonLayoutRendering() @@ -66,7 +66,7 @@ public void JsonLayoutRendering() Message = "hello, world" }; - Assert.Equal("{ \"date\": \"2010-01-01 12:34:56.0000\", \"level\": \"Info\", \"message\": \"hello, world\" }", jsonLayout.Render(logEventInfo)); + Assert.Equal("{\"date\":\"2010-01-01 12:34:56.0000\",\"level\":\"Info\",\"message\":\"hello, world\"}", jsonLayout.Render(logEventInfo)); } [Fact] @@ -157,7 +157,7 @@ public void JsonLayoutRenderingAndEncodingSpecialCharacters() Message = "\"hello, world\"" }; - Assert.Equal("{ \"date\": \"2010-01-01 12:34:56.0000\", \"level\": \"Info\", \"message\": \"\\\"hello, world\\\"\" }", jsonLayout.Render(logEventInfo)); + Assert.Equal("{\"date\":\"2010-01-01 12:34:56.0000\",\"level\":\"Info\",\"message\":\"\\\"hello, world\\\"\"}", jsonLayout.Render(logEventInfo)); } [Fact] @@ -180,7 +180,7 @@ public void JsonLayoutRenderingAndEncodingLineBreaks() Message = "hello,\n\r world" }; - Assert.Equal("{ \"date\": \"2010-01-01 12:34:56.0000\", \"level\": \"Info\", \"message\": \"hello,\\n\\r world\" }", jsonLayout.Render(logEventInfo)); + Assert.Equal("{\"date\":\"2010-01-01 12:34:56.0000\",\"level\":\"Info\",\"message\":\"hello,\\n\\r world\"}", jsonLayout.Render(logEventInfo)); } [Fact] @@ -203,7 +203,7 @@ public void JsonLayoutRenderingAndNotEncodingMessageAttribute() Message = "{ \"hello\" : \"world\" }" }; - Assert.Equal("{ \"date\": \"2010-01-01 12:34:56.0000\", \"level\": \"Info\", \"message\": { \"hello\" : \"world\" } }", jsonLayout.Render(logEventInfo)); + Assert.Equal("{\"date\":\"2010-01-01 12:34:56.0000\",\"level\":\"Info\",\"message\":{ \"hello\" : \"world\" }}", jsonLayout.Render(logEventInfo)); } [Fact] @@ -226,7 +226,7 @@ public void JsonLayoutRenderingAndEncodingMessageAttribute() Message = "{ \"hello\" : \"world\" }" }; - Assert.Equal("{ \"date\": \"2010-01-01 12:34:56.0000\", \"level\": \"Info\", \"message\": \"{ \\\"hello\\\" : \\\"world\\\" }\" }", jsonLayout.Render(logEventInfo)); + Assert.Equal("{\"date\":\"2010-01-01 12:34:56.0000\",\"level\":\"Info\",\"message\":\"{ \\\"hello\\\" : \\\"world\\\" }\"}", jsonLayout.Render(logEventInfo)); } [Fact] @@ -249,7 +249,7 @@ public void JsonLayoutValueTypeAttribute() Message = "{ \"hello\" : \"world\" }" }; - Assert.Equal("{ \"date\": \"2010-01-01T12:34:56Z\", \"level\": \"Info\", \"message\": \"{ \\\"hello\\\" : \\\"world\\\" }\" }", jsonLayout.Render(logEventInfo)); + Assert.Equal("{\"date\":\"2010-01-01T12:34:56Z\",\"level\":\"Info\",\"message\":\"{ \\\"hello\\\" : \\\"world\\\" }\"}", jsonLayout.Render(logEventInfo)); } [Fact] @@ -345,7 +345,7 @@ public void NestedJsonAttrTest() }; var json = jsonLayout.Render(logEventInfo); - Assert.Equal("{ \"type\": \"NLog.NLogRuntimeException\", \"message\": \"test\", \"innerException\": { \"type\": \"System.NullReferenceException\", \"message\": \"null is bad!\" } }", json); + Assert.Equal("{\"type\":\"NLog.NLogRuntimeException\",\"message\":\"test\",\"innerException\":{\"type\":\"System.NullReferenceException\",\"message\":\"null is bad!\"}}", json); } [Fact] @@ -378,7 +378,7 @@ public void NestedJsonAttrDoesNotRenderEmptyLiteralIfRenderEmptyObjectIsFalseTes }; var json = jsonLayout.Render(logEventInfo); - Assert.Equal("{ \"type\": \"NLog.NLogRuntimeException\", \"message\": \"test\" }", json); + Assert.Equal("{\"type\":\"NLog.NLogRuntimeException\",\"message\":\"test\"}", json); } @@ -412,7 +412,7 @@ public void NestedJsonAttrRendersEmptyLiteralIfRenderEmptyObjectIsTrueTest() }; var json = jsonLayout.Render(logEventInfo); - Assert.Equal("{ \"type\": \"NLog.NLogRuntimeException\", \"message\": \"test\", \"innerException\": { } }", json); + Assert.Equal("{\"type\":\"NLog.NLogRuntimeException\",\"message\":\"test\",\"innerException\":{}}", json); } @@ -467,7 +467,7 @@ public void NestedJsonAttrTestFromXML() }; var json = jsonLayout.Render(logEventInfo); - Assert.Equal("{ \"time\": \"2016-10-30 13:30:55.0000\", \"level\": \"INFO\", \"nested\": { \"message\": \"this is message\", \"exception\": \"test\" } }", json); + Assert.Equal("{\"time\":\"2016-10-30 13:30:55.0000\",\"level\":\"INFO\",\"nested\":{\"message\":\"this is message\",\"exception\":\"test\"}}", json); } [Fact] @@ -496,7 +496,7 @@ public void PropertyKeyWithQuote() var logEventInfo = new LogEventInfo(); logEventInfo.Properties.Add(@"fo""o", "bar"); - Assert.Equal(@"{ ""fo\""o"": ""bar"" }", jsonLayout.Render(logEventInfo)); + Assert.Equal(@"{""fo\""o"":""bar""}", jsonLayout.Render(logEventInfo)); } [Fact] @@ -505,7 +505,7 @@ public void AttributerKeyWithQuote() var jsonLayout = new JsonLayout(); jsonLayout.Attributes.Add(new JsonAttribute(@"fo""o", "bar")); - Assert.Equal(@"{ ""fo\""o"": ""bar"" }", jsonLayout.Render(LogEventInfo.CreateNullEvent())); + Assert.Equal(@"{""fo\""o"":""bar""}", jsonLayout.Render(LogEventInfo.CreateNullEvent())); } [Fact] @@ -575,7 +575,7 @@ public void IncludeAllJsonPropertiesMaxRecursionLimit() data = new Dictionary() { { 42, "Hello" } } }; - Assert.Equal(@"{ ""Message"": {""data"":{}} }", jsonLayout.Render(logEventInfo)); + Assert.Equal(@"{""Message"":{""data"":{}}}", jsonLayout.Render(logEventInfo)); } [Fact] @@ -843,7 +843,7 @@ public void IncludeMdlcJsonNestedProperties() logFactory.Flush(); - logFactory.AssertDebugLastMessage(@"{ ""scope"": " + ExpectedIncludeAllPropertiesWithExcludes + " }"); + logFactory.AssertDebugLastMessage(@"{""scope"":" + ExpectedIncludeAllPropertiesWithExcludes + "}"); } /// @@ -942,7 +942,7 @@ public void IncludeAllJsonPropertiesMutableNestedXml() logFactory.Flush(); // Assert - logFactory.AssertDebugLastMessage(@"{ ""properties"": " + ExpectedIncludeAllPropertiesWithExcludes + " }"); + logFactory.AssertDebugLastMessage(@"{""properties"":" + ExpectedIncludeAllPropertiesWithExcludes + "}"); } /// @@ -973,7 +973,7 @@ public void SerializeObjectRecursionSingle() logger.Debug(logEventInfo1); - logFactory.AssertDebugLastMessage("{ \"nestedObject\": [{\"val\":1, \"val2\":\"value2\"},{\"val3\":3, \"val4\":\"value4\"}] }"); + logFactory.AssertDebugLastMessage("{\"nestedObject\":[{\"val\":1,\"val2\":\"value2\"},{\"val3\":3,\"val4\":\"value4\"}]}"); var logEventInfo2 = new LogEventInfo(); @@ -981,7 +981,7 @@ public void SerializeObjectRecursionSingle() logger.Debug(logEventInfo2); - logFactory.AssertDebugLastMessage("{ \"nestedObject\": {\"val\":1, \"val2\":\"value2\"} }"); + logFactory.AssertDebugLastMessage("{\"nestedObject\":{\"val\":1,\"val2\":\"value2\"}}"); var logEventInfo3 = new LogEventInfo(); @@ -989,7 +989,7 @@ public void SerializeObjectRecursionSingle() logger.Debug(logEventInfo3); - logFactory.AssertDebugLastMessage("{ \"nestedObject\": [[\"{ val = 1, val2 = value2 }\"]] }"); // Allows nested collection, but then only ToString + logFactory.AssertDebugLastMessage("{\"nestedObject\":[[\"{ val = 1, val2 = value2 }\"]]}"); // Allows nested collection, but then only ToString } [Fact] @@ -1017,7 +1017,7 @@ public void SerializeObjectRecursionZero() logger.Debug(logEventInfo1); - logFactory.AssertDebugLastMessage("{ \"nestedObject\": [\"{ val = 1, val2 = value2 }\",\"{ val3 = 3, val4 = value5 }\"] }"); // Allows single collection recursion + logFactory.AssertDebugLastMessage("{\"nestedObject\":[\"{ val = 1, val2 = value2 }\",\"{ val3 = 3, val4 = value5 }\"]}"); // Allows single collection recursion var logEventInfo2 = new LogEventInfo(); @@ -1025,7 +1025,7 @@ public void SerializeObjectRecursionZero() logger.Debug(logEventInfo2); - logFactory.AssertDebugLastMessage("{ \"nestedObject\": \"{ val = 1, val2 = value2 }\" }"); // Never object recursion, only ToString + logFactory.AssertDebugLastMessage("{\"nestedObject\":\"{ val = 1, val2 = value2 }\"}"); // Never object recursion, only ToString var logEventInfo3 = new LogEventInfo(); @@ -1033,7 +1033,7 @@ public void SerializeObjectRecursionZero() logger.Debug(logEventInfo3); - logFactory.AssertDebugLastMessage("{ \"nestedObject\": [[]] }"); // No support for nested collections + logFactory.AssertDebugLastMessage("{\"nestedObject\":[[]]}"); // No support for nested collections } [Fact] @@ -1043,7 +1043,7 @@ public void EncodesInvalidCharacters() - + @@ -1052,7 +1052,6 @@ public void EncodesInvalidCharacters() ").LogFactory; - var logger = logFactory.GetLogger("A"); var logEventInfo1 = new LogEventInfo(); @@ -1061,7 +1060,7 @@ public void EncodesInvalidCharacters() logger.Debug(logEventInfo1); - logFactory.AssertDebugLastMessage("{ \"InvalidCharacters\": [\"|\",\"#\",\"{\",\"}\",\"%\",\"&\",\"\\\"\",\"~\",\"+\",\"\\\\\",\"\\/\",\":\",\"*\",\"?\",\"<\",\">\"] }"); + logFactory.AssertDebugLastMessage("{\"InvalidCharacters\":[\"|\",\"#\",\"{\",\"}\",\"%\",\"&\",\"\\\"\",\"~\",\"+\",\"\\\\\",\"/\",\":\",\"*\",\"?\",\"<\",\">\"]}"); } [Fact] @@ -1094,7 +1093,7 @@ public void EncodesInvalidDoubles() logger.Debug(logEventInfo1); - logFactory.AssertDebugLastMessage("{ \"DoubleNaN\": \"NaN\", \"DoubleInfPositive\": \"Infinity\", \"DoubleInfNegative\": \"-Infinity\", \"FloatNaN\": \"NaN\", \"FloatInfPositive\": \"Infinity\", \"FloatInfNegative\": \"-Infinity\" }"); + logFactory.AssertDebugLastMessage("{\"DoubleNaN\":\"NaN\",\"DoubleInfPositive\":\"Infinity\",\"DoubleInfNegative\":\"-Infinity\",\"FloatNaN\":\"NaN\",\"FloatInfPositive\":\"Infinity\",\"FloatInfNegative\":\"-Infinity\"}"); } [Fact] @@ -1106,7 +1105,6 @@ public void EscapeForwardSlashDefaultTest() - @@ -1121,7 +1119,7 @@ public void EscapeForwardSlashDefaultTest() logEventInfo1.Properties.Add("myurl", "http://hello.world.com/"); logger.Debug(logEventInfo1); - logFactory.AssertDebugLastMessage("{ \"myurl1\": \"http://hello.world.com/\", \"myurl2\": \"http:\\/\\/hello.world.com\\/\", \"myurl\": \"http://hello.world.com/\" }"); + logFactory.AssertDebugLastMessage("{\"myurl1\":\"http://hello.world.com/\",\"myurl\":\"http://hello.world.com/\"}"); } [Fact] @@ -1142,7 +1140,7 @@ public void SkipInvalidJsonPropertyValues() logEventInfo.Properties["RequestId"] = expectedValue; var actualValue = jsonLayout.Render(logEventInfo); - Assert.Equal($"{{ \"BadObject\": {{\"Recursive\":[\"Hello\"], \"WeirdProperty\":\"System.Action\"}}, \"RequestId\": \"{expectedValue}\" }}", actualValue); + Assert.Equal($"{{\"BadObject\":{{\"Recursive\":[\"Hello\"],\"WeirdProperty\":\"System.Action\"}},\"RequestId\":\"{expectedValue}\"}}", actualValue); } class BadObject diff --git a/tests/NLog.UnitTests/Layouts/LayoutTypedTests.cs b/tests/NLog.UnitTests/Layouts/LayoutTypedTests.cs index 5225cdd003..73608ad334 100644 --- a/tests/NLog.UnitTests/Layouts/LayoutTypedTests.cs +++ b/tests/NLog.UnitTests/Layouts/LayoutTypedTests.cs @@ -762,7 +762,7 @@ public void LayoutEqualsNullUrlValueFixedTest() { // Arrange Uri url = null; - Layout layout1 = url; + Layout layout1 = null; Layout layout2 = new Layout(url); // Act + Assert @@ -1119,7 +1119,7 @@ public void RenderShouldRecognizeStackTraceUsage() public void LayoutRendererSupportTypedLayout() { var cif = new NLog.Config.ConfigurationItemFactory(); - cif.LayoutRendererFactory.RegisterType(nameof(LayoutTypedTestLayoutRenderer)); + cif.GetLayoutRendererFactory().RegisterType(nameof(LayoutTypedTestLayoutRenderer)); Layout l = new SimpleLayout("${LayoutTypedTestLayoutRenderer:IntProperty=42}", cif); l.Initialize(null); diff --git a/tests/NLog.UnitTests/Layouts/SimpleLayoutOutputTests.cs b/tests/NLog.UnitTests/Layouts/SimpleLayoutOutputTests.cs index 954284c4c0..c83daddd32 100644 --- a/tests/NLog.UnitTests/Layouts/SimpleLayoutOutputTests.cs +++ b/tests/NLog.UnitTests/Layouts/SimpleLayoutOutputTests.cs @@ -62,7 +62,7 @@ public void LayoutRendererThrows() using (new NoThrowNLogExceptions()) { ConfigurationItemFactory configurationItemFactory = new ConfigurationItemFactory(); - configurationItemFactory.LayoutRendererFactory.RegisterType("throwsException"); + configurationItemFactory.GetLayoutRendererFactory().RegisterType("throwsException"); SimpleLayout l = new SimpleLayout("xx${throwsException}yy", configurationItemFactory); string output = l.Render(LogEventInfo.CreateNullEvent()); @@ -75,6 +75,7 @@ public void SimpleLayoutCachingTest() { var l = new SimpleLayout("xx${threadid}yy"); var ev = LogEventInfo.CreateNullEvent(); + l.Precalculate(ev); string output1 = l.Render(ev); string output2 = l.Render(ev); Assert.Same(output1, output2); @@ -86,7 +87,7 @@ public void SimpleLayoutToStringTest() var l = new SimpleLayout("xx${level}yy"); Assert.Equal("xx${level}yy", l.ToString()); - var l2 = new SimpleLayout(ArrayHelper.Empty(), "someFakeText", ConfigurationItemFactory.Default); + var l2 = new SimpleLayout(ArrayHelper.Empty(), "someFakeText"); Assert.Equal("someFakeText", l2.ToString()); var l3 = new SimpleLayout(""); @@ -102,7 +103,7 @@ public void LayoutRendererThrows2() using (new NoThrowNLogExceptions()) { ConfigurationItemFactory configurationItemFactory = new ConfigurationItemFactory(); - configurationItemFactory.LayoutRendererFactory.RegisterType("throwsException"); + configurationItemFactory.GetLayoutRendererFactory().RegisterType("throwsException"); SimpleLayout l = new SimpleLayout("xx${throwsException:msg1}yy${throwsException:msg2}zz", configurationItemFactory); string output = l.Render(LogEventInfo.CreateNullEvent()); @@ -182,8 +183,9 @@ public void TryGetRawValue_SingleLayoutRender_ShouldGiveRawValue() public void TryGetRawValue_MultipleLayoutRender_ShouldGiveNullRawValue() { // Arrange - SimpleLayout l = "${sequenceid} "; + SimpleLayout l = "${sequenceid}${threadid}"; var logEventInfo = LogEventInfo.CreateNullEvent(); + l.Precalculate(logEventInfo); // Act var success = l.TryGetRawValue(logEventInfo, out var value); @@ -251,7 +253,9 @@ public ThrowsExceptionRenderer() Message = "Some message."; } +#pragma warning disable CS0618 // Type or member is obsolete [RequiredParameter] +#pragma warning restore CS0618 // Type or member is obsolete [DefaultParameter] public string Message { get; set; } diff --git a/tests/NLog.UnitTests/Layouts/SimpleLayoutParserTests.cs b/tests/NLog.UnitTests/Layouts/SimpleLayoutParserTests.cs index 5ccdbb2e56..5c969278f4 100644 --- a/tests/NLog.UnitTests/Layouts/SimpleLayoutParserTests.cs +++ b/tests/NLog.UnitTests/Layouts/SimpleLayoutParserTests.cs @@ -297,10 +297,16 @@ public void EvaluateTest2() private static void AssertEscapeRoundTrips(string originalString) { +#pragma warning disable CS0618 // Type or member is obsolete string escapedString = SimpleLayout.Escape(originalString); +#pragma warning restore CS0618 // Type or member is obsolete SimpleLayout l = escapedString; string renderedString = l.Render(LogEventInfo.CreateNullEvent()); Assert.Equal(originalString, renderedString); + + var stringLiteral = Layout.FromLiteral(originalString); + renderedString = stringLiteral.Render(LogEventInfo.CreateNullEvent()); + Assert.Equal(originalString, renderedString); } [Fact] @@ -314,7 +320,7 @@ public void LayoutParserEscapeCodesForRegExTestV1() value=""(?<!\\d[ -]*)(?\u003a(?<digits>\\d)[ -]*)\u007b8,16\u007d(?=(\\d[ -]*)\u007b3\u007d(\\d)(?![ -]\\d))"" /> - + @@ -336,7 +342,6 @@ public void LayoutParserEscapeCodesForRegExTestV1() var l1 = layout.Renderers[0] as ReplaceLayoutRendererWrapper; Assert.NotNull(l1); - Assert.True(l1.Regex); Assert.True(l1.IgnoreCase); Assert.Equal(@"::", l1.ReplaceWith); Assert.Equal(@"(?\d)[ -]*){8,16}(?=(\d[ -]*){3}(\d)(?![ -]\d))", l1.SearchFor); @@ -353,7 +358,7 @@ public void LayoutParserEscapeCodesForRegExTestV2() value=""(?<!\\d[ -]*)(?\:(?<digits>\\d)[ -]*)\{8,16\}(?=(\\d[ -]*)\{3\}(\\d)(?![ -]\\d))"" /> - + @@ -375,7 +380,6 @@ public void LayoutParserEscapeCodesForRegExTestV2() var l1 = layout.Renderers[0] as ReplaceLayoutRendererWrapper; Assert.NotNull(l1); - Assert.True(l1.Regex); Assert.True(l1.IgnoreCase); Assert.Equal(@"::", l1.ReplaceWith); Assert.Equal(@"(?\d)[ -]*){8,16}(?=(\d[ -]*){3}(\d)(?![ -]\d))", l1.SearchFor); @@ -553,7 +557,7 @@ public void InvalidLayoutWillThrowIfExceptionThrowingIsOn() [Fact] public void InvalidLayoutWithExistingRenderer_WillThrowIfExceptionThrowingIsOn() { - ConfigurationItemFactory.Default.LayoutRendererFactory.RegisterType("layoutrenderer-with-list"); + ConfigurationItemFactory.Default.GetLayoutRendererFactory().RegisterType("layoutrenderer-with-list"); LogManager.ThrowConfigExceptions = true; Assert.Throws(() => { @@ -565,7 +569,7 @@ public void InvalidLayoutWithExistingRenderer_WillThrowIfExceptionThrowingIsOn() [Fact] public void UnknownPropertyInLayout_WillThrowIfExceptionThrowingIsOn() { - ConfigurationItemFactory.Default.LayoutRendererFactory.RegisterType("layoutrenderer-with-list"); + ConfigurationItemFactory.Default.GetLayoutRendererFactory().RegisterType("layoutrenderer-with-list"); LogManager.ThrowConfigExceptions = true; Assert.Throws(() => @@ -614,7 +618,7 @@ public void UnknownPropertyInLayout_WillThrowIfExceptionThrowingIsOn() #endif public void LayoutWithListParamTest(string input, string propname, string expected) { - ConfigurationItemFactory.Default.LayoutRendererFactory.RegisterType("layoutrenderer-with-list"); + ConfigurationItemFactory.Default.GetLayoutRendererFactory().RegisterType("layoutrenderer-with-list"); SimpleLayout l = $@"${{layoutrenderer-with-list:{propname}={input}}}"; var le = LogEventInfo.Create(LogLevel.Info, "logger", "message"); @@ -631,7 +635,7 @@ public void LayoutWithListParamTest_incorrect(string input, string propname) //note flags enum already supported //can;t convert empty to int - ConfigurationItemFactory.Default.LayoutRendererFactory.RegisterType("layoutrenderer-with-list"); + ConfigurationItemFactory.Default.GetLayoutRendererFactory().RegisterType("layoutrenderer-with-list"); Assert.Throws(() => { SimpleLayout l = $@"${{layoutrenderer-with-list:{propname}={input}}}"; diff --git a/tests/NLog.UnitTests/Layouts/ThreadAgnosticTests.cs b/tests/NLog.UnitTests/Layouts/ThreadAgnosticTests.cs index cb5088dbad..7e644ac864 100644 --- a/tests/NLog.UnitTests/Layouts/ThreadAgnosticTests.cs +++ b/tests/NLog.UnitTests/Layouts/ThreadAgnosticTests.cs @@ -158,7 +158,7 @@ public void CsvNonAgnostic() public void CustomNotAgnosticTests() { var cif = new ConfigurationItemFactory(); - cif.LayoutRendererFactory.RegisterType("customNotAgnostic"); + cif.GetLayoutRendererFactory().RegisterType("customNotAgnostic"); Layout l = new SimpleLayout("${customNotAgnostic}", cif); l.Initialize(null); @@ -169,7 +169,7 @@ public void CustomNotAgnosticTests() public void CustomAgnosticTests() { var cif = new ConfigurationItemFactory(); - cif.LayoutRendererFactory.RegisterType("customAgnostic"); + cif.GetLayoutRendererFactory().RegisterType("customAgnostic"); Layout l = new SimpleLayout("${customAgnostic}", cif); diff --git a/tests/NLog.UnitTests/Layouts/XmlLayoutTests.cs b/tests/NLog.UnitTests/Layouts/XmlLayoutTests.cs index 1ba521f38d..53cff40436 100644 --- a/tests/NLog.UnitTests/Layouts/XmlLayoutTests.cs +++ b/tests/NLog.UnitTests/Layouts/XmlLayoutTests.cs @@ -269,6 +269,32 @@ public void XmlLayout_OnlyLogEventProperties_RenderRootCorrect() Assert.Equal(expected, result); } + [Fact] + public void XmlLayout_LogEventProperties_NoRecursion() + { + // Arrange + var xmlLayout = new XmlLayout() + { + IncludeEventProperties = true, + MaxRecursionLimit = 0, + }; + + var logEventInfo = new LogEventInfo + { + Message = "message 1" + }; + logEventInfo.Properties["prop1"] = "a"; + logEventInfo.Properties["prop2"] = new KeyValuePair("path", "test"); + logEventInfo.Properties["prop3"] = "c"; + + // Act + var result = xmlLayout.Render(logEventInfo); + + // Assert + const string expected = @"a[path, test]c"; + Assert.Equal(expected, result); + } + [Fact] public void XmlLayout_InvalidXmlPropertyName_RenderNameCorrect() { diff --git a/tests/NLog.UnitTests/LogFactoryTests.cs b/tests/NLog.UnitTests/LogFactoryTests.cs index c732c22bd9..5f17492d4b 100644 --- a/tests/NLog.UnitTests/LogFactoryTests.cs +++ b/tests/NLog.UnitTests/LogFactoryTests.cs @@ -72,8 +72,90 @@ public void Flush_DoNotThrowExceptionsAndTimeout_DoesNotThrow() Assert.NotNull(timeoutException); } +#if !NET35 && !NET40 [Fact] - public void InvalidXMLConfiguration_DoesNotThrowErrorWhen_ThrowExceptionFlagIsNotSet() + public void FlushAsync_NoConfig_IsCompleted() + { + var logFactory = new LogFactory(); + var task = logFactory.FlushAsync(CancellationToken.None); + Assert.True(task.IsCompleted); + Assert.False(task.IsCanceled); + Assert.False(task.IsFaulted); + } + + [Fact] + public void FlushAsync_EmptyConfig_IsCompleted() + { + var logFactory = new LogFactory(); + logFactory.Configuration = new LoggingConfiguration(); + var task = logFactory.FlushAsync(CancellationToken.None); + Assert.True(task.IsCompleted); + Assert.False(task.IsCanceled); + Assert.False(task.IsFaulted); + } + + [Fact] + public void FlushAsync_SingleTarget_Async() + { + var logFactory = new LogFactory(); + logFactory.Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteToMethodCall((evt, parms) => { })); + var task = logFactory.FlushAsync(CancellationToken.None); + Assert.True(task.Wait(5000)); + } + + [Fact] + public void FlushAsync_TargetTimeout_Async() + { + var logFactory = new LogFactory(); + logFactory.Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteToMethodCall((evt, parms) => { System.Threading.Thread.Sleep(5000); })); + System.Threading.Tasks.Task.Run(() => logFactory.GetCurrentClassLogger().Info("Sleep")).Wait(50); + var task = logFactory.FlushAsync(new CancellationTokenSource(100).Token); + Assert.Throws(() => task.GetAwaiter().GetResult()); + } +#endif + +#if NET6_0_OR_GREATER + [Fact] + public void DisposeAsync_NoConfig_IsCompleted() + { + var logFactory = new LogFactory(); + var task = logFactory.DisposeAsync(); + Assert.True(task.IsCompleted); + Assert.False(task.IsCanceled); + Assert.False(task.IsFaulted); + } + + [Fact] + public void DisposeAsync_EmptyConfig_IsCompleted() + { + var logFactory = new LogFactory(); + logFactory.Configuration = new LoggingConfiguration(); + var task = logFactory.DisposeAsync(); + Assert.True(task.AsTask().Wait(5000)); + } + + [Fact] + public void DisposeAsync_SingleTarget_Async() + { + var logFactory = new LogFactory(); + logFactory.Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteToMethodCall((evt, parms) => { })); + var task = logFactory.DisposeAsync(); + Assert.True(task.AsTask().Wait(5000)); + } + + [Fact] + public void DisposeAsync_TargetTimeout_Async() + { + var logFactory = new LogFactory(); + logFactory.Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteToMethodCall((evt, parms) => { System.Threading.Thread.Sleep(500); })); + System.Threading.Tasks.Task.Run(() => logFactory.GetCurrentClassLogger().Info("Sleep")).Wait(25); + var task = logFactory.DisposeAsync(); + Assert.True(task.AsTask().Wait(5000)); + } +#endif + + [Fact] + public void InvalidXMLConfigurationValue_DoesNotThrowErrorWhen_ThrowExceptionFlagIsNotSet() { using (new NoThrowNLogExceptions()) { @@ -89,7 +171,7 @@ public void InvalidXMLConfiguration_DoesNotThrowErrorWhen_ThrowExceptionFlagIsNo } [Fact] - public void InvalidXMLConfiguration_ThrowErrorWhen_ThrowExceptionFlagIsSet() + public void InvalidXMLConfigurationValue_ThrowErrorWhen_ThrowExceptionFlagIsSet() { Boolean ExceptionThrown = false; try @@ -110,6 +192,28 @@ public void InvalidXMLConfiguration_ThrowErrorWhen_ThrowExceptionFlagIsSet() Assert.True(ExceptionThrown); } + [Fact] + public void InvalidXMLConfiguration_ThrowErrorWhen_ThrowExceptionFlagIsSet() + { + Boolean ExceptionThrown = false; + try + { + new LogFactory().Setup().LoadConfigurationFromXml(@" + + + + + + "); + } + catch (Exception) + { + ExceptionThrown = true; + } + + Assert.True(ExceptionThrown); + } + [Fact] [Obsolete("Replaced by LogFactory.Setup().LoadConfigurationFromFile(). Marked obsolete on NLog 5.2")] public void Configuration_InaccessibleNLog_doesNotThrowException() @@ -219,67 +323,6 @@ public void SecondaryLogFactoryDoesNotTakePrimaryLogFactoryLock() } } - [Fact] - public void ReloadConfigOnTimer_DoesNotThrowConfigException_IfConfigChangedInBetween() - { - EventHandler testChanged = null; - - try - { - LogManager.Configuration = null; - - var loggingConfiguration = new LoggingConfiguration(); - LogManager.Configuration = loggingConfiguration; - - var configLoader = new LoggingConfigurationWatchableFileLoader(LogFactory.DefaultAppEnvironment); - var logFactory = new LogFactory(configLoader); - logFactory.Configuration = loggingConfiguration; - - var differentConfiguration = new LoggingConfiguration(); - - // Verify that the random configuration change is ignored (Only the final reset is reacted upon) - bool called = false; - LoggingConfiguration oldConfiguration = null, newConfiguration = null; - testChanged = (s, e) => { called = true; oldConfiguration = e.DeactivatedConfiguration; newConfiguration = e.ActivatedConfiguration; }; - LogManager.LogFactory.ConfigurationChanged += testChanged; - - var exRecorded = Record.Exception(() => configLoader.ReloadConfigOnTimer(differentConfiguration)); - Assert.Null(exRecorded); - - // Final reset clears the configuration, so it is changed to null - LogManager.Configuration = null; - Assert.True(called); - Assert.Equal(loggingConfiguration, oldConfiguration); - Assert.Null(newConfiguration); - } - finally - { - if (testChanged != null) - LogManager.LogFactory.ConfigurationChanged -= testChanged; - } - } - - private class ReloadNullConfiguration : LoggingConfiguration - { - public override LoggingConfiguration Reload() - { - return null; - } - } - - [Fact] - public void ReloadConfigOnTimer_DoesNotThrowConfigException_IfConfigReloadReturnsNull() - { - var loggingConfiguration = new ReloadNullConfiguration(); - LogManager.Configuration = loggingConfiguration; - var configLoader = new LoggingConfigurationWatchableFileLoader(LogFactory.DefaultAppEnvironment); - var logFactory = new LogFactory(configLoader); - logFactory.Configuration = loggingConfiguration; - - var exRecorded = Record.Exception(() => configLoader.ReloadConfigOnTimer(loggingConfiguration)); - Assert.Null(exRecorded); - } - /// /// We should be forward compatible so that we can add easily attributes in the future. /// diff --git a/tests/NLog.UnitTests/LogManagerTests.cs b/tests/NLog.UnitTests/LogManagerTests.cs index 6669cc79d6..4bc7abd460 100644 --- a/tests/NLog.UnitTests/LogManagerTests.cs +++ b/tests/NLog.UnitTests/LogManagerTests.cs @@ -31,15 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -using JetBrains.Annotations; - namespace NLog.UnitTests { using System; - using System.IO; using System.Linq; using System.Threading.Tasks; - using NLog.Common; using NLog.Config; using NLog.Targets; using Xunit; @@ -232,113 +228,6 @@ public void DisableLoggingTest_WithoutUsingStatement() LogManager.Configuration = null; } - private int _reloadCounter; - - private void WaitForConfigReload(int counter) - { - while (_reloadCounter < counter) - { - System.Threading.Thread.Sleep(100); - } - } - - private void OnConfigReloaded(object sender, LoggingConfigurationChangedEventArgs e) - { - ++_reloadCounter; - Console.WriteLine("OnConfigReloaded triggered: {0}", _reloadCounter); - } - - [Fact] - public void AutoReloadTest() - { -#if NETSTANDARD || MONO - if (IsLinux()) - { - Console.WriteLine("[SKIP] LogManagerTests.AutoReloadTest because we are running in Travis"); - return; - } -#endif - - using (new InternalLoggerScope()) - { - string fileName = Path.GetTempFileName(); - try - { - _reloadCounter = 0; - LogManager.ConfigurationChanged += OnConfigReloaded; - using (StreamWriter fs = File.CreateText(fileName)) - { - fs.Write(@" - - - - - "); - } - LogManager.Configuration = new XmlLoggingConfiguration(fileName); - AssertDebugCounter("debug", 0); - var logger = LogManager.GetLogger("A"); - logger.Debug("aaa"); - AssertDebugLastMessage("debug", "aaa"); - - InternalLogger.Info("Rewriting test file..."); - - // now write the file again - using (StreamWriter fs = File.CreateText(fileName)) - { - fs.Write(@" - - - - - "); - } - - InternalLogger.Info("Rewritten."); - WaitForConfigReload(2); - - logger.Debug("aaa"); - AssertDebugLastMessage("debug", "xxx aaa"); - - // write the file again, this time make an error - using (StreamWriter fs = File.CreateText(fileName)) - { - fs.Write(@" - - - - - "); - } - - WaitForConfigReload(3); - logger.Debug("bbb"); - AssertDebugLastMessage("debug", "xxx bbb"); - - // write the corrected file again - using (StreamWriter fs = File.CreateText(fileName)) - { - fs.Write(@" - - - - - "); - } - WaitForConfigReload(4); - logger.Debug("ccc"); - AssertDebugLastMessage("debug", "zzz ccc"); - - } - finally - { - LogManager.ConfigurationChanged -= OnConfigReloaded; - if (File.Exists(fileName)) - File.Delete(fileName); - } - } - } - [Fact] public void GivenCurrentClass_WhenGetCurrentClassLogger_ThenLoggerShouldBeCurrentClass() { @@ -349,13 +238,11 @@ public void GivenCurrentClass_WhenGetCurrentClassLogger_ThenLoggerShouldBeCurren private static class ImAStaticClass { - [UsedImplicitly] public static readonly Logger Logger = LogManager.GetCurrentClassLogger(); static ImAStaticClass() { } public static void DummyToInvokeInitializers() { } - } [Fact] diff --git a/tests/NLog.UnitTests/LogMessageFormatterTests.cs b/tests/NLog.UnitTests/LogMessageFormatterTests.cs index 15a5ba98e6..6799dcd0fc 100644 --- a/tests/NLog.UnitTests/LogMessageFormatterTests.cs +++ b/tests/NLog.UnitTests/LogMessageFormatterTests.cs @@ -33,6 +33,7 @@ namespace NLog.UnitTests { + using System; using System.Text; using NLog.MessageTemplates; using NLog.Targets; @@ -41,7 +42,7 @@ namespace NLog.UnitTests public class LogMessageFormatterTests : NLogTestBase { [Fact] - public void ExtensionsLoggingFormatTest() + public void ExtensionsLoggingFormatJsonTest() { LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Info, "MyLogger", "Login request from {Username} for {Application}", new[] { @@ -74,7 +75,7 @@ public void ExtensionsLoggingFormatTest() var logger = logFactory.GetLogger("A"); logger.Log(logEventInfo); - logFactory.AssertDebugLastMessage("{ \"LogMessage\": \"Login request from {Username} for {Application}\", \"Username\": \"John\", \"Application\": \"BestApplicationEver\" }"); + logFactory.AssertDebugLastMessage("{\"LogMessage\":\"Login request from {Username} for {Application}\",\"Username\":\"John\",\"Application\":\"BestApplicationEver\"}"); Assert.Equal("Login request from John for BestApplicationEver", logEventInfo.FormattedMessage); @@ -85,20 +86,28 @@ public void ExtensionsLoggingFormatTest() } [Fact] - public void ExtensionsLoggingPreFormatTest() + public void ExtensionsLoggingPreFormatJsonTest() { - LogEventInfo logEventInfo1 = new LogEventInfo(LogLevel.Info, "MyLogger", "Login request from John for BestApplicationEver", "Login request from {Username} for {Application}", new[] + LogEventInfo logEventInfo1 = new LogEventInfo(LogLevel.Info, "MyLogger", "Login request from John for BestApplicationEver", "Login request from {Username} for {Application}", (System.Collections.Generic.IList)new[] { new MessageTemplateParameter("Username", "John", null, CaptureType.Normal), new MessageTemplateParameter("Application", "BestApplicationEver", null, CaptureType.Normal) }); - LogEventInfo logEventInfo2 = new LogEventInfo(LogLevel.Info, "MyLogger", "Login request from John for BestApplicationEver", "Login request from {Username} for {Application}", new[] -{ + LogEventInfo logEventInfo2 = new LogEventInfo(LogLevel.Info, "MyLogger", "Login request from John for BestApplicationEver", "Login request from {Username} for {Application}", (System.Collections.Generic.IList)new[] + { new MessageTemplateParameter("Username", "John", null, CaptureType.Normal), new MessageTemplateParameter("Application", new StringBuilder("BestApplicationEver", 32), null, CaptureType.Normal) }); +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER + LogEventInfo logEventInfo3 = new LogEventInfo(LogLevel.Info, "MyLogger", "Login request from John for BestApplicationEver", "Login request from {Username} for {Application}", new Span(new[] + { + new MessageTemplateParameter("Username", "John", null, CaptureType.Normal), + new MessageTemplateParameter("Application", new StringBuilder("BestApplicationEver", 32), null, CaptureType.Normal) + })); +#endif + var logFactory = new LogFactory().Setup().LoadConfigurationFromXml(@" @@ -123,12 +132,19 @@ public void ExtensionsLoggingPreFormatTest() var result2 = debugTarget.Layout.Render(logEventInfo2); Assert.Same(result2, debugTarget.LastMessage); +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER + logger.Log(logEventInfo3); + logFactory.Flush(); + var result3 = debugTarget.Layout.Render(logEventInfo3); + Assert.Same(result3, debugTarget.LastMessage); +#endif + logger.Log(logEventInfo1); logFactory.Flush(); var result1 = debugTarget.Layout.Render(logEventInfo1); Assert.NotSame(result1, debugTarget.LastMessage); - logFactory.AssertDebugLastMessage("{ \"LogMessage\": \"Login request from {Username} for {Application}\", \"Username\": \"John\", \"Application\": \"BestApplicationEver\" }"); + logFactory.AssertDebugLastMessage("{\"LogMessage\":\"Login request from {Username} for {Application}\",\"Username\":\"John\",\"Application\":\"BestApplicationEver\"}"); Assert.Equal("Login request from John for BestApplicationEver", logEventInfo1.FormattedMessage); @@ -139,7 +155,7 @@ public void ExtensionsLoggingPreFormatTest() } [Fact] - public void NormalStringFormatTest() + public void NormalStringFormatJsonTest() { LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Info, "MyLogger", null, "{0:X} - Login request from {1} for {2} with userid {0}", new object[] { @@ -164,7 +180,7 @@ public void NormalStringFormatTest() var logger = logFactory.GetLogger("A"); logger.Log(logEventInfo); - logFactory.AssertDebugLastMessage("{ \"LogMessage\": \"{0:X} - Login request from {1} for {2} with userid {0}\" }"); + logFactory.AssertDebugLastMessage("{\"LogMessage\":\"{0:X} - Login request from {1} for {2} with userid {0}\"}"); Assert.Equal("2A - Login request from John for BestApplicationEver with userid 42", logEventInfo.FormattedMessage); @@ -175,7 +191,7 @@ public void NormalStringFormatTest() } [Fact] - public void MessageTemplateFormatTest() + public void MessageTemplateFormatJsonTest() { LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Info, "MyLogger", null, "Login request from {@Username} for {Application:l}", new object[] { @@ -199,7 +215,7 @@ public void MessageTemplateFormatTest() var logger = logFactory.GetLogger("A"); logger.Log(logEventInfo); - logFactory.AssertDebugLastMessage("{ \"LogMessage\": \"Login request from {@Username} for {Application:l}\", \"Username\": \"John\", \"Application\": \"BestApplicationEver\" }"); + logFactory.AssertDebugLastMessage("{\"LogMessage\":\"Login request from {@Username} for {Application:l}\",\"Username\":\"John\",\"Application\":\"BestApplicationEver\"}"); Assert.Equal("Login request from \"John\" for BestApplicationEver", logEventInfo.FormattedMessage); diff --git a/tests/NLog.UnitTests/LoggerTests.cs b/tests/NLog.UnitTests/LoggerTests.cs index efe18744d6..0709b30d6c 100644 --- a/tests/NLog.UnitTests/LoggerTests.cs +++ b/tests/NLog.UnitTests/LoggerTests.cs @@ -2116,8 +2116,9 @@ public void When_Logging_LogEvent_Without_Level_Defined_No_Exception_Should_Be_T config.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, target)); LogManager.Configuration = config; var logger = LogManager.GetLogger("A"); - - Assert.Throws(() => logger.Log(new LogEventInfo())); + var logEvent = new LogEventInfo(); + logger.Log(new LogEventInfo()); + Assert.Same(LogLevel.Off, logEvent.Level); } [Fact] @@ -2136,14 +2137,14 @@ public void When_Logging_LogEvent_Without_Logger_Defined_UseLoggerName() Assert.NotNull(target.LastEvent); Assert.Equal(LogLevel.Info, target.LastEvent.Level); Assert.Equal(logger.Name, target.LastEvent.LoggerName); - logger.Log(new LogEventInfo() { Level = LogLevel.Warn, Message = "Hello", LoggerName = string.Empty }); + logger.Log(new LogEventInfo() { Level = LogLevel.Warn, Message = "Hello", LoggerName = " " }); Assert.NotNull(target.LastEvent); Assert.Equal(LogLevel.Warn, target.LastEvent.Level); - Assert.Equal(string.Empty, target.LastEvent.LoggerName); - logger.Log(logger.GetType(), new LogEventInfo() { Level = LogLevel.Error, Message = "Hello", LoggerName = string.Empty }); + Assert.Equal(" ", target.LastEvent.LoggerName); + logger.Log(logger.GetType(), new LogEventInfo() { Level = LogLevel.Error, Message = "Hello", LoggerName = " " }); Assert.NotNull(target.LastEvent); Assert.Equal(LogLevel.Error, target.LastEvent.Level); - Assert.Equal(string.Empty, target.LastEvent.LoggerName); + Assert.Equal(" ", target.LastEvent.LoggerName); } [Fact] @@ -2225,31 +2226,38 @@ public void SingleTargetMessageFormatOptimizationTest() [InlineData(null, "OrderId", "@Client")] public void MixedStructuredEventsConfigTest(bool? parseMessageTemplates, string param1, string param2) { - LogManager.Configuration = CreateSimpleDebugConfig(parseMessageTemplates); - var logger = LogManager.GetLogger("A"); - logger.Debug("Process order {" + param1 + "} for {" + param2 + "}", 13424, new { ClientId = 3001, ClientName = "John Doe" }); - - string param1Value; - if (param1.StartsWith("$")) + try { - param1Value = "\"13424\""; - } - else - { - param1Value = "13424"; - } + LogManager.Configuration = CreateSimpleDebugConfig(parseMessageTemplates); + var logger = LogManager.GetLogger("A"); + logger.Debug("Process order {" + param1 + "} for {" + param2 + "}", 13424, new { ClientId = 3001, ClientName = "John Doe" }); - string param2Value; - if (param2.StartsWith("@")) - { - param2Value = "{\"ClientId\":3001, \"ClientName\":\"John Doe\"}"; + string param1Value; + if (param1.StartsWith("$")) + { + param1Value = "\"13424\""; + } + else + { + param1Value = "13424"; + } + + string param2Value; + if (param2.StartsWith("@")) + { + param2Value = "{\"ClientId\":3001, \"ClientName\":\"John Doe\"}"; + } + else + { + param2Value = "{ ClientId = 3001, ClientName = John Doe }"; + } + + AssertDebugLastMessage("debug", $"A|Process order {param1Value} for {param2Value}"); } - else + finally { - param2Value = "{ ClientId = 3001, ClientName = John Doe }"; + ConfigurationItemFactory.Default.ParseMessageTemplates = null; } - - AssertDebugLastMessage("debug", $"A|Process order {param1Value} for {param2Value}"); } [Fact] @@ -2285,17 +2293,24 @@ public void StructuredParametersShouldHandleDeferredCheck() [InlineData(null)] public void TooManyStructuredParametersShouldKeepBeInParamList(bool? parseMessageTemplates) { - LogManager.Configuration = CreateSimpleDebugConfig(parseMessageTemplates); - var target = new MyTarget(); - LogManager.Configuration.AddRuleForAllLevels(target); - LogManager.ReconfigExistingLoggers(); + try + { + LogManager.Configuration = CreateSimpleDebugConfig(parseMessageTemplates); + var target = new MyTarget(); + LogManager.Configuration.AddRuleForAllLevels(target); + LogManager.ReconfigExistingLoggers(); - var logger = LogManager.GetLogger("A"); - logger.Debug("Hello World {0}", "world", "universe"); + var logger = LogManager.GetLogger("A"); + logger.Debug("Hello World {0}", "world", "universe"); - Assert.Equal(2, target.LastEvent.Parameters.Length); - Assert.Equal("world", target.LastEvent.Parameters[0]); - Assert.Equal("universe", target.LastEvent.Parameters[1]); + Assert.Equal(2, target.LastEvent.Parameters.Length); + Assert.Equal("world", target.LastEvent.Parameters[0]); + Assert.Equal("universe", target.LastEvent.Parameters[1]); + } + finally + { + ConfigurationItemFactory.Default.ParseMessageTemplates = null; + } } [Theory] @@ -2306,24 +2321,31 @@ public void TooManyStructuredParametersShouldKeepBeInParamList(bool? parseMessag [InlineData(null, null)] public void StructuredEventsConfigTest(bool? parseMessageTemplates, bool? overrideParseMessageTemplates) { - LogManager.Configuration = CreateSimpleDebugConfig(parseMessageTemplates); - - if (parseMessageTemplates.HasValue) + try { - Assert.Equal(ConfigurationItemFactory.Default.ParseMessageTemplates, parseMessageTemplates.Value); - } + LogManager.Configuration = CreateSimpleDebugConfig(parseMessageTemplates); + + if (parseMessageTemplates.HasValue) + { + Assert.Equal(ConfigurationItemFactory.Default.ParseMessageTemplates, parseMessageTemplates.Value); + } + + if (overrideParseMessageTemplates.HasValue) + { + ConfigurationItemFactory.Default.ParseMessageTemplates = overrideParseMessageTemplates.Value; + } - if (overrideParseMessageTemplates.HasValue) + var logger = LogManager.GetLogger("A"); + logger.Debug("Hello World {0}", new object[] { null }); + if (parseMessageTemplates == true || overrideParseMessageTemplates == true) + AssertDebugLastMessage("debug", "A|Hello World NULL"); + else + AssertDebugLastMessage("debug", "A|Hello World "); + } + finally { - ConfigurationItemFactory.Default.ParseMessageTemplates = overrideParseMessageTemplates.Value; + ConfigurationItemFactory.Default.ParseMessageTemplates = null; } - - var logger = LogManager.GetLogger("A"); - logger.Debug("Hello World {0}", new object[] { null }); - if (parseMessageTemplates == true || overrideParseMessageTemplates == true) - AssertDebugLastMessage("debug", "A|Hello World NULL"); - else - AssertDebugLastMessage("debug", "A|Hello World "); } [Fact] @@ -2365,16 +2387,21 @@ public void StructuredEventsTest1() if (enabled == 1) AssertDebugLastMessage("debug", "A|hello from {\"Name\":\"Jane\", \"Childs\":[{\"Name\":\"James\"},{\"Name\":\"Mike\"}]}"); logger.Error("Test structured logging in {NLogVersion} for .NET {NETVersion}", "4.5-alpha01", new[] { 3.5, 4, 4.5 }); - if (enabled == 1) AssertDebugLastMessage("debug", "A|Test structured logging in \"4.5-alpha01\" for .NET 3.5, 4, 4.5"); + if (enabled == 1) AssertDebugLastMessage("debug", "A|Test structured logging in 4.5-alpha01 for .NET 3.5, 4, 4.5"); logger.Error("hello from {FamilyNames}", new Dictionary() { { 1, "James" }, { 2, "Mike" }, { 3, "Jane" } }); - if (enabled == 1) AssertDebugLastMessage("debug", "A|hello from 1=\"James\", 2=\"Mike\", 3=\"Jane\""); + if (enabled == 1) AssertDebugLastMessage("debug", "A|hello from 1=James, 2=Mike, 3=Jane"); logger.Error("message {a} {b}", 1, 2); - if (enabled == 1) + if (enabled == 1) AssertDebugLastMessage("debug", "A|message 1 2"); + + using (new NoThrowNLogExceptions()) { - AssertDebugLastMessage("debug", "A|message 1 2"); + logger.Error("invalid format {age:l}", 30); + if (enabled == 1) AssertDebugLastMessage("debug", "A|invalid format {age:l}"); + logger.Error("invalid format {guid:l}", Guid.NewGuid()); + if (enabled == 1) AssertDebugLastMessage("debug", "A|invalid format {guid:l}"); } logger.Error("message{a}{b}{c}", 1, 2, 3); @@ -2383,20 +2410,17 @@ public void StructuredEventsTest1() AssertDebugLastMessage("debug", "A|message123"); } - logger.Error("message {a} {b} {c}", "1", "2", "3"); if (enabled == 1) { - //todo single quotes - AssertDebugLastMessage("debug", "A|message \"1\" \"2\" \"3\""); + AssertDebugLastMessage("debug", "A|message 1 2 3"); } - logger.Error("message{a}{b}{c}", 1, 2, 3); if (enabled == 1) AssertDebugLastMessage("debug", "A|message123"); logger.Error("message{a,2}{b,-2}{c,1}{d,-1}{f,1}", 1, 2, 3, 4, ""); - if (enabled == 1) AssertDebugLastMessage("debug", "A|message 12 34\"\""); + if (enabled == 1) AssertDebugLastMessage("debug", "A|message 12 34 "); //todo other tests @@ -2518,10 +2542,7 @@ public void StructuredEventsTest1() if (enabled == 1) AssertDebugLastMessage("debug", "A|messagetest"); logger.Error(new Exception("test"), "message {Exception}", "from parameter"); - if (enabled == 1) AssertDebugLastMessage("debug", "A|message \"from parameter\"test"); - - - + if (enabled == 1) AssertDebugLastMessage("debug", "A|message from parametertest"); // logger.Error(delegate { return "message from lambda"; }); // if (enabled == 1) AssertDebugLastMessage("debug", "A|message from lambda"); @@ -2555,7 +2576,7 @@ public void TestStructuredProperties_json() logger.Error("Login request from {@Username} for {$Application}", new Person("John"), "BestApplicationEver"); - AssertDebugLastMessage("debug", "{ \"LogMessage\": \"Login request from {@Username} for {$Application}\", \"Username\": {\"Name\":\"John\"}, \"Application\": \"BestApplicationEver\" }"); + AssertDebugLastMessage("debug", "{\"LogMessage\":\"Login request from {@Username} for {$Application}\",\"Username\":{\"Name\":\"John\"},\"Application\":\"BestApplicationEver\"}"); } /// @@ -2586,7 +2607,7 @@ public void TestStructuredProperties_json_async() logger.Error("Login request from {@Username} for {$Application}", new Person("John"), sb); sb.Clear(); LogManager.Flush(); - AssertDebugLastMessage("debug", "{ \"LogMessage\": \"Login request from {@Username} for {$Application}\", \"Username\": {\"Name\":\"John\"}, \"Application\": \"BestApplicationEver\" }"); + AssertDebugLastMessage("debug", "{\"LogMessage\":\"Login request from {@Username} for {$Application}\",\"Username\":{\"Name\":\"John\"},\"Application\":\"BestApplicationEver\"}"); } /// @@ -2601,7 +2622,7 @@ public void TestStructuredProperties_json_compound() - + @@ -2614,7 +2635,7 @@ public void TestStructuredProperties_json_compound() logger.Error("Login request from {Username} for {Application}", new Person("\"John\""), "BestApplicationEver"); - AssertDebugLastMessage("debug", "Login request from \"John\" for \"BestApplicationEver\"{ \"Username\": \"\\\"John\\\"\", \"Application\": \"BestApplicationEver\" }"); + AssertDebugLastMessage("debug", "Login request from \"John\" for BestApplicationEver{ \"Username\": \"\\\"John\\\"\", \"Application\": \"BestApplicationEver\" }"); } [Fact] @@ -2708,7 +2729,7 @@ public void LogEventTemplateHandleTrickyDictionary() dictionary.Add("key 2", 1.3m); LogEventInfo logEventInfo = new LogEventInfo(LogLevel.Info, "Logger", null, "{mybad}", new object[] { dictionary }); var result = logEventInfo.FormattedMessage; - Assert.Contains("\"key1\"=13, \"key 2\"", result); + Assert.Contains("key1=13, key 2", result); } [Fact] diff --git a/tests/NLog.UnitTests/MessageTemplates/ParserTests.cs b/tests/NLog.UnitTests/MessageTemplates/ParserTests.cs index be23dc9280..c55838f642 100644 --- a/tests/NLog.UnitTests/MessageTemplates/ParserTests.cs +++ b/tests/NLog.UnitTests/MessageTemplates/ParserTests.cs @@ -80,7 +80,7 @@ public class ParserTests public void ParseAndPrint(string input, int parameterCount) { var logEventInfo = new LogEventInfo(LogLevel.Info, "Logger", null, input, ManyParameters); - logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory.ServiceRepository, true, false).MessageFormatter, null); + logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory, true, false).MessageFormatter, null); var templateAuto = logEventInfo.MessageTemplateParameters; Assert.Equal(parameterCount, templateAuto.Count); } @@ -98,7 +98,7 @@ public void ParseAndPrint(string input, int parameterCount) public void ParsePositional(string input, int index, string format) { var logEventInfo = new LogEventInfo(LogLevel.Info, "Logger", null, input, ManyParameters); - logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory.ServiceRepository, true, false).MessageFormatter, null); + logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory, true, false).MessageFormatter, null); var template = logEventInfo.MessageTemplateParameters; Assert.True(template.IsPositional); @@ -117,7 +117,7 @@ public void ParsePositional(string input, int index, string format) public void ParseNominal(string input) { var logEventInfo = new LogEventInfo(LogLevel.Info, "Logger", null, input, ManyParameters); - logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory.ServiceRepository, true, false).MessageFormatter, null); + logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory, true, false).MessageFormatter, null); var template = logEventInfo.MessageTemplateParameters; Assert.False(template.IsPositional); @@ -137,7 +137,7 @@ public void ParseNominal(string input) public void ParseName(string input, string name) { var logEventInfo = new LogEventInfo(LogLevel.Info, "Logger", null, input, ManyParameters); - logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory.ServiceRepository, true, false).MessageFormatter, null); + logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory, true, false).MessageFormatter, null); var template = logEventInfo.MessageTemplateParameters; Assert.Equal(1, template.Count); @@ -157,7 +157,7 @@ public void ParseName(string input, string name) public void ParseHoleType(string input, string holeType) { var logEventInfo = new LogEventInfo(LogLevel.Info, "Logger", null, input, ManyParameters); - logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory.ServiceRepository, true, false).MessageFormatter, null); + logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory, true, false).MessageFormatter, null); var template = logEventInfo.MessageTemplateParameters; Assert.Equal(1, template.Count); @@ -179,7 +179,7 @@ public void ParseHoleType(string input, string holeType) public void ParseFormatAndAlignment_numeric(string input, int? alignment, string format) { var logEventInfo = new LogEventInfo(LogLevel.Info, "Logger", null, input, ManyParameters); - logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory.ServiceRepository, true, false).MessageFormatter, null); + logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory, true, false).MessageFormatter, null); var template = logEventInfo.MessageTemplateParameters; Assert.Equal(1, template.Count); @@ -198,7 +198,7 @@ public void ParseFormatAndAlignment_numeric(string input, int? alignment, string public void ParseFormatAndAlignment_text(string input, int? alignment, string format) { var logEventInfo = new LogEventInfo(LogLevel.Info, "Logger", null, input, ManyParameters); - logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory.ServiceRepository, true, false).MessageFormatter, null); + logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory, true, false).MessageFormatter, null); var template = logEventInfo.MessageTemplateParameters; Assert.Equal(1, template.Count); @@ -238,7 +238,7 @@ public void ThrowsTemplateParserException(string input) Assert.Throws(() => { var logEventInfo = new LogEventInfo(LogLevel.Info, "Logger", null, input, ManyParameters); - logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory.ServiceRepository, true, false).MessageFormatter, null); + logEventInfo.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory, true, false).MessageFormatter, null); var template = logEventInfo.MessageTemplateParameters; }); } diff --git a/tests/NLog.UnitTests/MessageTemplates/RendererTests.cs b/tests/NLog.UnitTests/MessageTemplates/RendererTests.cs index 675a213636..ee89787d84 100644 --- a/tests/NLog.UnitTests/MessageTemplates/RendererTests.cs +++ b/tests/NLog.UnitTests/MessageTemplates/RendererTests.cs @@ -48,18 +48,19 @@ public class RendererTests [InlineData(" {1} {0} {0}", new object[] { "a", "b" }, " b a a")] [InlineData(" message {1} {0} {0}", new object[] { "a", "b" }, " message b a a")] [InlineData(" message {1} {0} {0}", new object[] { 'a', 'b' }, " message b a a")] - [InlineData("char {one}", new object[] { 'X' }, "char \"X\"")] + [InlineData("char {one}", new object[] { 'X' }, "char X")] [InlineData("char {one:l}", new object[] { 'X' }, "char X")] + [InlineData("char {@one}", new object[] { 'X' }, "char \"X\"")] [InlineData(" message {{{1}}} {0} {0}", new object[] { "a", "b" }, " message {b} a a")] - [InlineData(" message {{{one}}} {two} {three}", new object[] { "a", "b", "c" }, " message {\"a\"} \"b\" \"c\"")] + [InlineData(" message {{{one}}} {two} {three}", new object[] { "a", "b", "c" }, " message {a} b c")] [InlineData(" message {{{1} {0} {0}}}", new object[] { "a", "b" }, " message {b a a}")] [InlineData(" completed in {time} sec", new object[] { 10 }, " completed in 10 sec")] - [InlineData(" completed task {name} in {time} sec", new object[] { "test", 10 }, " completed task \"test\" in 10 sec")] + [InlineData(" completed task {name} in {time} sec", new object[] { "test", 10 }, " completed task test in 10 sec")] [InlineData(" completed task {name:l} in {time} sec", new object[] { "test", 10 }, " completed task test in 10 sec")] [InlineData(" completed task {0} in {1} sec", new object[] { "test", 10 }, " completed task test in 10 sec")] [InlineData(" completed task {0} in {1:000} sec", new object[] { "test", 10 }, " completed task test in 010 sec")] - [InlineData(" completed task {name} in {time:000} sec", new object[] { "test", 10 }, " completed task \"test\" in 010 sec")] - [InlineData(" completed tasks {tasks} in {time:000} sec", new object[] { new[] { "parsing", "testing", "fixing" }, 10 }, " completed tasks \"parsing\", \"testing\", \"fixing\" in 010 sec")] + [InlineData(" completed task {name} in {time:000} sec", new object[] { "test", 10 }, " completed task test in 010 sec")] + [InlineData(" completed tasks {tasks} in {time:000} sec", new object[] { new[] { "parsing", "testing", "fixing" }, 10 }, " completed tasks parsing, testing, fixing in 010 sec")] [InlineData(" completed tasks {tasks:l} in {time:000} sec", new object[] { new[] { "parsing", "testing", "fixing" }, 10 }, " completed tasks parsing, testing, fixing in 010 sec")] #if !MONO [InlineData(" completed tasks {$tasks} in {time:000} sec", new object[] { new[] { "parsing", "testing", "fixing" }, 10 }, " completed tasks \"System.String[]\" in 010 sec")] @@ -129,7 +130,7 @@ public void RenderDateTimeOffset(string input, string arg, string expected) private static void RenderAndTest(string input, CultureInfo culture, object[] args, string expected) { var logEventInfoAlways = new LogEventInfo(LogLevel.Info, "Logger", culture, input, args); - logEventInfoAlways.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory.ServiceRepository, true, false).MessageFormatter, null); + logEventInfoAlways.SetMessageFormatter(new NLog.Internal.LogMessageTemplateFormatter(LogManager.LogFactory, true, false).MessageFormatter, null); var templateAlways = logEventInfoAlways.MessageTemplateParameters; Assert.Equal(expected, logEventInfoAlways.FormattedMessage); } diff --git a/tests/NLog.UnitTests/MessageTemplates/ValueFormatterTest.cs b/tests/NLog.UnitTests/MessageTemplates/ValueFormatterTest.cs index 4bf47e405c..47e71c14bf 100644 --- a/tests/NLog.UnitTests/MessageTemplates/ValueFormatterTest.cs +++ b/tests/NLog.UnitTests/MessageTemplates/ValueFormatterTest.cs @@ -196,7 +196,7 @@ public RecursiveTest(int integer) private static ValueFormatter CreateValueFormatter() { - return new ValueFormatter(LogManager.LogFactory.ServiceRepository); + return new ValueFormatter(LogManager.LogFactory.ServiceRepository, legacyStringQuotes: false); } [Fact] @@ -335,7 +335,7 @@ public void TestSerializationOfStringIsSuccessful() StringBuilder builder = new StringBuilder(); var result = CreateValueFormatter().FormatValue(@class, string.Empty, CaptureType.Normal, new CultureInfo("fr-FR"), builder); Assert.True(result); - Assert.Equal("\"str\"", builder.ToString()); + Assert.Equal("str", builder.ToString()); } [Fact] @@ -355,7 +355,7 @@ public void TestSerialisationOfIConvertibleStringObjectIsSuccessful() StringBuilder builder = new StringBuilder(); var result = CreateValueFormatter().FormatValue(@class, string.Empty, CaptureType.Normal, new CultureInfo("fr-FR"), builder); Assert.True(result); - var expectedValue = $"\"{typeof(Test).FullName}\""; + var expectedValue = $"{typeof(Test).FullName}"; Assert.Equal(expectedValue, builder.ToString()); } @@ -376,7 +376,7 @@ public void TestSerialisationOfIConvertibleCharObjectIsSuccessful() StringBuilder builder = new StringBuilder(); var result = CreateValueFormatter().FormatValue(@class, string.Empty, CaptureType.Normal, new CultureInfo("fr-FR"), builder); Assert.True(result); - Assert.Equal("\"t\"", builder.ToString()); + Assert.Equal("t", builder.ToString()); } [Theory] diff --git a/tests/NLog.UnitTests/Mocks/AppEnvironmentMock.cs b/tests/NLog.UnitTests/Mocks/AppEnvironmentMock.cs index 5e04ccbb96..cc4a969fc0 100644 --- a/tests/NLog.UnitTests/Mocks/AppEnvironmentMock.cs +++ b/tests/NLog.UnitTests/Mocks/AppEnvironmentMock.cs @@ -33,6 +33,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Xml; using NLog.Internal.Fakeables; @@ -41,12 +42,12 @@ namespace NLog.UnitTests.Mocks internal sealed class AppEnvironmentMock : IAppEnvironment { private readonly Func _fileexists; - private readonly Func _fileload; + private readonly Func _loadTextFile; - public AppEnvironmentMock(Func fileExists, Func fileLoad) + public AppEnvironmentMock(Func fileExists = null, Func loadTextFile = null) { - _fileexists = fileExists; - _fileload = fileLoad; + _fileexists = fileExists != null ? fileExists : (f) => throw new NotSupportedException("FileSystem unavailable"); + _loadTextFile = loadTextFile != null ? loadTextFile : (f) => throw new NotSupportedException("FileSystem unavailable"); } public int AppDomainId { get; set; } @@ -73,19 +74,17 @@ public AppEnvironmentMock(Func fileExists, Func public IEnumerable GetAppDomainRuntimeAssemblies() => NLog.Internal.ArrayHelper.Empty(); - [Obsolete("For unit testing only. Marked obsolete on NLog 5.0")] - public IAppDomain AppDomain { get; set; } = new AppDomainMock(string.Empty); - public event EventHandler ProcessExit; + public event EventHandler ProcessExit; public bool FileExists(string path) { return _fileexists(path); } - public XmlReader LoadXmlFile(string path) + public TextReader LoadTextFile(string path) { - return _fileload(path); + return _loadTextFile(path); } public void SignalShutdown() diff --git a/tests/NLog.UnitTests/NLog.UnitTests.csproj b/tests/NLog.UnitTests/NLog.UnitTests.csproj index e23fbaa6d4..328bdf4d7b 100644 --- a/tests/NLog.UnitTests/NLog.UnitTests.csproj +++ b/tests/NLog.UnitTests/NLog.UnitTests.csproj @@ -3,7 +3,7 @@ 17.0 net462 - net462;net6.0 + net462;net8.0 false @@ -20,30 +20,25 @@ false - + $(DefineConstants);NET35 - + $(DefineConstants);NET45 - - $(DefineConstants);NETSTANDARD - - $(DefineConstants);MONO - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -51,18 +46,15 @@ - - - - + ../../src/NLog/bin/$(Configuration)/$(TestTargetFramework)/NLog.dll True @@ -84,7 +76,6 @@ false - diff --git a/tests/NLog.UnitTests/NLogTestBase.cs b/tests/NLog.UnitTests/NLogTestBase.cs index 819087fa00..44aea81046 100644 --- a/tests/NLog.UnitTests/NLogTestBase.cs +++ b/tests/NLog.UnitTests/NLogTestBase.cs @@ -46,9 +46,6 @@ namespace NLog.UnitTests using NLog.Layouts; using NLog.Targets; using Xunit; -#if !NETSTANDARD - using Ionic.Zip; -#endif public abstract class NLogTestBase { @@ -72,7 +69,7 @@ protected NLogTestBase() LogManager.ThrowExceptions = true; // Ensure exceptions are thrown by default during unit-testing LogManager.ThrowConfigExceptions = null; System.Diagnostics.Trace.Listeners.Clear(); -#if !NETSTANDARD +#if NETFRAMEWORK System.Diagnostics.Debug.Listeners.Clear(); #endif } @@ -121,116 +118,6 @@ protected static DebugTarget GetDebugTarget(string targetName, LogFactory logFac return LogFactoryTestExtensions.GetDebugTarget(targetName, logFactory.Configuration); } - protected static void AssertFileContentsStartsWith(string fileName, string contents, Encoding encoding) - { - FileInfo fi = new FileInfo(fileName); - if (!fi.Exists) - Assert.Fail("File '" + fileName + "' doesn't exist."); - - byte[] encodedBuf = encoding.GetBytes(contents); - - byte[] buf = File.ReadAllBytes(fileName); - Assert.True(encodedBuf.Length <= buf.Length, - $"File:{fileName} encodedBytes:{encodedBuf.Length} does not match file.content:{buf.Length}, file.length = {fi.Length}"); - - for (int i = 0; i < encodedBuf.Length; ++i) - { - if (encodedBuf[i] != buf[i]) - Assert.True(encodedBuf[i] == buf[i], - $"File:{fileName} content mismatch {(int)encodedBuf[i]} <> {(int)buf[i]} at index {i}"); - } - } - - protected static void AssertFileContentsEndsWith(string fileName, string contents, Encoding encoding) - { - if (!File.Exists(fileName)) - Assert.Fail("File '" + fileName + "' doesn't exist."); - - string fileText = File.ReadAllText(fileName, encoding); - Assert.True(fileText.Length >= contents.Length); - Assert.Equal(contents, fileText.Substring(fileText.Length - contents.Length)); - } - - protected sealed class CustomFileCompressor : IArchiveFileCompressor - { - public void CompressFile(string fileName, string archiveFileName) - { - string entryName = Path.GetFileNameWithoutExtension(archiveFileName) + Path.GetExtension(fileName); - CompressFile(fileName, archiveFileName, entryName); - } - - public void CompressFile(string fileName, string archiveFileName, string entryName) - { -#if !NETSTANDARD - using (var zip = new Ionic.Zip.ZipFile()) - { - ZipEntry entry = zip.AddFile(fileName); - entry.FileName = entryName; - zip.Save(archiveFileName); - } -#endif - } - } - -#if NET35 || NET40 - protected static void AssertZipFileContents(string fileName, string expectedEntryName, string contents, Encoding encoding) - { - if (!File.Exists(fileName)) - Assert.True(false, "File '" + fileName + "' doesn't exist."); - - byte[] encodedBuf = encoding.GetBytes(contents); - - using (var zip = new Ionic.Zip.ZipFile(fileName)) - { - Assert.Equal(1, zip.Count); - Assert.Equal(encodedBuf.Length, zip[0].UncompressedSize); - - byte[] buf = new byte[zip[0].UncompressedSize]; - using (var fs = zip[0].OpenReader()) - { - fs.Read(buf, 0, buf.Length); - } - - for (int i = 0; i < buf.Length; ++i) - { - Assert.Equal(encodedBuf[i], buf[i]); - } - } - } -#else - protected static void AssertZipFileContents(string fileName, string expectedEntryName, string contents, Encoding encoding) - { - FileInfo fi = new FileInfo(fileName); - if (!fi.Exists) - Assert.Fail("File '" + fileName + "' doesn't exist."); - - byte[] encodedBuf = encoding.GetBytes(contents); - using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var zip = new ZipArchive(stream, ZipArchiveMode.Read)) - { - Assert.Single(zip.Entries); - Assert.Equal(expectedEntryName, zip.Entries[0].Name); - Assert.Equal(encodedBuf.Length, zip.Entries[0].Length); - - byte[] buf = new byte[(int)zip.Entries[0].Length]; - using (var fs = zip.Entries[0].Open()) - { - fs.Read(buf, 0, buf.Length); - } - - for (int i = 0; i < buf.Length; ++i) - { - Assert.Equal(encodedBuf[i], buf[i]); - } - } - } -#endif - - protected static void AssertFileContents(string fileName, string expectedEntryName, string contents, Encoding encoding) - { - AssertFileContents(fileName, contents, encoding, false); - } - protected static void AssertFileContents(string fileName, string contents, Encoding encoding) { AssertFileContents(fileName, contents, encoding, false); @@ -436,16 +323,6 @@ private sealed class Logger : TextWriter public override Encoding Encoding => writer.Encoding; -#if NETSTANDARD1_5 - public override void Write(char value) - { - lock (this.writer) - { - this.writer.Write(value); - } - } -#endif - public override void Write(string value) { lock (writer) diff --git a/tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs b/tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs index 4cc138fe9d..60c50c96bd 100644 --- a/tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs +++ b/tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs @@ -47,13 +47,11 @@ public void RowHighLightNewLineTest() { var target = new ColoredConsoleTarget { Layout = "${logger} ${message}" }; AssertOutput(target, "Before\a\nAfter\a", - new string[] { "Before", "After" }); + new string[] { "Before", "After" }); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void WordHighlightingTextTest(bool compileRegex) + [Fact] + public void WordHighlightingTextTest() { var target = new ColoredConsoleTarget { Layout = "${logger} ${message}" }; target.WordHighlightingRules.Add( @@ -61,8 +59,6 @@ public void WordHighlightingTextTest(bool compileRegex) { ForegroundColor = ConsoleOutputColor.Red, Text = "at", - CompileRegex = compileRegex - }); AssertOutput(target, "The Cat Sat At The Bar\t\n.\a", @@ -90,10 +86,8 @@ public void WordHighlightingTextCondition() new string[] { "The Cat Sat At The Bar" }, loggerName: "DefaultLogger"); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void WordHighlightingTextIgnoreCase(bool compileRegex) + [Fact] + public void WordHighlightingTextIgnoreCase() { var target = new ColoredConsoleTarget { Layout = "${logger} ${message}" }; target.WordHighlightingRules.Add( @@ -102,17 +96,14 @@ public void WordHighlightingTextIgnoreCase(bool compileRegex) ForegroundColor = ConsoleOutputColor.Red, Text = "at", IgnoreCase = true, - CompileRegex = compileRegex }); AssertOutput(target, "The Cat Sat At The Bar.", new string[] { "The C", "at", " S", "at", " ", "At", " The Bar." }); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void WordHighlightingTextWholeWords(bool compileRegex) + [Fact] + public void WordHighlightingTextWholeWords() { var target = new ColoredConsoleTarget { Layout = "${logger} ${message}" }; target.WordHighlightingRules.Add( @@ -121,31 +112,12 @@ public void WordHighlightingTextWholeWords(bool compileRegex) ForegroundColor = ConsoleOutputColor.Red, Text = "at", WholeWords = true, - CompileRegex = compileRegex }); AssertOutput(target, "The cat sat at the bar.", new string[] { "The cat sat ", "at", " the bar." }); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void WordHighlightingRegex(bool compileRegex) - { - var target = new ColoredConsoleTarget { Layout = "${logger} ${message}" }; - target.WordHighlightingRules.Add( - new ConsoleWordHighlightingRule - { - ForegroundColor = ConsoleOutputColor.Red, - Regex = "\\wat", - CompileRegex = compileRegex - }); - - AssertOutput(target, "The cat sat at the bar.", - new string[] { "The ", "cat", " ", "sat", " at the bar." }); - } - [Fact] public void ColoredConsoleAnsi_OverlappingWordHighlight_VerificationTest() { @@ -270,29 +242,8 @@ public void ColoredConsoleAnsi_RowColorWithWordHighlight_VerificationTest() string.Empty); } - /// - /// With or without CompileRegex, CompileRegex is never null, even if not used when CompileRegex=false. (needed for backwards-compatibility) - /// - /// - [Theory] - [InlineData(true)] - [InlineData(false)] - public void CompiledRegexPropertyNotNull(bool compileRegex) - { - var rule = new ConsoleWordHighlightingRule - { - ForegroundColor = ConsoleOutputColor.Red, - Regex = "\\wat", - CompileRegex = compileRegex - }; - - Assert.NotNull(rule.CompiledRegex); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void DonRemoveIfRegexIsEmpty(bool compileRegex) + [Fact] + public void DonRemoveIfRegexIsEmpty() { var target = new ColoredConsoleTarget { Layout = "${logger} ${message}" }; target.WordHighlightingRules.Add( @@ -301,7 +252,6 @@ public void DonRemoveIfRegexIsEmpty(bool compileRegex) ForegroundColor = ConsoleOutputColor.Red, Text = null, IgnoreCase = true, - CompileRegex = compileRegex }); AssertOutput(target, "The Cat Sat At The Bar.", @@ -314,7 +264,6 @@ public void ColortedConsoleAutoFlushOnWrite() var target = new ColoredConsoleTarget { Layout = "${logger} ${message}", AutoFlush = true }; AssertOutput(target, "The Cat Sat At The Bar.", new string[] { "The Cat Sat At The Bar." }); - } #if !MONO @@ -348,6 +297,14 @@ public void ColoredConsoleDetectOutputRedirectedTest() } #endif + [Fact] + public void ColoredConsoleNoColor() + { + var target = new ColoredConsoleTarget { Layout = "${logger} ${message}", NoColor = true }; + AssertOutput(target, "The Cat Sat At The Bar.", + new string[] { "The Cat Sat At The Bar." }); + } + private static void AssertOutput(Target target, string message, string[] expectedParts, string loggerName = "Logger ") { var consoleOutWriter = new PartsWriter(); diff --git a/tests/NLog.UnitTests/Targets/ConsoleTargetTests.cs b/tests/NLog.UnitTests/Targets/ConsoleTargetTests.cs index a5dc3c20d9..9eefd07427 100644 --- a/tests/NLog.UnitTests/Targets/ConsoleTargetTests.cs +++ b/tests/NLog.UnitTests/Targets/ConsoleTargetTests.cs @@ -55,14 +55,14 @@ public void ConsoleOutWriteBufferTest() ConsoleOutTest(true); } - private static void ConsoleOutTest(bool writeBuffer) + private static void ConsoleOutTest(bool forceWriteLine) { var target = new ConsoleTarget() { Header = "-- header --", Layout = "${logger} ${message}", Footer = "-- footer --", - WriteBuffer = writeBuffer, + ForceWriteLine = forceWriteLine, }; var consoleOutWriter = new StringWriter(); diff --git a/tests/NLog.UnitTests/Targets/DebugSystemTargetTests.cs b/tests/NLog.UnitTests/Targets/DebugSystemTargetTests.cs index 1556272e60..2534b2204f 100644 --- a/tests/NLog.UnitTests/Targets/DebugSystemTargetTests.cs +++ b/tests/NLog.UnitTests/Targets/DebugSystemTargetTests.cs @@ -46,7 +46,7 @@ public void DebugWriteLineTest() try { // Arrange -#if !NETSTANDARD +#if NETFRAMEWORK Debug.Listeners.Clear(); Debug.Listeners.Add(new TextWriterTraceListener(sw)); #endif @@ -60,13 +60,13 @@ public void DebugWriteLineTest() // Assert Assert.Single(logFactory.Configuration.AllTargets); -#if !NETSTANDARD +#if NETFRAMEWORK Assert.Contains("Hello World", sw.ToString()); #endif } finally { -#if !NETSTANDARD +#if NETFRAMEWORK Debug.Listeners.Clear(); #endif } @@ -80,7 +80,7 @@ public void DebugWriteLineHeaderFooterTest() try { // Arrange -#if !NETSTANDARD +#if NETFRAMEWORK Debug.Listeners.Clear(); Debug.Listeners.Add(new TextWriterTraceListener(sw)); #endif @@ -102,7 +102,7 @@ public void DebugWriteLineHeaderFooterTest() // Assert Assert.Single(logFactory.Configuration.AllTargets); -#if !NETSTANDARD +#if NETFRAMEWORK Assert.Contains("Startup", sw.ToString()); Assert.Contains("Hello World", sw.ToString()); Assert.DoesNotContain("Shutdown", sw.ToString()); @@ -113,7 +113,7 @@ public void DebugWriteLineHeaderFooterTest() } finally { -#if !NETSTANDARD +#if NETFRAMEWORK Debug.Listeners.Clear(); #endif } diff --git a/tests/NLog.UnitTests/Targets/DefaultJsonSerializerClassTests.cs b/tests/NLog.UnitTests/Targets/DefaultJsonSerializerClassTests.cs index 21c5cf98b3..ffb7b15869 100644 --- a/tests/NLog.UnitTests/Targets/DefaultJsonSerializerClassTests.cs +++ b/tests/NLog.UnitTests/Targets/DefaultJsonSerializerClassTests.cs @@ -110,7 +110,7 @@ public void IExcludedInterfaceSerializer_RegistersSerializeAsToString_InvokesToS var jsonSerializer = new DefaultJsonSerializer(logFactory.ServiceRepository); jsonSerializer.SerializeObject(testObject, sb, options); const string expectedValue = - @"{""S"":""sample"", ""Excluded"":""Skipped"", ""Included"":{""IncludedString"":""serialized""}}"; + @"{""S"":""sample"",""Excluded"":""Skipped"",""Included"":{""IncludedString"":""serialized""}}"; Assert.Equal(expectedValue, sb.ToString()); } @@ -128,7 +128,7 @@ public void ExcludedClassSerializer_RegistersSerializeAsToString_InvokesToString var jsonSerializer = new DefaultJsonSerializer(logFactory.ServiceRepository); jsonSerializer.SerializeObject(testObject, sb, options); const string expectedValue = - @"{""S"":""sample"", ""Excluded"":""Skipped"", ""Included"":{""IncludedString"":""serialized""}}"; + @"{""S"":""sample"",""Excluded"":""Skipped"",""Included"":{""IncludedString"":""serialized""}}"; Assert.Equal(expectedValue, sb.ToString()); } } diff --git a/tests/NLog.UnitTests/Targets/DefaultJsonSerializerTestsBase.cs b/tests/NLog.UnitTests/Targets/DefaultJsonSerializerTestsBase.cs index df86044cbe..6d78f8800d 100644 --- a/tests/NLog.UnitTests/Targets/DefaultJsonSerializerTestsBase.cs +++ b/tests/NLog.UnitTests/Targets/DefaultJsonSerializerTestsBase.cs @@ -372,7 +372,7 @@ public void SerializeExpandoDict_Test() dictionary.Add("key 2", 1.3m); dictionary.Add("level", LogLevel.Info); var actual = SerializeObject(dictionary); - Assert.Equal("{\"key 2\":1.3, \"level\":\"Info\"}", actual); + Assert.Equal("{\"key 2\":1.3,\"level\":\"Info\"}", actual); } [Fact] @@ -393,7 +393,7 @@ public void SerializeReadOnlyExpandoDict_Test() var readonlyDictionary = new Internal.ReadOnlyExpandoTestDictionary(dictionary); var actual = SerializeObject(readonlyDictionary); - Assert.Equal("{\"key 2\":1.3, \"level\":\"Info\"}", actual); + Assert.Equal("{\"key 2\":1.3,\"level\":\"Info\"}", actual); } #endif @@ -476,7 +476,7 @@ public void SerializeObject_Test() object1.Linked = object2; var actual = SerializeObject(object1); - Assert.Equal("{\"Name\":\"object1\", \"Linked\":{\"Name\":\"object2\"}}", actual); + Assert.Equal("{\"Name\":\"object1\",\"Linked\":{\"Name\":\"object2\"}}", actual); } [Fact] @@ -499,7 +499,7 @@ public void SerializeListObject_Test() var list = new[] { object1, object2 }; var actual = SerializeObject(list); - Assert.Equal("[{\"Name\":\"object1\", \"Linked\":{\"Name\":\"object2\"}},{\"Name\":\"object2\"}]", actual); + Assert.Equal("[{\"Name\":\"object1\",\"Linked\":{\"Name\":\"object2\"}},{\"Name\":\"object2\"}]", actual); } [Fact] @@ -535,7 +535,7 @@ public void SerializeAnonymousObject_Test() { var object1 = new { Id = 123, Name = "test name" }; var actual = SerializeObject(object1); - Assert.Equal("{\"Id\":123, \"Name\":\"test name\"}", actual); + Assert.Equal("{\"Id\":123,\"Name\":\"test name\"}", actual); } /// @@ -563,7 +563,7 @@ private class CustomNullProperty : IConvertible public override string ToString() => "nullValue"; } -#if !NET35 && !NET40 +#if !NET35 && !NET40 && NETFRAMEWORK [Fact] public void SerializeExpandoObject_Test() @@ -572,7 +572,7 @@ public void SerializeExpandoObject_Test() object1.Id = 123; object1.Name = "test name"; var actual = SerializeObject(object1); - Assert.Equal("{\"Id\":123, \"Name\":\"test name\"}", actual); + Assert.Equal("{\"Id\":123,\"Name\":\"test name\"}", actual); } [Fact] @@ -580,7 +580,7 @@ public void SerializeDynamicObject_Test() { var object1 = new MyDynamicClass(); var actual = SerializeObject(object1); - Assert.Equal("{\"Id\":123, \"Name\":\"test name\"}", actual); + Assert.Equal("{\"Id\":123,\"Name\":\"test name\"}", actual); } private class MyDynamicClass : DynamicObject diff --git a/tests/NLog.UnitTests/Targets/EventLogTargetTests.cs b/tests/NLog.UnitTests/Targets/EventLogTargetTests.cs index ab05c38fa6..7e9965a14b 100644 --- a/tests/NLog.UnitTests/Targets/EventLogTargetTests.cs +++ b/tests/NLog.UnitTests/Targets/EventLogTargetTests.cs @@ -31,7 +31,7 @@ // THE POSSIBILITY OF SUCH DAMAGE. // -#if !MONO && !NETSTANDARD +#if NETFRAMEWORK && !MONO namespace NLog.UnitTests.Targets { @@ -96,27 +96,6 @@ public void MaxMessageLengthShouldBeAsSpecifiedOption() } [Theory] - [InlineData(0)] - [InlineData(-1)] - public void ConfigurationShouldThrowException_WhenMaxMessageLengthIsNegativeOrZero(int maxMessageLength) - { - string configrationText = $@" - - - - - - - - - "; - - NLogConfigurationException ex = Assert.Throws(() => XmlLoggingConfiguration.CreateFromXmlString(configrationText)); - Assert.Equal("MaxMessageLength cannot be zero or negative.", ex.InnerException.InnerException.Message); - } - - [Theory] - [InlineData(0)] // Is multiple of 64, but less than the min value of 64 [InlineData(65)] // Isn't multiple of 64 [InlineData(4194304)] // Is multiple of 64, but bigger than the max value of 4194240 public void Configuration_ShouldThrowException_WhenMaxKilobytesIsInvalid(long maxKilobytes) @@ -131,23 +110,23 @@ public void Configuration_ShouldThrowException_WhenMaxKilobytesIsInvalid(long ma "; - NLogConfigurationException ex = Assert.Throws(() => XmlLoggingConfiguration.CreateFromXmlString(configrationText)); - Assert.Equal("MaxKilobytes must be a multiple of 64, and between 64 and 4194240", ex.InnerException.InnerException.Message); + NLogConfigurationException ex = Assert.Throws(() => new LogFactory().Setup().LoadConfigurationFromXml(configrationText)); + Assert.Equal("EventLogTarget MaxKilobytes must be a multiple of 64, and between 64 and 4194240", ex.Message); } [Theory] - [InlineData(0)] // Is multiple of 64, but less than the min value of 64 [InlineData(65)] // Isn't multiple of 64 [InlineData(4194304)] // Is multiple of 64, but bigger than the max value of 4194240 public void MaxKilobytes_ShouldThrowException_WhenMaxKilobytesIsInvalid(long maxKilobytes) { - ArgumentException ex = Assert.Throws(() => + NLogConfigurationException ex = Assert.Throws(() => { var target = new EventLogTarget(); target.MaxKilobytes = maxKilobytes; + target.Initialize(null); }); - Assert.Equal("MaxKilobytes must be a multiple of 64, and between 64 and 4194240", ex.Message); + Assert.Equal("EventLogTarget MaxKilobytes must be a multiple of 64, and between 64 and 4194240", ex.Message); } [Theory] @@ -211,27 +190,14 @@ public void ShouldSetMaxKilobytes_WhenNeeded(long? newValue, long initialValue, MaxMessageLength = MaxMessageLength, MaxKilobytes = newValue, }; - eventLogMock.AssociateNewEventLog(target.Log, target.MachineName, target.GetFixedSource()); + + eventLogMock.AssociateNewEventLog(targetLog, ".", target.GetFixedSource()); target.Install(new InstallationContext()); Assert.Equal(expectedValue, eventLogMock.MaximumKilobytes); } - [Theory] - [InlineData(0)] - [InlineData(-1)] - public void ShouldThrowException_WhenMaxMessageLengthSetNegativeOrZero(int maxMessageLength) - { - ArgumentException ex = Assert.Throws(() => - { - var target = new EventLogTarget(); - target.MaxMessageLength = maxMessageLength; - }); - - Assert.Equal("MaxMessageLength cannot be zero or negative.", ex.Message); - } - [Theory] [InlineData(0, EventLogEntryType.Information, "AtInformationLevel_WhenNLogLevelIsTrace", null)] [InlineData(1, EventLogEntryType.Information, "AtInformationLevel_WhenNLogLevelIsDebug", null)] @@ -424,9 +390,9 @@ public void WriteEventLogEntry_WillRecreate_WhenWrongLogName() // Assert Assert.Equal(sourceName, deletedSourceName); - Assert.Equal(target.Log, createdLogName); + Assert.Equal(target.Log.ToString(), createdLogName); Assert.Single(eventLogMock.WrittenEntries); - Assert.Equal(target.Log, eventLogMock.WrittenEntries[0].Log); + Assert.Equal(target.Log.ToString(), eventLogMock.WrittenEntries[0].Log); Assert.Equal(sourceName, eventLogMock.WrittenEntries[0].Source); Assert.Equal("Hello", eventLogMock.WrittenEntries[0].Message); } @@ -460,7 +426,7 @@ public void WriteEventLogEntry_WillComplain_WhenWrongLogName() // Assert Assert.Equal(string.Empty, deletedSourceName); Assert.Equal(string.Empty, createdLogName); - Assert.Equal(target.Log, eventLogMock.WrittenEntries[0].Log); + Assert.Equal(target.Log.ToString(), eventLogMock.WrittenEntries[0].Log); Assert.Equal(sourceName, eventLogMock.WrittenEntries[0].Source); Assert.Equal($"Hello {sourceName}", eventLogMock.WrittenEntries[0].Message); } @@ -484,7 +450,7 @@ public void WriteEventLogEntryWithDynamicSource() logger.Log(logEvent); - var eventLog = new EventLog(target.Log); + var eventLog = new EventLog(target.Log.ToString()); var entries = GetEventRecords(eventLog.Log).ToList(); entries = entries.Where(a => a.ProviderName == sourceName).ToList(); @@ -515,7 +481,7 @@ public void LogEntryWithStaticEventIdAndCategoryInTargetLayout() LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(target)); var logger = LogManager.GetLogger("WriteEventLogEntry"); logger.Log(LogLevel.Error, "Simple Test Message"); - var eventLog = new EventLog(target.Log); + var eventLog = new EventLog(target.Log.ToString()); var entries = GetEventRecords(eventLog.Log).ToList(); var expectedProviderName = target.GetFixedSource(); var filtered = entries.Where(entry => @@ -543,7 +509,7 @@ public void LogEntryWithDynamicEventIdAndCategory() theEvent.Properties["EventId"] = eventId; theEvent.Properties["Category"] = category; logger.Log(theEvent); - var eventLog = new EventLog(target.Log); + var eventLog = new EventLog(target.Log.ToString()); var entries = GetEventRecords(eventLog.Log).ToList(); var expectedProviderName = target.GetFixedSource(); var filtered = entries.Where(entry => diff --git a/tests/NLog.UnitTests/Targets/FileTargetTests.cs b/tests/NLog.UnitTests/Targets/FileTargetTests.cs index a62356b990..bf234e1378 100644 --- a/tests/NLog.UnitTests/Targets/FileTargetTests.cs +++ b/tests/NLog.UnitTests/Targets/FileTargetTests.cs @@ -46,39 +46,12 @@ namespace NLog.UnitTests.Targets using NLog.Targets; using NLog.Targets.Wrappers; using NLog.Time; - using NSubstitute; using Xunit; public class FileTargetTests : NLogTestBase { private readonly Logger logger = LogManager.GetLogger("NLog.UnitTests.Targets.FileTargetTests"); - public static IEnumerable SimpleFileTest_TestParameters - { - get - { - var booleanValues = new[] { true, false }; - return - from concurrentWrites in booleanValues - from keepFileOpen in booleanValues - from forceMutexConcurrentWrites in booleanValues - where UniqueBaseAppender(concurrentWrites, keepFileOpen, forceMutexConcurrentWrites) - from forceManaged in booleanValues - select new object[] { concurrentWrites, keepFileOpen, forceManaged, forceMutexConcurrentWrites }; - } - } - - private static bool UniqueBaseAppender(bool concurrentWrites, bool keepFileOpen, bool forceMutexConcurrentWrites) - { - if (!keepFileOpen && !concurrentWrites && !forceMutexConcurrentWrites) - return true; - if (!keepFileOpen && concurrentWrites && !forceMutexConcurrentWrites) - return true; - if (keepFileOpen && !forceMutexConcurrentWrites) - return true; - return false; - } - [Fact] public void SetupBuilder_WriteToFile() { @@ -105,22 +78,20 @@ public void SetupBuilder_WriteToFile() } [Theory] - [MemberData(nameof(SimpleFileTest_TestParameters))] - public void SimpleFileTest(bool concurrentWrites, bool keepFileOpen, bool forceManaged, bool forceMutexConcurrentWrites) + [InlineData(true)] + [InlineData(false)] + public void SimpleFileTest(bool keepFileOpen) { var logFile = Path.GetTempFileName(); try { var fileTarget = new FileTarget { - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}", OpenFileCacheTimeout = 0, - ConcurrentWrites = concurrentWrites, KeepFileOpen = keepFileOpen, - ForceManaged = forceManaged, - ForceMutexConcurrentWrites = forceMutexConcurrentWrites, }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -141,22 +112,10 @@ public void SimpleFileTest(bool concurrentWrites, bool keepFileOpen, bool forceM } [Theory] - [MemberData(nameof(SimpleFileTest_TestParameters))] - public void SimpleFileDeleteTest(bool concurrentWrites, bool keepFileOpen, bool forceManaged, bool forceMutexConcurrentWrites) + [InlineData(true)] + [InlineData(false)] + public void SimpleFileDeleteTest(bool keepFileOpen) { - bool isSimpleKeepFileOpen = keepFileOpen && !concurrentWrites -#if !NETSTANDARD && !MONO - && IsLinux() -#endif - ; - -#if MONO - if (IsLinux() && concurrentWrites && keepFileOpen) - { - Console.WriteLine("[SKIP] FileTargetTests.SimpleFileDeleteTest Not supported on MONO on Travis, because of FileSystemWatcher not working"); - return; - } -#endif foreach (var archiveSameFolder in new[] { true, false }) { RetryingIntegrationTest(3, () => @@ -170,17 +129,14 @@ public void SimpleFileDeleteTest(bool concurrentWrites, bool keepFileOpen, bool { var fileTarget = new FileTarget { - FileName = SimpleLayout.Escape(logFile), - ArchiveFileName = SimpleLayout.Escape(arhiveFile), + FileName = Layout.FromLiteral(logFile), + ArchiveFileName = Layout.FromLiteral(arhiveFile), ArchiveEvery = FileArchivePeriod.Year, LineEnding = LineEndingMode.LF, Layout = "${level} ${message}", OpenFileCacheTimeout = 0, EnableFileDelete = true, - ConcurrentWrites = concurrentWrites, KeepFileOpen = keepFileOpen, - ForceManaged = forceManaged, - ForceMutexConcurrentWrites = forceMutexConcurrentWrites, ArchiveAboveSize = archiveSameFolder ? 1000000 : 0, }; @@ -193,10 +149,8 @@ public void SimpleFileDeleteTest(bool concurrentWrites, bool keepFileOpen, bool Directory.CreateDirectory(Path.GetDirectoryName(arhiveFile)); File.Move(logFile, arhiveFile); - if (isSimpleKeepFileOpen) + if (keepFileOpen) Thread.Sleep(1500); // Ensure EnableFileDeleteSimpleMonitor will trigger - else if (keepFileOpen) - Thread.Sleep(150); // Allow AutoClose-Timer-Thread to react (FileWatcher schedules timer after 50 msec) logger.Info("bbb"); @@ -257,7 +211,7 @@ public void SimpleFileTestInRoot() [Fact] public void SimpleFileWithSpecialCharsTest() { - var logFile = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid() + "!@#$%^&()_-=+ .log"); + var logFile = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString() + "!@#$%^&()_-=+ .log"); SimpleFileWriteLogTest(logFile); } @@ -267,7 +221,7 @@ private void SimpleFileWriteLogTest(string logFile) { var fileTarget = new FileTarget { - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}", }; @@ -297,7 +251,7 @@ public void SimpleFileTestWriteBom() { var fileTarget = new FileTarget { - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Encoding = Encoding.UTF8, WriteBom = true, @@ -324,8 +278,9 @@ public void SimpleFileTestWriteBom() /// If a drive doesn't existing, before repeatably creating a dir was tried. This test was taking +60 seconds /// [Theory] - [MemberData(nameof(SimpleFileTest_TestParameters))] - public void NonExistingDriveShouldNotDelayMuch(bool concurrentWrites, bool keepFileOpen, bool forceManaged, bool forceMutexConcurrentWrites) + [InlineData(true)] + [InlineData(false)] + public void NonExistingDriveShouldNotDelayMuch(bool keepFileOpen) { var nonExistingDrive = GetFirstNonExistingDriveWindows(); @@ -341,10 +296,7 @@ public void NonExistingDriveShouldNotDelayMuch(bool concurrentWrites, bool keepF { FileName = logFile, Layout = "${level} ${message}", - ConcurrentWrites = concurrentWrites, KeepFileOpen = keepFileOpen, - ForceManaged = forceManaged, - ForceMutexConcurrentWrites = forceMutexConcurrentWrites, }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -379,11 +331,12 @@ private static char GetFirstNonExistingDriveWindows() "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToList().First(driveLetter => !existingDrives.Contains(driveLetter.ToString())); return nonExistingDrive; } - #endif - [Fact] - public void RollingArchiveEveryMonth() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void RollingArchiveEveryMonday(bool keepFileOpen) { var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var defaultTimeSource = TimeSource.Current; @@ -401,36 +354,35 @@ public void RollingArchiveEveryMonth() var fileTarget = new FileTarget { - FileName = Path.Combine(tempDir, "${date:format=dd}_AppName.log"), + FileName = Path.Combine(tempDir, "${message}_AppName.log"), LineEnding = LineEndingMode.LF, Layout = "${message}", - ArchiveNumbering = ArchiveNumberingMode.Rolling, - ArchiveEvery = FileArchivePeriod.Month, + ArchiveEvery = FileArchivePeriod.Monday, MaxArchiveFiles = 1, + KeepFileOpen = keepFileOpen, }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); - for (int i = 0; i < 12; ++i) + for (int i = 0; i < 3; ++i) { - for (int j = 0; j < 31; ++j) + for (char j = 'A'; j < 'Z'; ++j) { - logger.Debug("aaa"); + logger.Debug(new string(j, 2)); timeSource.AddToLocalTime(TimeSpan.FromDays(1)); - timeSource.AddToSystemTime(TimeSpan.FromDays(1)); + //timeSource.AddToSystemTime(TimeSpan.FromDays(1)); } } - var files = Directory.GetFiles(tempDir); - // Cleanup doesn't work, as all file names has the same timestamp - if (files.Length < 28 || files.Length > 31) - Assert.Equal(30, files.Length); + LogManager.Configuration = null; // Flush and close + + var files = new DirectoryInfo(tempDir).GetFiles(); + Assert.Equal(25, files.Length); foreach (var file in files) { - Assert.Equal(14, Path.GetFileName(file).Length); + Assert.Equal(14, file.Name.Length); + Assert.Equal(3, file.Length); } - - fileTarget.Close(); } finally { @@ -446,15 +398,15 @@ public void RollingArchiveEveryMonth() #else [Theory(Skip="Not supported on MONO on Travis, because of File birthtime not working")] #endif - [InlineData(false, false, ArchiveNumberingMode.DateAndSequence)] - [InlineData(false, true, ArchiveNumberingMode.DateAndSequence)] - [InlineData(false, false, ArchiveNumberingMode.Sequence)] - [InlineData(false, true, ArchiveNumberingMode.Sequence)] - [InlineData(true, false, ArchiveNumberingMode.DateAndSequence)] - [InlineData(true, true, ArchiveNumberingMode.DateAndSequence)] - [InlineData(true, false, ArchiveNumberingMode.Sequence)] - [InlineData(true, true, ArchiveNumberingMode.Sequence)] - public void DatedArchiveEveryMonth(bool archiveSubFolder, bool maxArchiveDays, ArchiveNumberingMode archiveNumberingMode) + [InlineData(false, false, true)] + [InlineData(false, true, true)] + [InlineData(false, false, false)] + [InlineData(false, true, false)] + [InlineData(true, false, true)] + [InlineData(true, true, true)] + [InlineData(true, false, false)] + [InlineData(true, true, false)] + public void DatedArchiveEveryMonth(bool archiveSubFolder, bool maxArchiveDays, bool archiveWithDateAndSequence) { if (IsLinux()) { @@ -509,13 +461,14 @@ public void DatedArchiveEveryMonth(bool archiveSubFolder, bool maxArchiveDays, A Encoding = Encoding.ASCII, Layout = "${message}", KeepFileOpen = i % 2 != 0, - ArchiveFileName = archiveSubFolder ? Path.Combine(archiveDir, "AppName.{#}.log") : (Layout)null, - ArchiveNumbering = archiveNumberingMode, - ArchiveEvery = FileArchivePeriod.Month, - ArchiveDateFormat = "yyyyMMdd", - MaxArchiveFiles = maxArchiveDays ? 0 : 2, - MaxArchiveDays = maxArchiveDays ? 5 * 30 : 0 + ArchiveFileName = archiveSubFolder ? Path.Combine(archiveDir, "AppName.{#}.log") : Path.Combine(tempDir, "AppName.{#}.log"), + ArchiveSuffixFormat = archiveWithDateAndSequence ? "_{1:yyyyMMdd}_{0:0}" : "_{0:0}", + ArchiveEvery = FileArchivePeriod.Month }; + if (maxArchiveDays) + fileTarget.MaxArchiveDays = 5 * 30; + else + fileTarget.MaxArchiveFiles = 2; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); logger.Debug($"{i.ToString()}{i.ToString()}{i.ToString()}"); @@ -533,13 +486,13 @@ public void DatedArchiveEveryMonth(bool archiveSubFolder, bool maxArchiveDays, A Assert.Empty(newFile); newFile = fileName; - if (archiveNumberingMode == ArchiveNumberingMode.DateAndSequence && createdFiles.Count > 1) + if (archiveWithDateAndSequence && createdFiles.Count > 1) { // Verify it used the last-modified-time (And not file-creation-time) string dateName = string.Empty; dateName = Path.GetFileName(fileName); - dateName = dateName.Replace("AppName.", ""); - dateName = dateName.Replace(".0.log", ""); + dateName = dateName.Replace("AppName_", ""); + dateName = dateName.Replace("_0.log", ""); dateName = dateName.Replace("log", ""); Assert.NotEmpty(dateName); Assert.Equal(timeSource.Time.Month, DateTime.ParseExact(dateName, "yyyyMMdd", null).Month); @@ -592,12 +545,11 @@ public void CsvHeaderTest() var fileTarget = new FileTarget { - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = layout, OpenFileCacheTimeout = 0, - ReplaceFileContentsOnEachWrite = false, - ArchiveAboveSize = 120, // Only 2 LogEvents per file + ArchiveAboveSize = 100, // Only 2 LogEvents per file MaxArchiveFiles = 1, }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -643,7 +595,7 @@ public void DeleteFileOnStartTest() var fileTarget = new FileTarget { DeleteOldFileOnStartup = false, - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}" }; @@ -664,7 +616,7 @@ public void DeleteFileOnStartTest() fileTarget = new FileTarget { DeleteOldFileOnStartup = false, - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}" }; @@ -683,7 +635,7 @@ public void DeleteFileOnStartTest() fileTarget = new FileTarget { - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}", DeleteOldFileOnStartup = true @@ -744,52 +696,21 @@ public void DeleteFileOnStartTest_noExceptionWhenMissing() } } -#if !NETSTANDARD - public static IEnumerable ArchiveFileOnStartTests_TestParameters - { - get - { - var booleanValues = new[] { true, false }; - return - from enableCompression in booleanValues - from customFileCompressor in booleanValues - select new object[] { enableCompression, customFileCompressor }; - } - } -#else - public static IEnumerable ArchiveFileOnStartTests_TestParameters - { - get - { - var booleanValues = new[] { true, false }; - return - from enableCompression in booleanValues - select new object[] { enableCompression, false }; - } - } -#endif - - [Theory] - [MemberData(nameof(ArchiveFileOnStartTests_TestParameters))] - public void ArchiveFileOnStartTests(bool enableCompression, bool customFileCompressor) + [Fact] + public void ArchiveFileOnStartTests() { var logFile = Path.GetTempFileName() + ".txt"; - var tempArchiveFolder = Path.Combine(Path.GetTempPath(), "Archive"); - var archiveExtension = enableCompression ? "zip" : "txt"; - IFileCompressor fileCompressor = null; + var tempArchiveFolder = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString(), "Archive"); + var archiveExtension = "txt"; + try { - if (customFileCompressor) - { - fileCompressor = FileTarget.FileCompressor; - FileTarget.FileCompressor = new CustomFileCompressor(); - } - // Configure first time with ArchiveOldFileOnStartup = false. var fileTarget = new FileTarget { ArchiveOldFileOnStartup = false, - FileName = SimpleLayout.Escape(logFile), + ArchiveSuffixFormat = "", + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}" }; @@ -809,7 +730,7 @@ public void ArchiveFileOnStartTests(bool enableCompression, bool customFileCompr fileTarget = new FileTarget { ArchiveOldFileOnStartup = false, - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}" }; @@ -833,13 +754,12 @@ public void ArchiveFileOnStartTests(bool enableCompression, bool customFileCompr FileTarget ft; fileTarget = ft = new FileTarget { - EnableArchiveFileCompression = enableCompression, - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}", ArchiveOldFileOnStartup = true, ArchiveFileName = archiveTempName, - ArchiveNumbering = ArchiveNumberingMode.Sequence, + ArchiveSuffixFormat = "", MaxArchiveFiles = 1 }; @@ -853,9 +773,7 @@ public void ArchiveFileOnStartTests(bool enableCompression, bool customFileCompr AssertFileContents(logFile, "Debug ddd\nInfo eee\nWarn fff\n", Encoding.UTF8); Assert.True(File.Exists(archiveTempName)); - var assertFileContents = ft.EnableArchiveFileCompression ? - new Action(AssertZipFileContents) : - AssertFileContents; + Action assertFileContents = (f1, f2, content, encoding) => AssertFileContents(f1, content, encoding, false); #if !NET35 string expectedEntryName = Path.GetFileNameWithoutExtension(archiveTempName) + ".txt"; @@ -867,8 +785,6 @@ public void ArchiveFileOnStartTests(bool enableCompression, bool customFileCompr } finally { - if (customFileCompressor) - FileTarget.FileCompressor = fileCompressor; if (File.Exists(logFile)) File.Delete(logFile); if (Directory.Exists(tempArchiveFolder)) @@ -880,18 +796,18 @@ public void ArchiveFileOnStartTests(bool enableCompression, bool customFileCompr public void ArchiveOldFileOnStartupAboveSize() { var logFile = Path.GetTempFileName(); - var tempArchiveFolder = Path.Combine(Path.GetTempPath(), "Archive"); + var tempArchiveFolder = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var archiveTempName = Path.Combine(tempArchiveFolder, "archive_size_threshold.txt"); FileTarget CreateTestTarget(long threshold) { return new FileTarget { - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}", - ArchiveOldFileOnStartupAboveSize = threshold, + ArchiveAboveSize = threshold, ArchiveFileName = archiveTempName, - ArchiveNumbering = ArchiveNumberingMode.Sequence, + ArchiveSuffixFormat = "", MaxArchiveFiles = 1 }; } @@ -925,20 +841,18 @@ FileTarget CreateTestTarget(long threshold) public void ArchiveOldFileOnStartupAboveSizeWhenFileLocked() { var logFile = Path.GetTempFileName(); - var tempArchiveFolder = Path.Combine(Path.GetTempPath(), "Archive"); + var tempArchiveFolder = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString(), "Archive"); var archiveTempName = Path.Combine(tempArchiveFolder, "archive_size_threshold.zip"); FileTarget CreateTestTarget(long threshold) { return new FileTarget { - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}", - ArchiveOldFileOnStartupAboveSize = threshold, + ArchiveAboveSize = threshold, ArchiveFileName = archiveTempName, - ArchiveNumbering = ArchiveNumberingMode.Sequence, - EnableArchiveFileCompression = true, MaxArchiveFiles = 1 }; } @@ -982,11 +896,10 @@ public void RetryFileOpenWhenFileLocked() var fileTarget = new FileTarget("file") { - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}", KeepFileOpen = false, - ConcurrentWriteAttempts = 100, }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -1032,7 +945,7 @@ public void ReplaceFileContentsOnEachWriteTest(bool useHeader, bool useFooter) var fileTarget = new FileTarget { DeleteOldFileOnStartup = false, - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), ReplaceFileContentsOnEachWrite = true, LineEnding = LineEndingMode.LF, Layout = "${level} ${message}" @@ -1072,35 +985,36 @@ public void ReplaceFileContentsOnEachWriteTest(bool useHeader, bool useFooter) [InlineData(false)] public void ReplaceFileContentsOnEachWrite_CreateDirs(bool createDirs) { - var tempDir = Path.Combine(Path.GetTempPath(), "dir_" + Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logfile = Path.Combine(tempDir, "log.log"); try { - using (new NoThrowNLogExceptions()) + LogManager.ThrowExceptions = false; + + var target = new FileTarget { - var target = new FileTarget - { - FileName = logfile, - ReplaceFileContentsOnEachWrite = true, - CreateDirs = createDirs - }; - var config = new LoggingConfiguration(); + FileName = logfile, + ReplaceFileContentsOnEachWrite = true, + CreateDirs = createDirs + }; + var config = new LoggingConfiguration(); - config.AddTarget("logfile", target); + config.AddTarget("logfile", target); - config.AddRuleForAllLevels(target); + config.AddRuleForAllLevels(target); - LogManager.Configuration = config; + LogManager.Configuration = config; - var logger = LogManager.GetLogger("A"); - logger.Info("a"); + var logger = LogManager.GetLogger("A"); + logger.Info("a"); - Assert.Equal(createDirs, Directory.Exists(tempDir)); - } + Assert.Equal(createDirs, Directory.Exists(tempDir)); } finally { + LogManager.ThrowExceptions = true; + if (File.Exists(logfile)) File.Delete(logfile); if (Directory.Exists(tempDir)) @@ -1111,7 +1025,7 @@ public void ReplaceFileContentsOnEachWrite_CreateDirs(bool createDirs) [Fact] public void CreateDirsTest() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -1142,12 +1056,15 @@ public void CreateDirsTest() } [Theory] - [InlineData(true, 0)] - [InlineData(false, 0)] - [InlineData(false, 1)] - public void AutoFlushTest(bool autoFlush, int autoFlushTimeout) + [InlineData(true, true, 0)] + [InlineData(false, true, 0)] + [InlineData(false, true, 1)] + [InlineData(true, false, 0)] + [InlineData(false, false, 0)] + [InlineData(false, false, 1)] + public void AutoFlushTest(bool autoFlush, bool keepFileOpen, int autoFlushTimeout) { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -1156,11 +1073,11 @@ public void AutoFlushTest(bool autoFlush, int autoFlushTimeout) FileName = logFile, LineEnding = LineEndingMode.LF, Layout = "${level} ${message}", - KeepFileOpen = true, - ConcurrentWrites = false, - AutoFlush = autoFlush, + KeepFileOpen = keepFileOpen, OpenFileFlushTimeout = autoFlushTimeout, }; + if (!autoFlush) + fileTarget.AutoFlush = autoFlush; // Also ensures default-value is valid LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -1168,7 +1085,7 @@ public void AutoFlushTest(bool autoFlush, int autoFlushTimeout) logger.Info("bbb"); logger.Warn("ccc"); - if (autoFlush) + if (autoFlush || !keepFileOpen) { AssertFileContents(logFile, "Debug aaa\nInfo bbb\nWarn ccc\n", Encoding.UTF8); } @@ -1198,7 +1115,7 @@ public void AutoFlushTest(bool autoFlush, int autoFlushTimeout) [Fact] public void SequentialArchiveTest() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -1206,12 +1123,11 @@ public void SequentialArchiveTest() var fileTarget = new FileTarget { FileName = logFile, - ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveFileName = Path.Combine(archiveFolder, "{##}.txt"), ArchiveAboveSize = 100, LineEnding = LineEndingMode.LF, Layout = "${message}", MaxArchiveFiles = 3, - ArchiveNumbering = ArchiveNumberingMode.Sequence }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -1232,23 +1148,23 @@ public void SequentialArchiveTest() Encoding.UTF8); AssertFileContents( - Path.Combine(archiveFolder, "0001.txt"), + Path.Combine(archiveFolder, "01.txt"), StringRepeat(times, "bbb\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(archiveFolder, "0002.txt"), + Path.Combine(archiveFolder, "02.txt"), StringRepeat(times, "ccc\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(archiveFolder, "0003.txt"), + Path.Combine(archiveFolder, "03.txt"), StringRepeat(times, "ddd\n"), Encoding.UTF8); //0000 should not exists because of MaxArchiveFiles=3 - Assert.False(File.Exists(Path.Combine(archiveFolder, "0000.txt"))); - Assert.False(File.Exists(Path.Combine(archiveFolder, "0004.txt"))); + Assert.False(File.Exists(Path.Combine(archiveFolder, "00.txt"))); + Assert.False(File.Exists(Path.Combine(archiveFolder, "04.txt"))); } finally { @@ -1260,9 +1176,9 @@ public void SequentialArchiveTest() } [Fact] - public void SequentialArchiveTest_MaxArchiveFiles_0() + public void SequentialArchiveTest_MaxArchiveFiles_NoLimit() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -1270,12 +1186,10 @@ public void SequentialArchiveTest_MaxArchiveFiles_0() var fileTarget = new FileTarget { FileName = logFile, - ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveFileName = Path.Combine(archiveFolder, "{##}.txt"), ArchiveAboveSize = 100, LineEnding = LineEndingMode.LF, - ArchiveNumbering = ArchiveNumberingMode.Sequence, Layout = "${message}", - MaxArchiveFiles = 0 }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -1295,26 +1209,26 @@ public void SequentialArchiveTest_MaxArchiveFiles_0() Encoding.UTF8); AssertFileContents( - Path.Combine(archiveFolder, "0000.txt"), + Path.Combine(archiveFolder, "00.txt"), StringRepeat(25, "aaa\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(archiveFolder, "0001.txt"), + Path.Combine(archiveFolder, "01.txt"), StringRepeat(25, "bbb\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(archiveFolder, "0002.txt"), + Path.Combine(archiveFolder, "02.txt"), StringRepeat(25, "ccc\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(archiveFolder, "0003.txt"), + Path.Combine(archiveFolder, "03.txt"), StringRepeat(25, "ddd\n"), Encoding.UTF8); - Assert.False(File.Exists(Path.Combine(archiveFolder, "0004.txt"))); + Assert.False(File.Exists(Path.Combine(archiveFolder, "04.txt"))); } finally { @@ -1326,27 +1240,29 @@ public void SequentialArchiveTest_MaxArchiveFiles_0() } [Fact] - public void ArchiveAboveSizeWithArchiveNumberingModeDate_maxfiles_o() + public void ArchiveAboveSize_AppendSameArchiveDate() { - var tempDir = Path.Combine(Path.GetTempPath(), "ArchiveEveryCombinedWithArchiveAboveSize_" + Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { + var archiveDateFormat = "yyyyMMdd"; + string archiveFolder = Path.Combine(tempDir, "archive"); var fileTarget = new FileTarget { FileName = logFile, - ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveFileName = Path.Combine(archiveFolder, ".txt"), + ArchiveSuffixFormat = "{1:" + archiveDateFormat + "}", ArchiveAboveSize = 100, LineEnding = LineEndingMode.LF, Layout = "${message}", - ArchiveNumbering = ArchiveNumberingMode.Date }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); //e.g. 20150804 - var archiveFileName = DateTime.Now.ToString("yyyyMMdd"); + var archiveFileName = DateTime.Now.ToString(archiveDateFormat); // we emit 5 * 25 *(3 x aaa + \n) bytes // so that we should get a full file + 3 archives @@ -1355,7 +1271,6 @@ public void ArchiveAboveSizeWithArchiveNumberingModeDate_maxfiles_o() { logger.Debug("aaa"); } - for (var i = 0; i < times; ++i) { logger.Debug("bbb"); @@ -1401,7 +1316,7 @@ public void DeleteArchiveFilesByDate() { const int maxArchiveFiles = 3; - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -1412,16 +1327,17 @@ public void DeleteArchiveFilesByDate() ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), ArchiveAboveSize = 50, LineEnding = LineEndingMode.LF, - ArchiveNumbering = ArchiveNumberingMode.Date, +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = "yyyyMMddHHmmssfff", //make sure the milliseconds are set in the filename +#pragma warning restore CS0618 // Type or member is obsolete Layout = "${message}", MaxArchiveFiles = maxArchiveFiles }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); - //writing 19 times 10 bytes (9 char + linefeed) will result in 3 archive files and 1 current file - for (var i = 0; i < 19; ++i) + //writing 20 times 10 bytes (9 char + linefeed) will result in 3 archive files and 1 current file + for (var i = 0; i < 20; ++i) { logger.Debug("123456789"); //build in a small sleep to make sure the current time is reflected in the filename @@ -1470,7 +1386,7 @@ public void DeleteArchiveFilesByDateWithDateName() { const int maxArchiveFiles = 3; LogManager.ThrowExceptions = true; - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); try { var logFile = Path.Combine(tempDir, "${date:format=yyyyMMddHHmmssfff}.txt"); @@ -1480,10 +1396,11 @@ public void DeleteArchiveFilesByDateWithDateName() ArchiveFileName = Path.Combine(tempDir, "{#}.txt"), ArchiveEvery = FileArchivePeriod.Year, LineEnding = LineEndingMode.LF, - ArchiveNumbering = ArchiveNumberingMode.Date, +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = "yyyyMMddHHmmssfff", //make sure the milliseconds are set in the filename +#pragma warning restore CS0618 // Type or member is obsolete Layout = "${message}", - MaxArchiveFiles = maxArchiveFiles + MaxArchiveFiles = maxArchiveFiles, }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -1531,28 +1448,22 @@ public static IEnumerable DateArchive_UsesDateFromCurrentTimeSource_Te { get { - var maxArchiveDays = false; var booleanValues = new[] { true, false }; var timeKindValues = new[] { DateTimeKind.Utc, DateTimeKind.Local }; return from timeKind in timeKindValues - from includeDateInLogFilePath in booleanValues - from concurrentWrites in booleanValues from keepFileOpen in booleanValues - from forceMutexConcurrentWrites in booleanValues - where UniqueBaseAppender(concurrentWrites, keepFileOpen, forceMutexConcurrentWrites) - from includeSequenceInArchive in booleanValues - from forceManaged in booleanValues - select new object[] { timeKind, includeDateInLogFilePath, concurrentWrites, keepFileOpen, includeSequenceInArchive, forceManaged, forceMutexConcurrentWrites, maxArchiveDays }; + from maxArchiveDays in booleanValues + select new object[] { timeKind, keepFileOpen, maxArchiveDays }; } } [Theory] [MemberData(nameof(DateArchive_UsesDateFromCurrentTimeSource_TestParameters))] - public void DateArchive_UsesDateFromCurrentTimeSource(DateTimeKind timeKind, bool includeDateInLogFilePath, bool concurrentWrites, bool keepFileOpen, bool includeSequenceInArchive, bool forceManaged, bool forceMutexConcurrentWrites, bool maxArhiveDays) + public void DateArchive_UsesDateFromCurrentTimeSource(DateTimeKind timeKind, bool keepFileOpen, bool maxArchiveDays) { -#if NETSTANDARD || MONO - if (IsLinux()) +#if !NETFRAMEWORK || MONO + if (IsLinux() && !maxArchiveDays) { Console.WriteLine("[SKIP] FileTargetTests.DateArchive_UsesDateFromCurrentTimeSource because SetLastWriteTime is not working on Travis"); return; @@ -1562,8 +1473,8 @@ public void DateArchive_UsesDateFromCurrentTimeSource(DateTimeKind timeKind, boo const string archiveDateFormat = "yyyyMMdd"; const int maxArchiveFiles = 3; - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var logFile = Path.Combine(tempDir, includeDateInLogFilePath ? "file_${shortdate}.txt" : "file.txt"); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); var defaultTimeSource = TimeSource.Current; try { @@ -1578,18 +1489,18 @@ public void DateArchive_UsesDateFromCurrentTimeSource(DateTimeKind timeKind, boo FileName = logFile, ArchiveFileName = archiveFileNameTemplate, LineEnding = LineEndingMode.LF, - ArchiveNumbering = includeSequenceInArchive ? ArchiveNumberingMode.DateAndSequence : ArchiveNumberingMode.Date, ArchiveEvery = FileArchivePeriod.Day, +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = archiveDateFormat, +#pragma warning restore CS0618 // Type or member is obsolete Layout = "${date:format=O}|${message}", - MaxArchiveFiles = maxArhiveDays ? 0 : maxArchiveFiles, - MaxArchiveDays = maxArhiveDays ? maxArchiveFiles : 0, - ConcurrentWrites = concurrentWrites, KeepFileOpen = keepFileOpen, - ForceManaged = forceManaged, - ForceMutexConcurrentWrites = forceMutexConcurrentWrites, Header = "header", }; + if (maxArchiveDays) + fileTarget.MaxArchiveDays = maxArchiveFiles; + else + fileTarget.MaxArchiveFiles = maxArchiveFiles; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -1605,24 +1516,20 @@ public void DateArchive_UsesDateFromCurrentTimeSource(DateTimeKind timeKind, boo if (timeSource.Time.Date != previousWriteTime.Date) { - string currentLogFile = includeDateInLogFilePath - ? logFile.Replace("${shortdate}", timeSource.Time.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)) - : logFile; // Simulate that previous file write began in previous day and ended on current day. try { - File.SetLastWriteTime(currentLogFile, timeSource.SystemTime); + File.SetLastWriteTime(logFile, timeSource.SystemTime); } catch { } } var eventInfo = new LogEventInfo(LogLevel.Debug, logger.Name, "123456789"); logger.Log(eventInfo); - LogManager.Flush(); var dayIsChanged = eventInfo.TimeStamp.Date != previousWriteTime.Date; // ensure new archive is created only when the day part of time is changed - var archiveFileName = archiveFileNameTemplate.Replace("{#}", previousWriteTime.ToString(archiveDateFormat) + (includeSequenceInArchive ? ".0" : string.Empty)); + var archiveFileName = archiveFileNameTemplate.Replace("{#}", previousWriteTime.ToString(archiveDateFormat) + "_00"); var archiveExists = File.Exists(archiveFileName); if (dayIsChanged) Assert.True(archiveExists, @@ -1631,7 +1538,7 @@ public void DateArchive_UsesDateFromCurrentTimeSource(DateTimeKind timeKind, boo Assert.False(archiveExists, $"new archive should not be create when day part of {timeKind} time is unchanged"); - previousWriteTime = eventInfo.TimeStamp.Date; + previousWriteTime = eventInfo.TimeStamp; if (dayIsChanged) timeSource.AddToSystemTime(TimeSpan.FromDays(1)); } @@ -1648,21 +1555,24 @@ public void DateArchive_UsesDateFromCurrentTimeSource(DateTimeKind timeKind, boo LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); //writing one line on a new day will trigger the cleanup of old archived files //as stated by the MaxArchiveFiles property, but will only delete the oldest file - timeSource.AddToLocalTime(TimeSpan.FromDays(1)); + timeSource.AddToLocalTime(TimeSpan.FromDays(3)); logger.Debug("1234567890"); LogManager.Configuration = null; // Flush - var files2 = Directory.GetFiles(archiveFolder); - Assert.Equal(maxArchiveFiles, files2.Length); - - //the oldest file should be deleted - Assert.DoesNotContain(files[0], files2); - //two files should still be there - Assert.Equal(files[1], files2[0]); - Assert.Equal(files[2], files2[1]); - //one new archive file should be created - Assert.DoesNotContain(files2[2], files); + if (!maxArchiveDays) + { + var files2 = Directory.GetFiles(archiveFolder); + Assert.Equal(maxArchiveFiles, files2.Length); + + //the oldest file should be deleted + Assert.DoesNotContain(files[0], files2); + //two files should still be there + Assert.Equal(files[1], files2[0]); + Assert.Equal(files[2], files2[1]); + //one new archive file should be created + Assert.DoesNotContain(files2[2], files); + } } finally { @@ -1672,44 +1582,24 @@ public void DateArchive_UsesDateFromCurrentTimeSource(DateTimeKind timeKind, boo } } - [Theory] - [InlineData(DateTimeKind.Utc, false, false)] - [InlineData(DateTimeKind.Local, false, false)] - [InlineData(DateTimeKind.Utc, true, false)] - [InlineData(DateTimeKind.Local, true, false)] - [InlineData(DateTimeKind.Utc, false, true)] - [InlineData(DateTimeKind.Local, false, true)] - [InlineData(DateTimeKind.Utc, true, true)] - [InlineData(DateTimeKind.Local, true, true)] - public void DateArchive_UsesDateFromCurrentTimeSource_MaxArchiveDays(DateTimeKind timeKind, bool includeDateInLogFilePath, bool includeSequenceInArchive) - { - const bool MaxArchiveDays = true; - DateArchive_UsesDateFromCurrentTimeSource(timeKind, includeDateInLogFilePath, false, false, includeSequenceInArchive, false, false, MaxArchiveDays); - } - public static IEnumerable DateArchive_ArchiveOnceOnly_TestParameters { get { var booleanValues = new[] { true, false }; return - from concurrentWrites in booleanValues from keepFileOpen in booleanValues - from forceMutexConcurrentWrites in booleanValues - where UniqueBaseAppender(concurrentWrites, keepFileOpen, forceMutexConcurrentWrites) - from includeDateInLogFilePath in booleanValues from includeSequenceInArchive in booleanValues - from forceManaged in booleanValues - select new object[] { concurrentWrites, keepFileOpen, includeDateInLogFilePath, includeSequenceInArchive, forceManaged, forceMutexConcurrentWrites }; + select new object[] { keepFileOpen, includeSequenceInArchive }; } } [Theory] [MemberData(nameof(DateArchive_ArchiveOnceOnly_TestParameters))] - public void DateArchive_ArchiveOnceOnly(bool concurrentWrites, bool keepFileOpen, bool dateInLogFilePath, bool includeSequenceInArchive, bool forceManaged, bool forceMutexConcurrentWrites) + public void DateArchive_ArchiveOnceOnly(bool keepFileOpen, bool includeSequenceInArchive) { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var logFile = Path.Combine(tempDir, dateInLogFilePath ? "file_${shortdate}.txt" : "file.txt"); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); var defaultTimeSource = TimeSource.Current; @@ -1730,15 +1620,15 @@ public void DateArchive_ArchiveOnceOnly(bool concurrentWrites, bool keepFileOpen FileName = logFile, ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), LineEnding = LineEndingMode.LF, - ArchiveNumbering = includeSequenceInArchive ? ArchiveNumberingMode.DateAndSequence : ArchiveNumberingMode.Date, ArchiveEvery = FileArchivePeriod.Day, +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = "yyyyMMdd", +#pragma warning restore CS0618 // Type or member is obsolete Layout = "${message}", - ConcurrentWrites = concurrentWrites, KeepFileOpen = keepFileOpen, - ForceManaged = forceManaged, - ForceMutexConcurrentWrites = forceMutexConcurrentWrites, }; + if (!includeSequenceInArchive) + fileTarget.ArchiveSuffixFormat = @"_{1:yyyyMMdd}"; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -1747,8 +1637,7 @@ public void DateArchive_ArchiveOnceOnly(bool concurrentWrites, bool keepFileOpen LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); timeSource.AddToLocalTime(TimeSpan.FromDays(1)); - if (!dateInLogFilePath) - File.SetCreationTimeUtc(logFile, timeSource.Time.ToUniversalTime()); + File.SetCreationTimeUtc(logFile, timeSource.Time.ToUniversalTime()); timeSource.AddToLocalTime(TimeSpan.FromDays(1)); // This should archive the log before logging. @@ -1789,18 +1678,17 @@ public static IEnumerable DateArchive_SkipPeriod_TestParameters return from timeKind in timeKindValues from archivePeriod in archivePeriodValues - from includeDateInLogFilePath in booleanValues from includeSequenceInArchive in booleanValues - select new object[] { timeKind, archivePeriod, includeDateInLogFilePath, includeSequenceInArchive }; + select new object[] { timeKind, archivePeriod, includeSequenceInArchive }; } } [Theory] [MemberData(nameof(DateArchive_SkipPeriod_TestParameters))] - public void DateArchive_SkipPeriod(DateTimeKind timeKind, FileArchivePeriod archivePeriod, bool includeDateInLogFilePath, bool includeSequenceInArchive) + public void DateArchive_SkipPeriod(DateTimeKind timeKind, FileArchivePeriod archivePeriod, bool includeSequenceInArchive) { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var logFile = Path.Combine(tempDir, includeDateInLogFilePath ? "file_${date:format=yyyyMMddHHmm}.txt" : "file.txt"); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "file.txt"); var defaultTimeSource = TimeSource.Current; try { @@ -1822,12 +1710,18 @@ public void DateArchive_SkipPeriod(DateTimeKind timeKind, FileArchivePeriod arch FileName = logFile, ArchiveFileName = Path.Combine(tempDir, "archive", "{#}.txt"), LineEnding = LineEndingMode.LF, - ArchiveNumbering = includeSequenceInArchive ? ArchiveNumberingMode.DateAndSequence : ArchiveNumberingMode.Date, ArchiveEvery = archivePeriod, +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = "yyyyMMddHHmm", +#pragma warning restore CS0618 // Type or member is obsolete Layout = "${date:format=O}|${message}", }; + if (!includeSequenceInArchive) + fileTarget.ArchiveSuffixFormat = @"_{1:yyyyMMddHHmm}"; + +#pragma warning disable CS0618 // Type or member is obsolete string archiveDateFormat = fileTarget.ArchiveDateFormat; +#pragma warning restore CS0618 // Type or member is obsolete LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); logger.Debug("1234567890"); @@ -1835,7 +1729,7 @@ public void DateArchive_SkipPeriod(DateTimeKind timeKind, FileArchivePeriod arch logger.Debug("1234567890"); // The archive file name must be based on the last time the file was written. string archiveFileName = - $"{timeSource.Time.ToString(archiveDateFormat) + (includeSequenceInArchive ? ".0" : string.Empty)}.txt"; + $"{timeSource.Time.ToString(archiveDateFormat) + (includeSequenceInArchive ? "_00" : string.Empty)}.txt"; // Effectively update the file's last-write-time. timeSource.AddToSystemTime(TimeSpan.FromMinutes(1)); @@ -1857,44 +1751,15 @@ public void DateArchive_SkipPeriod(DateTimeKind timeKind, FileArchivePeriod arch } } - public static IEnumerable DateArchive_AllLoggersTransferToCurrentLogFile_TestParameters - { - get - { - var booleanValues = new[] { true, false }; - return - from concurrentWrites in booleanValues - from keepFileOpen in booleanValues - from forceMutexConcurrentWrites in booleanValues - where UniqueBaseAppender(concurrentWrites, keepFileOpen, forceMutexConcurrentWrites) - from includeDateInLogFilePath in booleanValues - from includeSequenceInArchive in booleanValues - from enableArchiveCompression in booleanValues - from forceManaged in booleanValues - select new object[] { concurrentWrites, keepFileOpen, includeDateInLogFilePath, includeSequenceInArchive, enableArchiveCompression, forceManaged, forceMutexConcurrentWrites }; - } - } - [Theory] - [MemberData(nameof(DateArchive_AllLoggersTransferToCurrentLogFile_TestParameters))] - public void DateArchive_AllLoggersTransferToCurrentLogFile(bool concurrentWrites, bool keepFileOpen, bool includeDateInLogFilePath, bool includeSequenceInArchive, bool enableArchiveCompression, bool forceManaged, bool forceMutexConcurrentWrites) + [InlineData(true)] + [InlineData(false)] + public void DateArchive_AllLoggersTransferToCurrentLogFile(bool includeSequenceInArchive) { - if (keepFileOpen && !concurrentWrites) - return; // This combination do not support two local FileTargets to the same file - -#if NET35 || NET40 - if (enableArchiveCompression) - return; // No need to test with compression -#endif - - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var logfile = Path.Combine(tempDir, includeDateInLogFilePath ? "file_${shortdate}.txt" : "file.txt"); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logfile = Path.Combine(tempDir, "file.txt"); var defaultTimeSource = TimeSource.Current; -#if NET35 || NET40 - IFileCompressor fileCompressor = null; -#endif - try { var timeSource = new TimeSourceTests.ShiftedTimeSource(DateTimeKind.Local); @@ -1908,30 +1773,22 @@ public void DateArchive_AllLoggersTransferToCurrentLogFile(bool concurrentWrites var config = new LoggingConfiguration(); -#if NET35 || NET40 - if (enableArchiveCompression) - { - fileCompressor = FileTarget.FileCompressor; - FileTarget.FileCompressor = new CustomFileCompressor(); - } -#endif - string archiveFolder = Path.Combine(tempDir, "archive"); var fileTarget1 = new FileTarget { FileName = logfile, ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), LineEnding = LineEndingMode.LF, - ArchiveNumbering = includeSequenceInArchive ? ArchiveNumberingMode.DateAndSequence : ArchiveNumberingMode.Date, ArchiveEvery = FileArchivePeriod.Day, +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = "yyyyMMdd", - EnableArchiveFileCompression = enableArchiveCompression, +#pragma warning restore CS0618 // Type or member is obsolete Layout = "${message}", - ConcurrentWrites = concurrentWrites, - KeepFileOpen = keepFileOpen, - ForceManaged = forceManaged, - ForceMutexConcurrentWrites = forceMutexConcurrentWrites, + KeepFileOpen = false, // KeepFileOpen = false is required when 2 file-targets to the same file }; + if (!includeSequenceInArchive) + fileTarget1.ArchiveSuffixFormat = @"_{1:yyyyMMdd}"; + var logger1Rule = new LoggingRule("logger1", LogLevel.Debug, fileTarget1); config.LoggingRules.Add(logger1Rule); @@ -1940,16 +1797,16 @@ public void DateArchive_AllLoggersTransferToCurrentLogFile(bool concurrentWrites FileName = logfile, ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), LineEnding = LineEndingMode.LF, - ArchiveNumbering = includeSequenceInArchive ? ArchiveNumberingMode.DateAndSequence : ArchiveNumberingMode.Date, ArchiveEvery = FileArchivePeriod.Day, +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = "yyyyMMdd", - EnableArchiveFileCompression = enableArchiveCompression, +#pragma warning restore CS0618 // Type or member is obsolete Layout = "${message}", - ConcurrentWrites = concurrentWrites, - KeepFileOpen = keepFileOpen, - ForceManaged = forceManaged, - ForceMutexConcurrentWrites = forceMutexConcurrentWrites, + KeepFileOpen = false, // KeepFileOpen = false is required when 2 file-targets to the same file }; + if (!includeSequenceInArchive) + fileTarget2.ArchiveSuffixFormat = @"_{1:yyyyMMdd}"; + var logger2Rule = new LoggingRule("logger2", LogLevel.Debug, fileTarget2); config.LoggingRules.Add(logger2Rule); @@ -1960,27 +1817,25 @@ public void DateArchive_AllLoggersTransferToCurrentLogFile(bool concurrentWrites logger1.Debug("123456789"); logger2.Debug("123456789"); - LogManager.Flush(); timeSource.AddToLocalTime(TimeSpan.FromDays(1)); + Thread.Sleep(50); // This should archive the log before logging. logger1.Debug("123456789"); timeSource.AddToSystemTime(TimeSpan.FromDays(1)); // Archive only once - Thread.Sleep(10); logger2.Debug("123456789"); LogManager.Configuration = null; // Flush var files = Directory.GetFiles(archiveFolder); Assert.Single(files); - if (!enableArchiveCompression) - { - string prevLogFile = Directory.GetFiles(archiveFolder)[0]; - AssertFileContents(prevLogFile, StringRepeat(2, "123456789\n"), Encoding.UTF8); - } + + string prevLogFile = Directory.GetFiles(archiveFolder)[0]; + AssertFileContents(prevLogFile, StringRepeat(2, "123456789\n"), Encoding.UTF8); + string currentLogFile = Directory.GetFiles(tempDir)[0]; AssertFileContents(currentLogFile, StringRepeat(2, "123456789\n"), Encoding.UTF8); } @@ -1988,22 +1843,15 @@ public void DateArchive_AllLoggersTransferToCurrentLogFile(bool concurrentWrites { TimeSource.Current = defaultTimeSource; // restore default time source -#if NET35 || NET40 - if (enableArchiveCompression) - { - FileTarget.FileCompressor = fileCompressor; - } -#endif - if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } } [Fact] - public void DeleteArchiveFilesByDate_MaxArchiveFiles_0() + public void DeleteArchiveFilesByDate_MaxArchiveFiles_NoLimit() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -2014,15 +1862,15 @@ public void DeleteArchiveFilesByDate_MaxArchiveFiles_0() ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), ArchiveAboveSize = 50, LineEnding = LineEndingMode.LF, - ArchiveNumbering = ArchiveNumberingMode.Date, +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = "yyyyMMddHHmmssfff", //make sure the milliseconds are set in the filename +#pragma warning restore CS0618 // Type or member is obsolete Layout = "${message}", - MaxArchiveFiles = 0 }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); //writing 19 times 10 bytes (9 char + linefeed) will result in 3 archive files and 1 current file - for (var i = 0; i < 19; ++i) + for (var i = 0; i < 20; ++i) { logger.Debug("123456789"); //build in a small sleep to make sure the current time is reflected in the filename @@ -2066,7 +1914,7 @@ public void DeleteArchiveFilesByDate_MaxArchiveFiles_0() [Fact] public void DeleteArchiveFilesByDate_AlteredMaxArchive() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -2077,15 +1925,16 @@ public void DeleteArchiveFilesByDate_AlteredMaxArchive() ArchiveFileName = Path.Combine(archiveFolder, "{#}.txt"), ArchiveAboveSize = 50, LineEnding = LineEndingMode.LF, - ArchiveNumbering = ArchiveNumberingMode.Date, +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = "yyyyMMddHHmmssfff", //make sure the milliseconds are set in the filename +#pragma warning restore CS0618 // Type or member is obsolete Layout = "${message}", MaxArchiveFiles = 5 }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); //writing 29 times 10 bytes (9 char + linefeed) will result in 3 archive files and 1 current file - for (var i = 0; i < 29; ++i) + for (var i = 0; i < 30; ++i) { logger.Debug("123456789"); //build in a small sleep to make sure the current time is reflected in the filename @@ -2133,7 +1982,7 @@ public void DeleteArchiveFilesByDate_AlteredMaxArchive() [Fact] public void RepeatingHeaderTest() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -2143,10 +1992,9 @@ public void RepeatingHeaderTest() var fileTarget = new FileTarget { FileName = logFile, - ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveFileName = Path.Combine(archiveFolder, "{##}.txt"), ArchiveAboveSize = 51, LineEnding = LineEndingMode.LF, - ArchiveNumbering = ArchiveNumberingMode.Sequence, Layout = "${message}", Header = header, MaxArchiveFiles = 2, @@ -2164,11 +2012,11 @@ public void RepeatingHeaderTest() AssertFileContentsStartsWith(logFile, header, Encoding.UTF8); - AssertFileContentsStartsWith(Path.Combine(archiveFolder, "0002.txt"), header, Encoding.UTF8); + AssertFileContentsStartsWith(Path.Combine(archiveFolder, "02.txt"), header, Encoding.UTF8); - AssertFileContentsStartsWith(Path.Combine(archiveFolder, "0001.txt"), header, Encoding.UTF8); + AssertFileContentsStartsWith(Path.Combine(archiveFolder, "01.txt"), header, Encoding.UTF8); - Assert.False(File.Exists(Path.Combine(archiveFolder, "0000.txt"))); // MaxArchiveFiles = 2 (Removes the first file) + Assert.False(File.Exists(Path.Combine(archiveFolder, "00.txt"))); // MaxArchiveFiles = 2 (Removes the first file) } finally { @@ -2184,7 +2032,7 @@ public void RepeatingHeaderTest() [InlineData(true)] public void RepeatingFooterTest(bool writeFooterOnArchivingOnly) { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -2194,10 +2042,9 @@ public void RepeatingFooterTest(bool writeFooterOnArchivingOnly) var fileTarget = new FileTarget { FileName = logFile, - ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveFileName = Path.Combine(archiveFolder, "{##}.txt"), ArchiveAboveSize = 51, LineEnding = LineEndingMode.LF, - ArchiveNumbering = ArchiveNumberingMode.Sequence, Layout = "${message}", Footer = footer, MaxArchiveFiles = 2, @@ -2219,9 +2066,9 @@ public void RepeatingFooterTest(bool writeFooterOnArchivingOnly) Assert.False(File.ReadAllText(logFile).EndsWith(expectedEnding), "Footer was unexpectedly written to log file."); else AssertFileContentsEndsWith(logFile, expectedEnding, Encoding.UTF8); - AssertFileContentsEndsWith(Path.Combine(archiveFolder, "0002.txt"), expectedEnding, Encoding.UTF8); - AssertFileContentsEndsWith(Path.Combine(archiveFolder, "0001.txt"), expectedEnding, Encoding.UTF8); - Assert.False(File.Exists(Path.Combine(archiveFolder, "0000.txt"))); // MaxArchiveFiles = 2 (Removes the first file) + AssertFileContentsEndsWith(Path.Combine(archiveFolder, "02.txt"), expectedEnding, Encoding.UTF8); + AssertFileContentsEndsWith(Path.Combine(archiveFolder, "01.txt"), expectedEnding, Encoding.UTF8); + Assert.False(File.Exists(Path.Combine(archiveFolder, "00.txt"))); // MaxArchiveFiles = 2 (Removes the first file) } finally { @@ -2234,9 +2081,15 @@ public void RepeatingFooterTest(bool writeFooterOnArchivingOnly) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void WriteHeaderOnStartupTest(bool writeHeaderWhenInitialFileNotEmpty) + [InlineData(false, false, true)] + [InlineData(true, false, true)] + [InlineData(false, false, false)] + [InlineData(true, false, false)] + [InlineData(false, true, true)] + [InlineData(true, true, true)] + [InlineData(false, true, false)] + [InlineData(true, true, false)] + public void WriteHeaderOnStartupTest(bool writeHeaderWhenInitialFileNotEmpty, bool writeBom, bool keepFileOpen) { var logFile = Path.GetTempFileName() + ".txt"; try @@ -2246,11 +2099,12 @@ public void WriteHeaderOnStartupTest(bool writeHeaderWhenInitialFileNotEmpty) // Configure first time var fileTarget = new FileTarget { - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${message}", Header = header, - WriteBom = true, + KeepFileOpen = keepFileOpen, + WriteBom = writeBom, WriteHeaderWhenInitialFileNotEmpty = true }; @@ -2264,16 +2118,17 @@ public void WriteHeaderOnStartupTest(bool writeHeaderWhenInitialFileNotEmpty) string headerPart = header + LineEndingMode.LF.NewLineCharacters; string logPart = "aaa\nbbb\nccc\n"; - AssertFileContents(logFile, headerPart + logPart, Encoding.UTF8, addBom: true); + AssertFileContents(logFile, headerPart + logPart, Encoding.UTF8, addBom: writeBom); // Configure second time fileTarget = new FileTarget { - FileName = SimpleLayout.Escape(logFile), + FileName = Layout.FromLiteral(logFile), LineEnding = LineEndingMode.LF, Layout = "${message}", Header = header, - WriteBom = true, + KeepFileOpen = keepFileOpen, + WriteBom = writeBom, WriteHeaderWhenInitialFileNotEmpty = writeHeaderWhenInitialFileNotEmpty }; @@ -2286,9 +2141,9 @@ public void WriteHeaderOnStartupTest(bool writeHeaderWhenInitialFileNotEmpty) LogManager.Configuration = null; // Flush if (writeHeaderWhenInitialFileNotEmpty) - AssertFileContents(logFile, headerPart + logPart + headerPart + logPart, Encoding.UTF8, addBom: true); + AssertFileContents(logFile, headerPart + logPart + headerPart + logPart, Encoding.UTF8, addBom: writeBom); else - AssertFileContents(logFile, headerPart + logPart + logPart, Encoding.UTF8, addBom: true); + AssertFileContents(logFile, headerPart + logPart + logPart, Encoding.UTF8, addBom: writeBom); } finally { @@ -2301,52 +2156,28 @@ public void WriteHeaderOnStartupTest(bool writeHeaderWhenInitialFileNotEmpty) [Theory] [InlineData(false)] [InlineData(true)] - public void RollingArchiveTest(bool specifyArchiveFileName) + public void RollingArchiveTest_MaxArchiveFiles(bool specifyArchiveFileName) { - RollingArchiveTests(enableCompression: false, specifyArchiveFileName: specifyArchiveFileName); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void RollingArchiveCompressionTest(bool specifyArchiveFileName) - { - RollingArchiveTests(enableCompression: true, specifyArchiveFileName: specifyArchiveFileName); - } - - private void RollingArchiveTests(bool enableCompression, bool specifyArchiveFileName) - { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); - var archiveExtension = enableCompression ? "zip" : "txt"; - -#if NET35 || NET40 - IFileCompressor fileCompressor = null; -#endif + var archiveExtension = "txt"; try { var fileTarget = new FileTarget { - EnableArchiveFileCompression = enableCompression, FileName = logFile, ArchiveAboveSize = 100, LineEnding = LineEndingMode.LF, - ArchiveNumbering = ArchiveNumberingMode.Rolling, Layout = "${message}", MaxArchiveFiles = 3 }; -#if NET35 || NET40 - if (enableCompression) + if (specifyArchiveFileName) { - fileCompressor = FileTarget.FileCompressor; - FileTarget.FileCompressor = new CustomFileCompressor(); + fileTarget.ArchiveFileName = Path.Combine(tempDir, "archive", "." + archiveExtension); + fileTarget.ArchiveSuffixFormat = "{0:00}"; } -#endif - - if (specifyArchiveFileName) - fileTarget.ArchiveFileName = Path.Combine(tempDir, "archive", "{####}." + archiveExtension); LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -2360,46 +2191,47 @@ private void RollingArchiveTests(bool enableCompression, bool specifyArchiveFile LogManager.Configuration = null; // Flush - var assertFileContents = - enableCompression ? new Action(AssertZipFileContents) : AssertFileContents; + Action assertFileContents = (f1, content, encoding) => AssertFileContents(f1, content, encoding, false); + + string archiveFileNameFormat = specifyArchiveFileName + ? Path.Combine("archive", "0{0}." + archiveExtension) + : "file_0{0}." + archiveExtension; + + var currentFile = specifyArchiveFileName ? logFile : Path.Combine(tempDir, string.Format(archiveFileNameFormat, 4)); var times = 25; - AssertFileContents(logFile, + AssertFileContents(currentFile, StringRepeat(times, "eee\n"), Encoding.UTF8); - string archiveFileNameFormat = specifyArchiveFileName - ? Path.Combine("archive", "000{0}." + archiveExtension) - : "file.{0}." + archiveExtension; + if (!specifyArchiveFileName) + { + Assert.False(File.Exists(logFile)); + Assert.False(File.Exists(Path.Combine(tempDir, string.Format(archiveFileNameFormat, 1)))); + } + else + { + Assert.False(File.Exists(Path.Combine(tempDir, string.Format(archiveFileNameFormat, 0)))); + Assert.False(File.Exists(Path.Combine(tempDir, string.Format(archiveFileNameFormat, 4)))); - assertFileContents( - Path.Combine(tempDir, string.Format(archiveFileNameFormat, 0)), - "file.txt", - StringRepeat(times, "ddd\n"), - Encoding.UTF8); + assertFileContents( + Path.Combine(tempDir, string.Format(archiveFileNameFormat, 1)), + StringRepeat(times, "bbb\n"), + Encoding.UTF8); + } assertFileContents( - Path.Combine(tempDir, string.Format(archiveFileNameFormat, 1)), - "file.txt", + Path.Combine(tempDir, string.Format(archiveFileNameFormat, 2)), StringRepeat(times, "ccc\n"), Encoding.UTF8); assertFileContents( - Path.Combine(tempDir, string.Format(archiveFileNameFormat, 2)), - "file.txt", - StringRepeat(times, "bbb\n"), + Path.Combine(tempDir, string.Format(archiveFileNameFormat, 3)), + StringRepeat(times, "ddd\n"), Encoding.UTF8); - - Assert.False(File.Exists(Path.Combine(tempDir, string.Format(archiveFileNameFormat, 3)))); } finally { -#if NET35 || NET40 - if (enableCompression) - { - FileTarget.FileCompressor = fileCompressor; - } -#endif if (File.Exists(logFile)) File.Delete(logFile); if (Directory.Exists(tempDir)) @@ -2410,21 +2242,19 @@ private void RollingArchiveTests(bool enableCompression, bool specifyArchiveFile [InlineData("/")] [InlineData("\\")] [Theory] - public void RollingArchiveTest_MaxArchiveFiles_0(string slash) + public void RollingArchiveTest_MaxArchiveFiles_NoLimit(string slash) { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { var fileTarget = new FileTarget { FileName = logFile, - ArchiveFileName = Path.Combine(tempDir, "archive" + slash + "{####}.txt"), + ArchiveFileName = Path.Combine(tempDir, "archive" + slash + "log_{##}.txt"), ArchiveAboveSize = 100, LineEnding = LineEndingMode.LF, - ArchiveNumbering = ArchiveNumberingMode.Rolling, Layout = "${message}", - MaxArchiveFiles = 0 }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -2445,28 +2275,27 @@ public void RollingArchiveTest_MaxArchiveFiles_0(string slash) Encoding.UTF8); AssertFileContents( - Path.Combine(tempDir, "archive" + slash + "0000.txt"), - StringRepeat(times, "ddd\n"), + Path.Combine(tempDir, "archive" + slash + "log_00.txt"), + StringRepeat(times, "aaa\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(tempDir, "archive" + slash + "0001.txt"), - StringRepeat(times, "ccc\n"), + Path.Combine(tempDir, "archive" + slash + "log_01.txt"), + StringRepeat(times, "bbb\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(tempDir, "archive" + slash + "0002.txt"), - StringRepeat(times, "bbb\n"), + Path.Combine(tempDir, "archive" + slash + "log_02.txt"), + StringRepeat(times, "ccc\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(tempDir, "archive" + slash + "0003.txt"), - StringRepeat(times, "aaa\n"), + Path.Combine(tempDir, "archive" + slash + "log_03.txt"), + StringRepeat(times, "ddd\n"), Encoding.UTF8); } finally { - if (File.Exists(logFile)) { File.Delete(logFile); @@ -2482,7 +2311,7 @@ public void RollingArchiveTest_MaxArchiveFiles_0(string slash) [Fact] public void MultiFileWrite() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); try { var fileTarget = new FileTarget @@ -2534,7 +2363,7 @@ public void MultiFileWrite() [Fact] public void BufferedMultiFileWrite() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); try { var fileTarget = new FileTarget @@ -2586,7 +2415,7 @@ public void BufferedMultiFileWrite() [Fact] public void AsyncMultiFileWrite() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); try { var fileTarget = new FileTarget @@ -2599,7 +2428,7 @@ public void AsyncMultiFileWrite() // this also checks that thread-volatile layouts // such as ${threadid} are properly cached and not recalculated // in logging threads. - var threadID = CurrentManagedThreadId.ToString(); + var threadID = Thread.CurrentThread.ManagedThreadId.ToString(); LogManager.Setup().LoadConfiguration(c => c.ForLogger(LogLevel.Debug).WriteTo(fileTarget).WithAsync()); @@ -2661,22 +2490,16 @@ public void DisposingFileTarget_WhenNotIntialized_ShouldNotThrow() [Fact] public void FileTarget_ArchiveNumbering_DateAndSequence() { - FileTarget_ArchiveNumbering_DateAndSequenceTests(enableCompression: false, fileTxt: "file.txt", archiveFileName: Path.Combine("archive", "{#}.txt")); + FileTarget_ArchiveNumbering_DateAndSequenceTests(fileTxt: "file.txt", archiveFileName: Path.Combine("archive", "{#}.txt")); } [Fact] public void FileTarget_ArchiveNumbering_DateAndSequence_archive_same_as_log_name() { - FileTarget_ArchiveNumbering_DateAndSequenceTests(enableCompression: false, fileTxt: "file-${date:format=yyyy-MM-dd}.txt", archiveFileName: "file-{#}.txt"); + FileTarget_ArchiveNumbering_DateAndSequenceTests(fileTxt: "file${date:format=yyyy-MM-dd}.txt", archiveFileName: "file_{#}.txt"); } - [Fact] - public void FileTarget_ArchiveNumbering_DateAndSequence_WithCompression() - { - FileTarget_ArchiveNumbering_DateAndSequenceTests(enableCompression: true, fileTxt: "file.txt", archiveFileName: Path.Combine("archive", "{#}.zip")); - } - - private void FileTarget_ArchiveNumbering_DateAndSequenceTests(bool enableCompression, string fileTxt, string archiveFileName) + private void FileTarget_ArchiveNumbering_DateAndSequenceTests(string fileTxt, string archiveFileName) { const string archiveDateFormat = "yyyy-MM-dd"; const int archiveAboveSize = 100; @@ -2685,35 +2508,22 @@ private void FileTarget_ArchiveNumbering_DateAndSequenceTests(bool enableCompres Layout logFile = Path.Combine(tempDir, fileTxt); var logFileName = logFile.Render(LogEventInfo.CreateNullEvent()); -#if NET35 || NET40 - IFileCompressor fileCompressor = null; -#endif - try { var fileTarget = new FileTarget { - EnableArchiveFileCompression = enableCompression, FileName = logFile, ArchiveFileName = Path.Combine(tempDir, archiveFileName), +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = archiveDateFormat, +#pragma warning restore CS0618 // Type or member is obsolete ArchiveAboveSize = archiveAboveSize, LineEnding = LineEndingMode.LF, Layout = "${message}", MaxArchiveFiles = 3, - ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, ArchiveEvery = FileArchivePeriod.Day }; - -#if NET35 || NET40 - if (enableCompression) - { - fileCompressor = FileTarget.FileCompressor; - FileTarget.FileCompressor = new CustomFileCompressor(); - } -#endif - LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); // we emit 5 * 25 *(3 x aaa + \n) bytes @@ -2728,7 +2538,7 @@ private void FileTarget_ArchiveNumbering_DateAndSequenceTests(bool enableCompres LogManager.Configuration = null; - var assertFileContents = enableCompression ? new Action(AssertZipFileContents) : AssertFileContents; + Action assertFileContents = (f1, content, encoding) => AssertFileContents(f1, content, encoding, false); var extension = Path.GetExtension(renderedArchiveFileName); var fileNameWithoutExt = renderedArchiveFileName.Substring(0, renderedArchiveFileName.Length - extension.Length); @@ -2739,31 +2549,15 @@ private void FileTarget_ArchiveNumbering_DateAndSequenceTests(bool enableCompres StringRepeat(times, "eee\n"), Encoding.UTF8); -#if !NET35 - string expectedEntry1Name = Path.GetFileNameWithoutExtension(helper.GetFullPath(1)) + ".txt"; - string expectedEntry2Name = Path.GetFileNameWithoutExtension(helper.GetFullPath(2)) + ".txt"; - string expectedEntry3Name = Path.GetFileNameWithoutExtension(helper.GetFullPath(3)) + ".txt"; -#else - string expectedEntry1Name = fileTxt; - string expectedEntry2Name = fileTxt; - string expectedEntry3Name = fileTxt; -#endif - assertFileContents(helper.GetFullPath(1), expectedEntry1Name, StringRepeat(times, "bbb\n"), Encoding.UTF8); - assertFileContents(helper.GetFullPath(2), expectedEntry2Name, StringRepeat(times, "ccc\n"), Encoding.UTF8); - assertFileContents(helper.GetFullPath(3), expectedEntry3Name, StringRepeat(times, "ddd\n"), Encoding.UTF8); + assertFileContents(helper.GetFullPath(1), StringRepeat(times, "bbb\n"), Encoding.UTF8); + assertFileContents(helper.GetFullPath(2), StringRepeat(times, "ccc\n"), Encoding.UTF8); + assertFileContents(helper.GetFullPath(3), StringRepeat(times, "ddd\n"), Encoding.UTF8); Assert.False(helper.Exists(0), "First archive should have been deleted due to max archive count."); Assert.False(helper.Exists(4), "Fifth archive must not have been created yet."); } finally { -#if NET35 || NET40 - if (enableCompression) - { - FileTarget.FileCompressor = fileCompressor; - } -#endif - if (File.Exists(logFileName)) File.Delete(logFileName); if (Directory.Exists(tempDir)) @@ -2772,13 +2566,13 @@ private void FileTarget_ArchiveNumbering_DateAndSequenceTests(bool enableCompres } [Theory] - [InlineData("archive/test.log.{####}", "archive/test.log.0000", ArchiveNumberingMode.Sequence)] - [InlineData("archive\\test.log.{####}", "archive\\test.log.0000", ArchiveNumberingMode.Sequence)] - [InlineData("file-${date:format=yyyyMMdd}.txt", "file-${date:format=yyyyMMdd}.txt", ArchiveNumberingMode.Sequence)] - [InlineData("file-{#}.txt", "file-${date:format=yyyyMMdd}.txt", ArchiveNumberingMode.Date)] - public void FileTargetArchiveFileNameTest(string archiveFileName, string expectedArchiveFileName, ArchiveNumberingMode archiveNumbering) + [InlineData("archive/test.log_{##}.txt", "archive/test.log_00.txt", null)] + [InlineData("archive\\test.log_{##}.txt", "archive\\test.log_00.txt", null)] + [InlineData("file_${date:format=yyyyMMdd}.txt", "file_${date:format=yyyyMMdd}_0.txt", null)] + [InlineData("file_{#}.txt", "file_${date:format=yyyyMMdd}_00.txt", "yyyyMMdd")] + public void FileTargetArchiveFileNameTest(string archiveFileName, string expectedArchiveFileName, string archiveDateFormat) { - var subPath = Guid.NewGuid().ToString(); + var subPath = "nlog_" + Guid.NewGuid().ToString(); var tempDir = Path.Combine(Path.GetTempPath(), subPath); var logFile = Path.Combine(tempDir, "file-${date:format=yyyyMMdd}.txt"); try @@ -2787,10 +2581,13 @@ public void FileTargetArchiveFileNameTest(string archiveFileName, string expecte { FileName = logFile, ArchiveFileName = Path.Combine(tempDir, "..", subPath, archiveFileName), - ArchiveNumbering = archiveNumbering, ArchiveAboveSize = 1000, MaxArchiveFiles = 1000, }; +#pragma warning disable CS0618 // Type or member is obsolete + if (!string.IsNullOrEmpty(archiveDateFormat)) + fileTarget.ArchiveDateFormat = archiveDateFormat; +#pragma warning restore CS0618 // Type or member is obsolete LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -2827,7 +2624,7 @@ public void FileTarget_InvalidFileNameCorrection() { var fileTarget = new FileTarget { - FileName = SimpleLayout.Escape(invalidLogFileName), + FileName = Layout.FromLiteral(invalidLogFileName), LineEnding = LineEndingMode.LF, Layout = "${level} ${message}", OpenFileCacheTimeout = 0 @@ -2853,7 +2650,7 @@ public void FileTarget_InvalidFileNameCorrection() [Fact] public void FileTarget_LogAndArchiveFilesWithSameName_ShouldArchive() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "Application.log"); var tempDirectory = new DirectoryInfo(tempDir); try @@ -2867,7 +2664,6 @@ public void FileTarget_LogAndArchiveFilesWithSameName_ShouldArchive() FileName = logFile, ArchiveFileName = archiveFile, ArchiveAboveSize = 1, //Force immediate archival - ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, MaxArchiveFiles = 5 }; @@ -2895,10 +2691,113 @@ public void FileTarget_LogAndArchiveFilesWithSameName_ShouldArchive() } } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FileTarget_ArchiveAboveSize_NewStyle_RollWhenFull(bool reloadConfig) + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "Application"); + var tempDirectory = new DirectoryInfo(tempDir); + var maxArchiveFiles = 2; + + try + { + var fileTarget = new FileTarget + { + FileName = logFile + ".log", + Layout = "${message}", + LineEnding = LineEndingMode.LF, + ArchiveAboveSize = 7, + MaxArchiveFiles = maxArchiveFiles, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaaa"); + if (reloadConfig) + LogManager.Setup().ReloadConfiguration(); + logger.Debug("bbbb"); // Not roll (new style so all agree when rolling) + logger.Debug("cccc"); // Roll + logger.Debug("dddd"); // Not roll (new style so all agree when rolling) + logger.Debug("eeee"); // Roll + logger.Debug("ffff"); // Not roll (new style so all agree when rolling) + if (reloadConfig) + LogManager.Setup().ReloadConfiguration(); + logger.Debug("gggg"); // Roll + + LogManager.Configuration = null; // Flush + + Assert.False(File.Exists(logFile + ".log")); + Assert.False(File.Exists(logFile + "_01.log")); + AssertFileContents(logFile + "_02.log", "eeee\nffff\n", Encoding.UTF8); + AssertFileContents(logFile + "_03.log", "gggg\n", Encoding.UTF8); + Assert.Equal(maxArchiveFiles, tempDirectory.GetFiles().Length); + } + finally + { + if (tempDirectory.Exists) + { + tempDirectory.Delete(true); + } + } + } + + [Fact] + public void FileTarget_ArchiveAboveSize_OldStyle_RollWhenFull() + { + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + var logFile = Path.Combine(tempDir, "${shortdate}"); + var tempDirectory = new DirectoryInfo(tempDir); + var maxArchiveFiles = 2; + + try + { + var fileTarget = new FileTarget + { + FileName = logFile + ".log", + ArchiveFileName = logFile + ".log", + ArchiveSuffixFormat = "_{0:00}", + Layout = "${message}", + LineEnding = LineEndingMode.LF, + ArchiveAboveSize = 7, + MaxArchiveFiles = maxArchiveFiles, + }; + + LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); + + logger.Debug("aaaa"); + logger.Debug("bbbb"); // Not roll (new style so all agree when rolling) + logger.Debug("cccc"); // Roll + logger.Debug("dddd"); // Not roll (new style so all agree when rolling) + logger.Debug("eeee"); // Roll + logger.Debug("ffff"); // Not roll (new style so all agree when rolling) + logger.Debug("gggg"); // Roll + + LogManager.Configuration = null; // Flush + + var logFileName = SimpleLayout.Evaluate(logFile); + + Assert.True(File.Exists(logFileName + ".log")); + Assert.False(File.Exists(logFileName + "_01.log")); + AssertFileContents(logFileName + ".log", "gggg\n", Encoding.UTF8); + AssertFileContents(logFileName + "_02.log", "cccc\ndddd\n", Encoding.UTF8); + AssertFileContents(logFileName + "_03.log", "eeee\nffff\n", Encoding.UTF8); + Assert.Equal(maxArchiveFiles + 1, tempDirectory.GetFiles().Length); + } + finally + { + if (tempDirectory.Exists) + { + tempDirectory.Delete(true); + } + } + } + [Fact] public void FileTarget_Handle_Other_Files_That_Match_Archive_Format() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "Application.log"); var tempDirectory = new DirectoryInfo(tempDir); @@ -2914,8 +2813,9 @@ public void FileTarget_Handle_Other_Files_That_Match_Archive_Format() Encoding = Encoding.UTF8, ArchiveFileName = archiveFileLayout, ArchiveEvery = FileArchivePeriod.Day, - ArchiveNumbering = ArchiveNumberingMode.Date, +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = "___________yyyyMMddHHmm", +#pragma warning restore CS0618 // Type or member is obsolete MaxArchiveFiles = 10 // Get past the optimization to avoid deleting old files. }; @@ -2944,7 +2844,7 @@ public void FileTarget_Handle_Other_Files_That_Match_Archive_Format() [Fact] public void SingleArchiveFileRollsCorrectly() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -2952,6 +2852,7 @@ public void SingleArchiveFileRollsCorrectly() { FileName = logFile, ArchiveFileName = Path.Combine(tempDir, "archive", "file.txt2"), + ArchiveSuffixFormat = "", ArchiveAboveSize = 100, LineEnding = LineEndingMode.LF, Layout = "${message}", @@ -3009,7 +2910,7 @@ public void SingleArchiveFileRollsCorrectly() [Fact] public void ArchiveFileRollsCorrectly() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -3017,6 +2918,7 @@ public void ArchiveFileRollsCorrectly() { FileName = logFile, ArchiveFileName = Path.Combine(tempDir, "archive", "file.txt2"), + ArchiveSuffixFormat = ".{0}", ArchiveAboveSize = 100, LineEnding = LineEndingMode.LF, Layout = "${message}", @@ -3051,7 +2953,7 @@ public void ArchiveFileRollsCorrectly() StringRepeat(times, "bbb\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(tempDir, "archive", "file.txt2"), + Path.Combine(tempDir, "archive", "file.0.txt2"), StringRepeat(times, "aaa\n"), Encoding.UTF8); @@ -3087,7 +2989,7 @@ public void ArchiveFileRollsCorrectly() [Fact] public void ArchiveFileRollsCorrectly_ExistingArchives() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -3099,6 +3001,7 @@ public void ArchiveFileRollsCorrectly_ExistingArchives() { FileName = logFile, ArchiveFileName = Path.Combine(tempDir, "archive", "file.txt2"), + ArchiveSuffixFormat = ".{0}", ArchiveAboveSize = 100, LineEnding = LineEndingMode.LF, Layout = "${message}", @@ -3151,23 +3054,26 @@ public void FileTarget_ArchiveNumbering_remove_correct_order() var tempDir = ArchiveFileNameHelper.GenerateTempPath(); var logFile = Path.Combine(tempDir, "file.txt"); var archiveExtension = "txt"; + var archiveDateFormat = "yyyy-MM-dd"; + try { var fileTarget = new FileTarget { FileName = logFile, ArchiveFileName = Path.Combine(tempDir, "archive", "{#}." + archiveExtension), - ArchiveDateFormat = "yyyy-MM-dd", +#pragma warning disable CS0618 // Type or member is obsolete + ArchiveDateFormat = archiveDateFormat, +#pragma warning restore CS0618 // Type or member is obsolete ArchiveAboveSize = 100, LineEnding = LineEndingMode.LF, Layout = "${message}", MaxArchiveFiles = maxArchiveFiles, - ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); - ArchiveFileNameHelper helper = new ArchiveFileNameHelper(Path.Combine(tempDir, "archive"), DateTime.Now.ToString(fileTarget.ArchiveDateFormat), archiveExtension); + ArchiveFileNameHelper helper = new ArchiveFileNameHelper(Path.Combine(tempDir, "archive"), DateTime.Now.ToString(archiveDateFormat), archiveExtension); Generate100BytesLog('a'); @@ -3199,13 +3105,15 @@ public void FileTarget_ArchiveNumbering_remove_correct_order() /// /// Allow multiple archives within the same directory /// - [Fact] - public void FileTarget_ArchiveNumbering_remove_correct_wildcard() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void FileTarget_ArchiveNumbering_remove_correct_wildcard(bool keepFileOpen) { const int maxArchiveFiles = 5; var tempDir = ArchiveFileNameHelper.GenerateTempPath(); - var logFile = Path.Combine(tempDir, "{0}{1}.txt"); + var logFile = Path.Combine(tempDir, "{0}_{1:00}.txt"); var defaultTimeSource = TimeSource.Current; try @@ -3226,6 +3134,7 @@ public void FileTarget_ArchiveNumbering_remove_correct_wildcard() LineEnding = LineEndingMode.LF, Layout = "${message}", MaxArchiveFiles = maxArchiveFiles, + KeepFileOpen = keepFileOpen, }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -3247,8 +3156,8 @@ public void FileTarget_ArchiveNumbering_remove_correct_wildcard() $"{logFile1} is missing"); Assert.True(File.Exists(logFile2), $"{logFile2} is missing"); - logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); - logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); + logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + $"_{(i + 1):00}"); + logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + $"_{(i + 1):00}"); Assert.True(File.Exists(logFile1), $"{logFile1} is missing"); Assert.True(File.Exists(logFile2), @@ -3262,8 +3171,8 @@ public void FileTarget_ArchiveNumbering_remove_correct_wildcard() { Generate100BytesLog((char)('b' + i), logger1); Generate100BytesLog((char)('b' + i), logger2); - var logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); - var logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); + var logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + $"_{(i + 1):00}"); + var logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + $"_{(i + 1):00}"); Assert.True(File.Exists(logFile1), $"{logFile1} is missing"); Assert.True(File.Exists(logFile2), @@ -3274,18 +3183,18 @@ public void FileTarget_ArchiveNumbering_remove_correct_wildcard() { Generate100BytesLog((char)('b' + i), logger1); Generate100BytesLog((char)('b' + i), logger2); - var numberToBeRemoved = i - maxArchiveFiles; + var numberToBeRemoved = i - maxArchiveFiles + 1; - var logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + numberToBeRemoved.ToString()); - var logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + numberToBeRemoved.ToString()); + var logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + $"_{numberToBeRemoved:00}"); + var logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + $"_{numberToBeRemoved:00}"); Assert.False(File.Exists(logFile1), $"archive FirstFile {numberToBeRemoved} has not been removed! We are created file {i}"); Assert.False(File.Exists(logFile2), $"archive SecondFile {numberToBeRemoved} has not been removed! We are created file {i}"); - logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); - logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + "." + i.ToString()); + logFile1 = string.Format(logFile, logger1.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + $"_{(i + 1):00}"); + logFile2 = string.Format(logFile, logger2.Name, TimeSource.Current.Time.Date.ToString("yyyy-MM-dd") + $"_{(i + 1):00}"); Assert.True(File.Exists(logFile1), $"{logFile1} is missing"); @@ -3293,12 +3202,15 @@ public void FileTarget_ArchiveNumbering_remove_correct_wildcard() $"{logFile2} is missing"); } + LogManager.Configuration = null; + Assert.Equal(10, Directory.GetFiles(tempDir).Length); + // Verify that archieve-cleanup after startup handles same folder archive correctly fileTarget.ArchiveAboveSize = 200; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); logger1.Info("Bye"); logger2.Info("Bye"); - Assert.Equal(12, Directory.GetFiles(tempDir).Length); + Assert.Equal(10, Directory.GetFiles(tempDir).Length); LogManager.Configuration = null; } @@ -3306,6 +3218,8 @@ public void FileTarget_ArchiveNumbering_remove_correct_wildcard() { TimeSource.Current = defaultTimeSource; // restore default time source + LogManager.Configuration = null; + if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } @@ -3314,11 +3228,12 @@ public void FileTarget_ArchiveNumbering_remove_correct_wildcard() /// /// See that dynamic sequence archive supports same-folder archiving. /// - [Fact] - public void FileTarget_SameDirectory_MaxArchiveFiles_One() + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void FileTarget_SameDirectory_MaxArchiveFiles(int maxArchiveFiles) { - const int maxArchiveFiles = 1; - var tempDir = ArchiveFileNameHelper.GenerateTempPath(); var logFile1 = Path.Combine(tempDir, "Log{0}.txt"); try @@ -3340,13 +3255,24 @@ public void FileTarget_SameDirectory_MaxArchiveFiles_One() Generate100BytesLog('c'); var times = 25; - AssertFileContents(string.Format(logFile1, ".0"), - StringRepeat(times, "bbb\n"), - Encoding.ASCII); + if (maxArchiveFiles > 1) + { + Assert.False(File.Exists(string.Format(logFile1, ""))); - AssertFileContents(string.Format(logFile1, ""), - StringRepeat(times, "ccc\n"), - Encoding.ASCII); + AssertFileContents(string.Format(logFile1, "_01"), + StringRepeat(times, "bbb\n"), + Encoding.ASCII); + + AssertFileContents(string.Format(logFile1, "_02"), + StringRepeat(times, "ccc\n"), + Encoding.ASCII); + } + else + { + AssertFileContents(string.Format(logFile1, ""), + StringRepeat(times, "ccc\n"), + Encoding.ASCII); + } LogManager.Configuration = null; } @@ -3398,32 +3324,31 @@ public bool Exists(int number) public string GetFullPath(int number) { - return Path.Combine($"{FolderName}/{FileName}.{number}.{Ext}"); + return Path.Combine(FolderName, $"{FileName}_{number:00}.{Ext}"); } public static string GenerateTempPath() { - return Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + return Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); } } [Theory] [InlineData("##", 0, "00")] - [InlineData("###", 1, "001")] + [InlineData("##", 1, "01")] [InlineData("#", 20, "20")] public void FileTarget_WithDateAndSequenceArchiveNumbering_ShouldPadSequenceNumberInArchiveFileName( string placeHolderSharps, int sequenceNumber, string expectedSequenceInArchiveFileName) { - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); const string archiveDateFormat = "yyyy-MM-dd"; string archiveFileName = Path.Combine(tempDir, $"{{{placeHolderSharps}}}.log"); string expectedArchiveFullName = - $"{tempDir}/{DateTime.Now.ToString(archiveDateFormat)}.{expectedSequenceInArchiveFileName}.log"; + $"{tempDir}/{DateTime.Now.ToString(archiveDateFormat)}_{expectedSequenceInArchiveFileName}.log"; - GenerateArchives(count: sequenceNumber + 1, archiveDateFormat: archiveDateFormat, - archiveFileName: archiveFileName, archiveNumbering: ArchiveNumberingMode.DateAndSequence); + GenerateArchives(count: sequenceNumber + 2, archiveDateFormat: archiveDateFormat, + archiveFileName: archiveFileName); bool resultArchiveWithExpectedNameExists = File.Exists(expectedArchiveFullName); - Assert.True(resultArchiveWithExpectedNameExists); } @@ -3434,21 +3359,19 @@ public void FileTarget_WithDateAndSequenceArchiveNumbering_ShouldPadSequenceNumb public void FileTarget_WithDateAndSequenceArchiveNumbering_ShouldRespectArchiveDateFormat( string archiveDateFormat) { - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - string archiveFileName = Path.Combine(tempDir, "{#}.log"); + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); + string archiveFileName = Path.Combine(tempDir, "{##}.log"); string expectedDateInArchiveFileName = DateTime.Now.ToString(archiveDateFormat); - string expectedArchiveFullName = $"{tempDir}/{expectedDateInArchiveFileName}.1.log"; + string expectedArchiveFullName = $"{tempDir}/{expectedDateInArchiveFileName}_00.log"; // We generate 2 archives so that the algorithm that seeks old archives is also tested. - GenerateArchives(count: 2, archiveDateFormat: archiveDateFormat, archiveFileName: archiveFileName, - archiveNumbering: ArchiveNumberingMode.DateAndSequence); + GenerateArchives(count: 2, archiveDateFormat: archiveDateFormat, archiveFileName: archiveFileName); bool resultArchiveWithExpectedNameExists = File.Exists(expectedArchiveFullName); Assert.True(resultArchiveWithExpectedNameExists); } - private void GenerateArchives(int count, string archiveDateFormat, string archiveFileName, - ArchiveNumberingMode archiveNumbering) + private void GenerateArchives(int count, string archiveDateFormat, string archiveFileName) { string logFileName = Path.GetTempFileName(); const int logFileMaxSize = 1; @@ -3456,15 +3379,16 @@ private void GenerateArchives(int count, string archiveDateFormat, string archiv { FileName = logFileName, ArchiveFileName = archiveFileName, +#pragma warning disable CS0618 // Type or member is obsolete ArchiveDateFormat = archiveDateFormat, - ArchiveNumbering = archiveNumbering, +#pragma warning restore CS0618 // Type or member is obsolete ArchiveAboveSize = logFileMaxSize }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); for (int currentSequenceNumber = 0; currentSequenceNumber < count; currentSequenceNumber++) logger.Debug("Test {0}", currentSequenceNumber); - LogManager.Flush(); + LogManager.Configuration = null; } [Fact] @@ -3478,7 +3402,7 @@ public void Dont_throw_Exception_when_archiving_is_enabled() throwExceptions='true' > - + @@ -3508,7 +3432,7 @@ public void Dont_throw_Exception_when_archiving_is_enabled_with_async() throwExceptions='true' > - + @@ -3554,9 +3478,8 @@ public void DatedArchiveForFileTargetWithMultipleFiles() @@ -3618,7 +3541,7 @@ public void LoggingShouldNotTriggerTypeResolveEventTest() - + @@ -3648,7 +3571,6 @@ public void LoggingShouldNotTriggerTypeResolveEventTest() } [Theory] - [InlineData("yyyyMMdd-HHmm")] [InlineData("yyyyMMdd")] [InlineData("yyyy-MM-dd")] public void MaxArchiveFilesWithDateFormatTest(string archiveDateFormat) @@ -3666,7 +3588,7 @@ public void MaxArchiveFilesWithDateFormatTest(string archiveDateFormat) /// change file creation/last write date private static void TestMaxArchiveFilesWithDate(int maxArchiveFilesConfig, int expectedArchiveFiles, string dateFormat, bool changeCreationAndWriteTime) { - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); string archivePath = Path.Combine(tempDir, "archive"); var archiveDir = new DirectoryInfo(archivePath); @@ -3679,7 +3601,7 @@ private static void TestMaxArchiveFilesWithDate(int maxArchiveFilesConfig, int e string fileExt = ".log"; DateTime now = DateTime.Now; int i = 0; - foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, fileExt).Take(30)) + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, "{0:" + dateFormat + "}" + fileExt).Take(30)) { File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); var time = now.AddDays(i); @@ -3701,8 +3623,7 @@ private static void TestMaxArchiveFilesWithDate(int maxArchiveFilesConfig, int e archiveEvery='minute' maxArchiveFiles='" + maxArchiveFilesConfig + @"' archiveFileName='" + archivePath + @"/{#}.log' - archiveDateFormat='" + dateFormat + @"' - archiveNumbering='Date'/> + archiveDateFormat='" + dateFormat + @"' /> @@ -3762,7 +3683,7 @@ public void HandleArchiveFilesMultipleContextSingleTargetTest_ascii(bool changeC private static void HandleArchiveFilesMultipleContextMultipleTargetsTest( int maxArchiveFilesConfig, int expectedArchiveFiles, string dateFormat, bool changeCreationAndWriteTime) { - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); string archivePath = Path.Combine(tempDir, "archive"); var archiveDir = new DirectoryInfo(archivePath); try @@ -3780,7 +3701,7 @@ private static void HandleArchiveFilesMultipleContextMultipleTargetsTest( var now = DateTime.Now; var i = 0; // create mock app1_trace archives (matches app1 config for trace target) - foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, app1TraceNm + fileExt).Take(numberFilesCreatedPerTargetArchive)) + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, app1TraceNm + "_{0:" + dateFormat + "}" + fileExt).Take(numberFilesCreatedPerTargetArchive)) { File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); var time = now.AddDays(i); @@ -3793,7 +3714,7 @@ private static void HandleArchiveFilesMultipleContextMultipleTargetsTest( } i = 0; // create mock app1_debug archives (matches app1 config for debug target) - foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, app1DebugNm + fileExt).Take(numberFilesCreatedPerTargetArchive)) + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, app1DebugNm + "_{0:" + dateFormat + "}" + fileExt).Take(numberFilesCreatedPerTargetArchive)) { File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); var time = now.AddDays(i); @@ -3806,7 +3727,7 @@ private static void HandleArchiveFilesMultipleContextMultipleTargetsTest( } i = 0; // create mock app2 archives (matches app2 config for target) - foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, app2Nm + fileExt).Take(numberFilesCreatedPerTargetArchive)) + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, app2Nm + "_{0:" + dateFormat + "}" + fileExt).Take(numberFilesCreatedPerTargetArchive)) { File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); var time = now.AddDays(i); @@ -3819,26 +3740,26 @@ private static void HandleArchiveFilesMultipleContextMultipleTargetsTest( } // Create same app1 Debug file as config defines. Will force archiving to happen on startup - File.WriteAllLines(tempDir + "\\" + app1DebugNm + fileExt, new[] { "Write first app debug target. Startup will archive this file" }, Encoding.ASCII); + File.WriteAllLines(Path.Combine(tempDir, app1TraceNm + fileExt), new[] { "Write first app debug target. Startup will archive this file" }, Encoding.ASCII); var app1Config = XmlLoggingConfiguration.CreateFromXmlString(@" + keepFileOpen='false' /> + keepFileOpen='false' /> @@ -3850,12 +3771,12 @@ private static void HandleArchiveFilesMultipleContextMultipleTargetsTest( + keepFileOpen='false' /> ; @@ -3904,7 +3825,7 @@ private static void HandleArchiveFilesMultipleContextMultipleTargetsTest( private static void HandleArchiveFilesMultipleContextSingleTargetsTest( int maxArchiveFilesConfig, int expectedArchiveFiles, string dateFormat, bool changeCreationAndWriteTime) { - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); string archivePath = Path.Combine(tempDir, "archive"); var archiveDir = new DirectoryInfo(archivePath); try @@ -3914,13 +3835,13 @@ private static void HandleArchiveFilesMultipleContextSingleTargetsTest( // use same config vars for mock files, as for nlog config var fileExt = ".log"; - var app1Nm = "App1"; - var app2Nm = "App2"; + var app1Nm = "AppA"; + var app2Nm = "AppB"; var now = DateTime.Now; var i = 0; // create mock app1 archives (matches app1 config for target) - foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, app1Nm + fileExt).Take(numberFilesCreatedPerTargetArchive)) + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, app1Nm + "_{0:" + dateFormat + "}" + fileExt).Take(numberFilesCreatedPerTargetArchive)) { File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); var time = now.AddDays(i); @@ -3933,7 +3854,7 @@ private static void HandleArchiveFilesMultipleContextSingleTargetsTest( } i = 0; // create mock app2 archives (matches app2 config for target) - foreach (string filePath in ArchiveFileNamesGenerator(archivePath, dateFormat, app2Nm + fileExt).Take(numberFilesCreatedPerTargetArchive)) + foreach (string filePath in ArchiveFileNamesGenerator(archivePath, app2Nm + "_{0:" + dateFormat + "}" + fileExt).Take(numberFilesCreatedPerTargetArchive)) { File.WriteAllLines(filePath, new[] { "test archive ", "=====", filePath }); var time = now.AddDays(i); @@ -3952,12 +3873,12 @@ private static void HandleArchiveFilesMultipleContextSingleTargetsTest( + keepFileOpen='false' /> ; @@ -3968,12 +3889,12 @@ private static void HandleArchiveFilesMultipleContextSingleTargetsTest( + keepFileOpen='false' /> ; @@ -4012,7 +3933,7 @@ private static void HandleArchiveFilesMultipleContextSingleTargetsTest( /// /// fileext with . /// - private static IEnumerable ArchiveFileNamesGenerator(string path, string dateFormat, string fileExt) + private static IEnumerable ArchiveFileNamesGenerator(string path, string fileFormat) { //yyyyMMdd-HHmm int dateOffset = 1; @@ -4020,7 +3941,7 @@ private static IEnumerable ArchiveFileNamesGenerator(string path, string while (true) { dateOffset--; - yield return Path.Combine(path, now.AddDays(dateOffset).ToString(dateFormat) + fileExt); + yield return Path.Combine(path, string.Format(fileFormat, now.AddDays(dateOffset))); } } @@ -4085,9 +4006,9 @@ public void RelativeFileNaming_DirectoryNavigation_ShouldSuccess() } [Fact] - public void RelativeSequentialArchiveTest_MaxArchiveFiles_0() + public void RelativeSequentialArchiveTest_MaxArchiveFiles_NoLimit() { - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logfile = Path.Combine(tempDir, "file.txt"); try { @@ -4095,13 +4016,11 @@ public void RelativeSequentialArchiveTest_MaxArchiveFiles_0() var fileTarget = new FileTarget { FileName = logfile, - ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveFileName = Path.Combine(archiveFolder, "{##}.txt"), ArchiveAboveSize = 100, ArchiveOldFileOnStartup = true, // Verify ArchiveOldFileOnStartup works together with ArchiveAboveSize LineEnding = LineEndingMode.LF, - ArchiveNumbering = ArchiveNumberingMode.Sequence, Layout = "${message}", - MaxArchiveFiles = 0 }; LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget)); @@ -4122,26 +4041,26 @@ public void RelativeSequentialArchiveTest_MaxArchiveFiles_0() Encoding.UTF8); AssertFileContents( - Path.Combine(archiveFolder, "0000.txt"), + Path.Combine(archiveFolder, "00.txt"), StringRepeat(times, "aaa\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(archiveFolder, "0001.txt"), + Path.Combine(archiveFolder, "01.txt"), StringRepeat(times, "bbb\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(archiveFolder, "0002.txt"), + Path.Combine(archiveFolder, "02.txt"), StringRepeat(times, "ccc\n"), Encoding.UTF8); AssertFileContents( - Path.Combine(archiveFolder, "0003.txt"), + Path.Combine(archiveFolder, "03.txt"), StringRepeat(times, "ddd\n"), Encoding.UTF8); - Assert.False(File.Exists(Path.Combine(archiveFolder, "0004.txt"))); + Assert.False(File.Exists(Path.Combine(archiveFolder, "04.txt"))); } finally { @@ -4171,23 +4090,22 @@ public void TestFilenameCleanup() //underscore is used for clean expectedFileName += i + "_"; } + expectedFileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, expectedFileName); + //under mono this the invalid chars is sometimes only 1 char (so min width 2) Assert.True(invalidFileName.Length >= 2); //CleanupFileName is default true; var fileTarget = new FileTarget(); fileTarget.FileName = invalidFileName; - var filePathLayout = new NLog.Internal.FilePathLayout(invalidFileName, true, FilePathKind.Absolute); - - - var path = filePathLayout.Render(LogEventInfo.CreateNullEvent()); + var path = FileTarget.CleanFullFilePath(fileTarget.FileName.Render(LogEventInfo.CreateNullEvent())); Assert.Equal(expectedFileName, path); } [Theory] - [InlineData(DayOfWeek.Sunday, "2017-03-02 15:27:34.651", "2017-03-05 15:27:34.651")] // On a Thursday, finding next Sunday - [InlineData(DayOfWeek.Thursday, "2017-03-02 15:27:34.651", "2017-03-09 15:27:34.651")] // On a Thursday, finding next Thursday - [InlineData(DayOfWeek.Monday, "2017-03-02 00:00:00.000", "2017-03-06 00:00:00.000")] // On Thursday at Midnight, finding next Monday + [InlineData(DayOfWeek.Sunday, "2017-03-02 15:27:34.651", "2017-03-05")] // On a Thursday, finding next Sunday + [InlineData(DayOfWeek.Thursday, "2017-03-02 15:27:34.651", "2017-03-09")] // On a Thursday, finding next Thursday + [InlineData(DayOfWeek.Monday, "2017-03-02 00:00:00.000", "2017-03-06")] // On Thursday at Midnight, finding next Monday public void TestCalculateNextWeekday(DayOfWeek day, string todayString, string expectedString) { DateTime today = DateTime.Parse(todayString); @@ -4220,10 +4138,12 @@ public void TestInitialBomValue(string encodingName, bool expected) [Fact] public void BatchErrorHandlingTest() { - using (new NoThrowNLogExceptions()) + try { + LogManager.ThrowExceptions = false; + var fileTarget = new FileTarget { FileName = "${logger}", Layout = "${message}", ArchiveAboveSize = 10, DiscardAll = true }; - fileTarget.Initialize(null); + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(fileTarget)).LogFactory; // make sure that when file names get sorted, the asynchronous continuations are sorted with them as well var exceptions = new List(); @@ -4237,7 +4157,7 @@ public void BatchErrorHandlingTest() }; fileTarget.WriteAsyncLogEvents(events); - LogManager.Flush(); + logFactory.Flush(); Assert.Equal(5, exceptions.Count); Assert.Null(exceptions[0]); @@ -4246,12 +4166,16 @@ public void BatchErrorHandlingTest() Assert.NotNull(exceptions[3]); Assert.NotNull(exceptions[4]); } + finally + { + LogManager.ThrowExceptions = true; + } } [Fact] public void BatchBufferOverflowTest() { - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logfile = Path.Combine(tempDir, "file.txt"); try { @@ -4264,7 +4188,8 @@ public void BatchBufferOverflowTest() Layout = "${message}", Encoding = Encoding.UTF8, }; - fileTarget.Initialize(null); + + var logFactory = new LogFactory().Setup().LoadConfiguration(cfg => cfg.ForLogger().WriteTo(fileTarget)).LogFactory; var result = new List(); var events = new List(); @@ -4277,7 +4202,8 @@ public void BatchBufferOverflowTest() // Act fileTarget.WriteAsyncLogEvents(events); - fileTarget.Close(); + logFactory.Flush(); + logFactory.Dispose(); // Assert Assert.Equal(Enumerable.Range(1, times).ToList(), result); @@ -4321,7 +4247,7 @@ public void HandleArchiveFileAlreadyExistsTest_ascii() private static void HandleArchiveFileAlreadyExistsTest(Encoding encoding, bool hasBom) { - var tempDir = Path.Combine(Path.GetTempPath(), "HandleArchiveFileAlreadyExistsTest-" + Guid.NewGuid()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); string logFile = Path.Combine(tempDir, "log.txt"); try { @@ -4336,7 +4262,7 @@ private static void HandleArchiveFileAlreadyExistsTest(Encoding encoding, bool h //write to archive directly var archiveDateFormat = "yyyy-MM-dd"; - var archiveFileNamePattern = Path.Combine(tempDir, "log-{#}.txt"); + var archiveFileNamePattern = Path.Combine(tempDir, "log_{#}.txt"); var archiveFileName = archiveFileNamePattern.Replace("{#}", oldTime.ToString(archiveDateFormat)); File.WriteAllText(archiveFileName, "message already in archive" + Environment.NewLine, encoding); @@ -4348,8 +4274,7 @@ private static void HandleArchiveFileAlreadyExistsTest(Encoding encoding, bool h FileName = logFile, ArchiveEvery = FileArchivePeriod.Day, ArchiveFileName = archiveFileNamePattern, - ArchiveNumbering = ArchiveNumberingMode.Date, - ArchiveDateFormat = archiveDateFormat, + ArchiveSuffixFormat = "_{1:" + archiveDateFormat + "}", Encoding = encoding, Layout = "${message}", WriteBom = hasBom, @@ -4383,7 +4308,7 @@ private static void HandleArchiveFileAlreadyExistsTest(Encoding encoding, bool h [Fact] public void DontCrashWhenDateAndSequenceDoesntMatchFiles() { - var tempDir = Path.Combine(Path.GetTempPath(), "DontCrashWhenDateAndSequenceDoesntMatchFiles-" + Guid.NewGuid()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); string logFile = Path.Combine(tempDir, "log.txt"); try { @@ -4410,7 +4335,6 @@ public void DontCrashWhenDateAndSequenceDoesntMatchFiles() FileName = logFile, ArchiveEvery = FileArchivePeriod.Day, ArchiveFileName = "log-{#}.txt", - ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, ArchiveAboveSize = 50000, MaxArchiveFiles = 7 }; @@ -4436,43 +4360,10 @@ public void DontCrashWhenDateAndSequenceDoesntMatchFiles() } } - [Theory] - [InlineData(true, 100, true)] // archive, as size of file is 101 - [InlineData(true, 101, false)] //equals is not above - [InlineData(true, 102, false)] // don;t archive, we didn't reach the aboveSize - [InlineData(false, 100, false)] - [InlineData(null, 0, false)] - [InlineData(null, 99, true)] - [InlineData(null, 100, true)] - [InlineData(null, 101, false)] - public void ShouldArchiveOldFileOnStartupTest(bool? archiveOldFileOnStartup, long archiveOldFileOnStartupAboveSize, bool expected) - { - // Arrange - var fileAppenderCacheMock = Substitute.For(); - - var filePath = "x:/somewhere/file.txt"; - fileAppenderCacheMock.GetFileLength(filePath).Returns(101); - - var target = new FileTarget(fileAppenderCacheMock) - { - ArchiveOldFileOnStartupAboveSize = archiveOldFileOnStartupAboveSize - }; - if (archiveOldFileOnStartup.HasValue) - { - target.ArchiveOldFileOnStartup = archiveOldFileOnStartup.Value; - } - - // Act - var result = target.ShouldArchiveOldFileOnStartup(filePath); - - // Assert - Assert.Equal(expected, result); - } - [Fact] public void ShouldNotArchiveWhenMeetingOldLogEventTimestamps() { - var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString()); var logFile = Path.Combine(tempDir, "file.txt"); try { @@ -4480,10 +4371,10 @@ public void ShouldNotArchiveWhenMeetingOldLogEventTimestamps() var fileTarget = new FileTarget { FileName = logFile, - ArchiveFileName = Path.Combine(archiveFolder, "{####}.txt"), + ArchiveFileName = Path.Combine(archiveFolder, ".txt"), + ArchiveSuffixFormat = "{0:0000}", ArchiveEvery = FileArchivePeriod.Day, LineEnding = LineEndingMode.LF, - ArchiveNumbering = ArchiveNumberingMode.Sequence, Layout = "${message}", }; @@ -4517,5 +4408,35 @@ public void ShouldNotArchiveWhenMeetingOldLogEventTimestamps() Directory.Delete(tempDir, true); } } + + private static void AssertFileContentsStartsWith(string fileName, string contents, Encoding encoding) + { + FileInfo fi = new FileInfo(fileName); + if (!fi.Exists) + Assert.Fail("File '" + fileName + "' doesn't exist."); + + byte[] encodedBuf = encoding.GetBytes(contents); + + byte[] buf = File.ReadAllBytes(fileName); + Assert.True(encodedBuf.Length <= buf.Length, + $"File:{fileName} encodedBytes:{encodedBuf.Length} does not match file.content:{buf.Length}, file.length = {fi.Length}"); + + for (int i = 0; i < encodedBuf.Length; ++i) + { + if (encodedBuf[i] != buf[i]) + Assert.True(encodedBuf[i] == buf[i], + $"File:{fileName} content mismatch {(int)encodedBuf[i]} <> {(int)buf[i]} at index {i}"); + } + } + + private static void AssertFileContentsEndsWith(string fileName, string contents, Encoding encoding) + { + if (!File.Exists(fileName)) + Assert.Fail("File '" + fileName + "' doesn't exist."); + + string fileText = File.ReadAllText(fileName, encoding); + Assert.True(fileText.Length >= contents.Length); + Assert.Equal(contents, fileText.Substring(fileText.Length - contents.Length)); + } } } diff --git a/tests/NLog.UnitTests/Targets/TargetTests.cs b/tests/NLog.UnitTests/Targets/TargetTests.cs index 99d4e8d3b4..b477b07f57 100644 --- a/tests/NLog.UnitTests/Targets/TargetTests.cs +++ b/tests/NLog.UnitTests/Targets/TargetTests.cs @@ -168,8 +168,6 @@ public void TargetContructorWithNameTest() CheckEquals(targetType, targetConstructedTarget, namedConstructedTarget, ref lastPropertyName, ref checkCount); } - - } catch (Exception ex) { @@ -179,6 +177,7 @@ public void TargetContructorWithNameTest() Assert.False(constructionFailed, failureMessage); } } + Assert.Equal(neededCheckCount, checkCount); } @@ -274,7 +273,7 @@ public override bool Equals(AsyncRequestQueue x, AsyncRequestQueue y) { if (x is null) return y is null; - return x.RequestLimit == y.RequestLimit && x.OnOverflow == y.OnOverflow; + return x.QueueLimit == y.QueueLimit && x.OnOverflow == y.OnOverflow; } /// @@ -288,7 +287,7 @@ public override int GetHashCode(AsyncRequestQueue obj) { unchecked { - return (obj.RequestLimit * 397) ^ (int)obj.OnOverflow; + return (obj.QueueLimit * 397) ^ (int)obj.OnOverflow; } } } @@ -535,11 +534,9 @@ public void FlushWithoutInitializeTest() } [Theory] - [InlineData("Trace")] - [InlineData("TRACE")] - [InlineData("TraceSystem")] - [InlineData("TraceSYSTEM")] - [InlineData("Trace--SYSTEM")] + [InlineData("DebugSystem")] + [InlineData("Debug-System")] + [InlineData("DEBUGSYSTEM")] public void TargetAliasShouldWork(string typeName) { LoggingConfiguration c = XmlLoggingConfiguration.CreateFromXmlString($@" @@ -549,7 +546,7 @@ public void TargetAliasShouldWork(string typeName) "); - var t = c.FindTargetByName("d"); + var t = c.FindTargetByName("d"); Assert.NotNull(t); } @@ -762,7 +759,6 @@ protected override void InitializeTarget() } } - [Fact] public void TypedLayoutTargetTest() { @@ -789,6 +785,9 @@ public void TypedLayoutTargetTest() typeProperty='System.Int32' uriProperty='https://nlog-project.org' lineEndingModeProperty='default' + shortSetProperty='1,2,3' + shortListProperty='1,2,3' + longListProperty='1,2,3' /> "); @@ -811,6 +810,18 @@ public void TypedLayoutTargetTest() Assert.Equal(typeof(int), myTarget.TypeProperty.FixedValue); Assert.Equal(new Uri("https://nlog-project.org"), myTarget.UriProperty.FixedValue); Assert.Equal(LineEndingMode.Default, myTarget.LineEndingModeProperty.FixedValue); + Assert.Equal(3, myTarget.ShortSetProperty.Count); + Assert.Contains((short)1, myTarget.ShortSetProperty); + Assert.Contains((short)2, myTarget.ShortSetProperty); + Assert.Contains((short)3, myTarget.ShortSetProperty); + Assert.Equal(3, myTarget.ShortListProperty.Count); + Assert.Contains((short)1, myTarget.ShortListProperty); + Assert.Contains((short)2, myTarget.ShortListProperty); + Assert.Contains((short)3, myTarget.ShortListProperty); + Assert.Equal(3, myTarget.LongListProperty.Count); + Assert.Contains(1L, myTarget.LongListProperty); + Assert.Contains(2L, myTarget.LongListProperty); + Assert.Contains(3L, myTarget.LongListProperty); } [Fact] @@ -862,7 +873,9 @@ public void TypedLayoutTargetAsyncTest() Assert.Equal((byte)42, logEvent.Properties["ByteProperty"]); Assert.Equal((short)43, logEvent.Properties["Int16Property"]); Assert.Equal(CurrentManagedThreadId, logEvent.Properties["Int32Property"]); +#pragma warning disable CS0618 // Type or member is obsolete Assert.Equal((long)logEvent.SequenceID, logEvent.Properties["Int64Property"]); +#pragma warning restore CS0618 // Type or member is obsolete Assert.Equal(AppDomain.CurrentDomain.FriendlyName, logEvent.Properties["StringProperty"]); Assert.Equal(true, logEvent.Properties["BoolProperty"]); Assert.Equal(3.14159, logEvent.Properties["DoubleProperty"]); @@ -1022,6 +1035,15 @@ public class MyTypedLayoutTarget : Target public Layout LineEndingModeProperty { get; set; } +#if NET35 + public HashSet ShortSetProperty { get; set; } +#else + public IList ShortSetProperty { get; set; } +#endif + + public IList ShortListProperty { get; set; } + public IList LongListProperty { get; set; } = new List(); + protected override void Write(LogEventInfo logEvent) { logEvent.Properties[nameof(ByteProperty)] = RenderLogEvent(ByteProperty, logEvent); diff --git a/tests/NLog.UnitTests/Targets/TargetWithContextTest.cs b/tests/NLog.UnitTests/Targets/TargetWithContextTest.cs index 35c30b8c81..c12ed53af5 100644 --- a/tests/NLog.UnitTests/Targets/TargetWithContextTest.cs +++ b/tests/NLog.UnitTests/Targets/TargetWithContextTest.cs @@ -36,7 +36,6 @@ namespace NLog.UnitTests.Targets using System; using System.Collections.Generic; using System.Linq; - using System.Threading; using NLog.Targets; using NLog.Targets.Wrappers; using Xunit; @@ -128,7 +127,7 @@ public void TargetWithContextAsyncTest() private static bool WaitForLastMessage(CustomTargetWithContext target) { System.Threading.Thread.Sleep(1); - for (int i = 0; i < 1000; ++i) + for (int i = 0; i < 5000; ++i) { if (target.LastMessage != null) return true; @@ -202,6 +201,22 @@ private static void WriteAndAssertSingleKey(CustomTargetWithContext target) Assert.Equal("{ a = b }", target.LastCombinedProperties["TestKey"]); } + [Fact] + public void TargetWithContextStringLayoutTest() + { + var target = new CustomTargetWithContext() { Layout = "${message}", SkipAssert = true }; + var logFactory = new LogFactory().Setup() + .SetupExtensions(ext => ext.RegisterTarget("contexttarget")) + .LoadConfiguration(cfg => cfg.ForLogger().WriteTo(target)).LogFactory; + + var logEventInfo = LogEventInfo.Create(LogLevel.Info, null, null, "Hello {World}", new[] { "Earth" }); + var expectedMessage = logEventInfo.FormattedMessage; + + logFactory.GetCurrentClassLogger().Info(logEventInfo); + + Assert.Same(expectedMessage, target.LastMessage); + } + [Fact] public void TargetWithContextConfigTest() { @@ -329,7 +344,7 @@ public void TargetWithContextJsonTest() logger.Error("log message"); var target = logFactory.Configuration.AllTargets.OfType().FirstOrDefault(); System.Threading.Thread.Sleep(1); - for (int i = 0; i < 1000; ++i) + for (int i = 0; i < 5000; ++i) { if (target.LastMessage != null) break; @@ -386,5 +401,56 @@ public void TargetWithContextPropertyTypeTest() Assert.Contains(new KeyValuePair("object-non-existing", null), lastCombinedProperties); Assert.DoesNotContain("object-non-existing-empty", lastCombinedProperties.Keys); } + + [Theory] + [MemberData(nameof(ConvertFromStringTestCases))] + public void GetPropertyValueFromStringTest(string value, Type propertyType, object expected, bool? includeEmptyValue = null) + { + // Arrange + var targetPropertyWithContext = new TargetPropertyWithContext("@test", value) + { + PropertyType = propertyType, + IncludeEmptyValue = includeEmptyValue ?? false, + }; + + // Act + var result = targetPropertyWithContext.RenderValue(LogEventInfo.CreateNullEvent()); + + // Assert + Assert.Equal(expected, result); + } + + public static IEnumerable ConvertFromStringTestCases() + { + yield return new object[] { "true", typeof(bool), true }; + yield return new object[] { "True", typeof(bool), true }; + yield return new object[] { 1.2.ToString(), typeof(decimal), (decimal)1.2 }; + yield return new object[] { 1.2.ToString(), typeof(double), (double)1.2 }; + yield return new object[] { 1.2.ToString(), typeof(float), (float)1.2 }; + yield return new object[] { "2:30", typeof(TimeSpan), new TimeSpan(0, 2, 30, 0), }; + yield return new object[] { "2018-12-23 22:56", typeof(DateTime), new DateTime(2018, 12, 23, 22, 56, 0), }; + //yield return new object[] { new DateTime(2018, 12, 23, 22, 56, 0).ToString(CultureInfo.InvariantCulture), typeof(DateTime), new DateTime(2018, 12, 23, 22, 56, 0) }; + yield return new object[] { "2018-12-23", typeof(DateTime), new DateTime(2018, 12, 23, 0, 0, 0), }; + yield return new object[] { "2018-12-23 +2:30", typeof(DateTimeOffset), new DateTimeOffset(2018, 12, 23, 0, 0, 0, new TimeSpan(2, 30, 0)) }; + yield return new object[] { "3888CCA3-D11D-45C9-89A5-E6B72185D287", typeof(Guid), Guid.Parse("3888CCA3-D11D-45C9-89A5-E6B72185D287") }; + yield return new object[] { "3888CCA3D11D45C989A5E6B72185D287", typeof(Guid), Guid.Parse("3888CCA3-D11D-45C9-89A5-E6B72185D287") }; + yield return new object[] { "3", typeof(byte), (byte)3 }; + yield return new object[] { "3", typeof(sbyte), (sbyte)3 }; + yield return new object[] { "3", typeof(short), (short)3 }; + yield return new object[] { " 3 ", typeof(short), (short)3 }; + yield return new object[] { "3", typeof(int), 3 }; + yield return new object[] { "3", typeof(long), (long)3 }; + yield return new object[] { "3", typeof(ushort), (ushort)3 }; + yield return new object[] { "3", typeof(uint), (uint)3 }; + yield return new object[] { "3", typeof(ulong), (ulong)3 }; + yield return new object[] { "3", typeof(string), "3" }; + yield return new object[] { "${event-properties:userid}", typeof(int), 0, true }; + yield return new object[] { "${event-properties:userid}", typeof(int), (int?)null, false }; + yield return new object[] { "${date:universalTime=true:format=yyyy-MM:norawvalue=true}", typeof(DateTime), DateTime.SpecifyKind(DateTime.UtcNow.Date.AddDays(-DateTime.UtcNow.Day + 1), DateTimeKind.Unspecified) }; + yield return new object[] { "${shortdate:universalTime=true}", typeof(DateTime), DateTime.UtcNow.Date, true }; + yield return new object[] { "${shortdate:universalTime=true}", typeof(DateTime), DateTime.UtcNow.Date, false }; + yield return new object[] { "${shortdate:universalTime=true}", typeof(string), DateTime.UtcNow.Date.ToString("yyyy-MM-dd"), true }; + yield return new object[] { "${shortdate:universalTime=true}", typeof(string), DateTime.UtcNow.Date.ToString("yyyy-MM-dd"), false }; + } } } diff --git a/tests/NLog.UnitTests/Targets/Wrappers/AsyncRequestQueueTests.cs b/tests/NLog.UnitTests/Targets/Wrappers/AsyncRequestQueueTests.cs index f5d20959b0..13feb88ca8 100644 --- a/tests/NLog.UnitTests/Targets/Wrappers/AsyncRequestQueueTests.cs +++ b/tests/NLog.UnitTests/Targets/Wrappers/AsyncRequestQueueTests.cs @@ -49,7 +49,7 @@ public void AsyncRequestQueueWithDiscardBehaviorTest() var ev4 = LogEventInfo.CreateNullEvent().WithContinuation(ex => { }); var queue = new AsyncRequestQueue(3, AsyncTargetWrapperOverflowAction.Discard); - Assert.Equal(3, queue.RequestLimit); + Assert.Equal(3, queue.QueueLimit); Assert.Equal(AsyncTargetWrapperOverflowAction.Discard, queue.OnOverflow); Assert.Equal(0, queue.RequestCount); queue.Enqueue(ev1); @@ -82,7 +82,7 @@ public void AsyncRequestQueueWithGrowBehaviorTest() var ev4 = LogEventInfo.CreateNullEvent().WithContinuation(ex => { }); var queue = new AsyncRequestQueue(3, AsyncTargetWrapperOverflowAction.Grow); - Assert.Equal(3, queue.RequestLimit); + Assert.Equal(3, queue.QueueLimit); Assert.Equal(AsyncTargetWrapperOverflowAction.Grow, queue.OnOverflow); Assert.Equal(0, queue.RequestCount); queue.Enqueue(ev1); @@ -147,7 +147,7 @@ public void AsyncRequestQueueWithBlockBehavior() logEventInfos = queue.DequeueBatch(left); int got = logEventInfos.Length; - Assert.True(got <= queue.RequestLimit); + Assert.True(got <= queue.QueueLimit); total += got; } @@ -168,7 +168,7 @@ public void AsyncRequestQueueWithBlockBehavior() logEventInfos = queue.DequeueBatch(left); int got = logEventInfos.Length; - Assert.True(got <= queue.RequestLimit); + Assert.True(got <= queue.QueueLimit); total += got; } @@ -185,7 +185,7 @@ public void AsyncRequestQueueClearTest() var ev4 = LogEventInfo.CreateNullEvent().WithContinuation(ex => { }); var queue = new AsyncRequestQueue(3, AsyncTargetWrapperOverflowAction.Grow); - Assert.Equal(3, queue.RequestLimit); + Assert.Equal(3, queue.QueueLimit); Assert.Equal(AsyncTargetWrapperOverflowAction.Grow, queue.OnOverflow); Assert.Equal(0, queue.RequestCount); queue.Enqueue(ev1); @@ -210,43 +210,16 @@ public void AsyncRequestQueueClearTest() [Fact] public void RaiseEventLogEventQueueGrow_OnLogItems() { - const int RequestsLimit = 2; - const int EventsCount = 5; - const int ExpectedCountOfGrovingTimes = 2; - const int ExpectedFinalSize = 8; - int grovingItemsCount = 0; - - AsyncRequestQueue requestQueue = new AsyncRequestQueue(RequestsLimit, AsyncTargetWrapperOverflowAction.Grow); - - requestQueue.LogEventQueueGrow += (o, e) => { grovingItemsCount++; }; - - for (int i = 0; i < EventsCount; i++) - { - requestQueue.Enqueue(new AsyncLogEventInfo()); - } - - Assert.Equal(ExpectedCountOfGrovingTimes, grovingItemsCount); - Assert.Equal(ExpectedFinalSize, requestQueue.RequestLimit); + CommonRequestQueueTests.RaiseEventLogEventQueueGrow_OnLogItems(GetAsyncRequestQueue); } [Fact] public void RaiseEventLogEventDropped_OnLogItems() { - const int RequestsLimit = 2; - const int EventsCount = 5; - int discardedItemsCount = 0; - - int ExpectedDiscardedItemsCount = EventsCount - RequestsLimit; - AsyncRequestQueue requestQueue = new AsyncRequestQueue(RequestsLimit, AsyncTargetWrapperOverflowAction.Discard); - - requestQueue.LogEventDropped += (o, e) => { discardedItemsCount++; }; - - for (int i = 0; i < EventsCount; i++) - { - requestQueue.Enqueue(new AsyncLogEventInfo()); - } - - Assert.Equal(ExpectedDiscardedItemsCount, discardedItemsCount); + CommonRequestQueueTests.RaiseEventLogEventDropped_OnLogItems(GetAsyncRequestQueue); } + + private static AsyncRequestQueueBase GetAsyncRequestQueue(int size, AsyncTargetWrapperOverflowAction action) => + new AsyncRequestQueue(size, action); } } diff --git a/tests/NLog.UnitTests/Targets/Wrappers/AsyncTargetWrapperTests.cs b/tests/NLog.UnitTests/Targets/Wrappers/AsyncTargetWrapperTests.cs index 3eae20a3c3..1feb308f52 100644 --- a/tests/NLog.UnitTests/Targets/Wrappers/AsyncTargetWrapperTests.cs +++ b/tests/NLog.UnitTests/Targets/Wrappers/AsyncTargetWrapperTests.cs @@ -73,13 +73,19 @@ public void AsyncTargetWrapperInitTest2() [Fact] public void AsyncTargetWrapperSyncTest_WithLock_WhenTimeToSleepBetweenBatchesIsEqualToZero() { - AsyncTargetWrapperSyncTest_WhenTimeToSleepBetweenBatchesIsEqualToZero(true); + RetryingIntegrationTest(3, () => + { + AsyncTargetWrapperSyncTest_WhenTimeToSleepBetweenBatchesIsEqualToZero(true); + }); } [Fact] public void AsyncTargetWrapperSyncTest_NoLock_WhenTimeToSleepBetweenBatchesIsEqualToZero() { - AsyncTargetWrapperSyncTest_WhenTimeToSleepBetweenBatchesIsEqualToZero(false); + RetryingIntegrationTest(3, () => + { + AsyncTargetWrapperSyncTest_WhenTimeToSleepBetweenBatchesIsEqualToZero(false); + }); } /// @@ -115,7 +121,9 @@ private static void AsyncTargetWrapperSyncTest_WhenTimeToSleepBetweenBatchesIsEq for (int i = 0; i < itemPrepareList.Capacity; ++i) { var logEvent = new LogEventInfo(); +#pragma warning disable CS0618 // Type or member is obsolete int sequenceID = logEvent.SequenceID; +#pragma warning restore CS0618 // Type or member is obsolete bool blockConsumer = (itemPrepareList.Capacity / 2) == i; // Force producers to get into blocking-mode itemPrepareList.Add(logEvent.WithContinuation((ex) => { if (blockConsumer) Thread.Sleep(125); itemWrittenList.Add(sequenceID); })); } @@ -178,7 +186,7 @@ private static void AsyncTargetWrapperSyncTest_WhenTimeToSleepBetweenBatchesIsEq #if DEBUG if (!IsAppVeyor()) // Skip timing test when running within OpenCover.Console.exe #endif - Assert.InRange(elapsedMilliseconds, 0, 975); + Assert.InRange(elapsedMilliseconds, 0, 975); targetWrapper.Flush(flushHandler); for (int i = 0; i < 2000 && flushCounter != 2; ++i) @@ -508,11 +516,12 @@ public void FlushingMultipleTimesSimultaneous() } [Fact] - public void LogEventDropped_OnRequestqueueOverflow() + public void LogEventDropped_OnRequestQueueOverflow() { - int queueLimit = 2; - int loggedEventCount = 5; - int eventsCounter = 0; + const int queueLimit = 2; + const int loggedEventCount = 5; + const int expectedEventsDropped = loggedEventCount - queueLimit; + int eventsDroppedCounter = 0; var myTarget = new MyTarget(); var targetWrapper = new AsyncTargetWrapper() @@ -531,18 +540,38 @@ public void LogEventDropped_OnRequestqueueOverflow() try { - targetWrapper.LogEventDropped += (o, e) => { eventsCounter++; }; + // subscribe, log, assert values were reported + targetWrapper.LogEventDropped += OnLogEventDropped; + + LogEvents(); + + Assert.Equal(expectedEventsDropped, eventsDroppedCounter); + // unsubscribe, log, assert values were not reported + targetWrapper.LogEventDropped -= OnLogEventDropped; + + LogEvents(); + + Assert.Equal(expectedEventsDropped, eventsDroppedCounter); + } + finally + { + logFactory.Configuration = null; + } + + return; + + void LogEvents() + { for (int i = 0; i < loggedEventCount; i++) { logger.Info("Hello"); } - - Assert.Equal(loggedEventCount - queueLimit, eventsCounter); } - finally + + void OnLogEventDropped(object _, LogEventDroppedEventArgs logEventDroppedEventArgs) { - logFactory.Configuration = null; + eventsDroppedCounter++; } } @@ -625,12 +654,15 @@ public void LogEventNotDropped_IfOverflowActionGrow() [Fact] public void EventQueueGrow_OnQueueGrow() { - int queueLimit = 2; - int loggedEventCount = 10; - - int expectedGrowingNumber = 3; - - int eventsCounter = 0; + const int queueLimit = 2; + const int loggedEventCount = 10; + const int expectedGrowingNumber = 3; + const int expectedNewQueueSize = 16; + const int expectedRequestsCount = expectedNewQueueSize / 2 + 1; + + int growEventsCounter = 0; + long reportedRequestsCount = 0; + long reportedNewQueueSize = 0; var myTarget = new MyTarget(); var targetWrapper = new AsyncTargetWrapper() @@ -649,19 +681,146 @@ public void EventQueueGrow_OnQueueGrow() try { - targetWrapper.EventQueueGrow += (o, e) => { eventsCounter++; }; + // subscribe, log, assert values were reported + targetWrapper.EventQueueGrow += OnEventQueueGrow; + LogEvents(); + + Assert.Equal(expectedGrowingNumber, growEventsCounter); + Assert.Equal(expectedRequestsCount, reportedRequestsCount); + Assert.Equal(expectedNewQueueSize, reportedNewQueueSize); + + // unsubscribe, log, assert values were not reported + targetWrapper.EventQueueGrow -= OnEventQueueGrow; + + LogEvents(); + + Assert.Equal(expectedGrowingNumber, growEventsCounter); + Assert.Equal(expectedRequestsCount, reportedRequestsCount); + Assert.Equal(expectedNewQueueSize, reportedNewQueueSize); + } + finally + { + logFactory.Configuration = null; + } + + return; + + void LogEvents() + { for (int i = 0; i < loggedEventCount; i++) { logger.Info("Hello"); } + } - Assert.Equal(expectedGrowingNumber, eventsCounter); + void OnEventQueueGrow(object _, LogEventQueueGrowEventArgs e) + { + growEventsCounter++; + reportedRequestsCount = e.RequestsCount; + reportedNewQueueSize = e.NewQueueSize; + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void EnqueueLimitReached_EventsAreCalled_SubscriptionOrderDoesntMatter(bool reverseSubscribeOrder) + { + const int queueLimit = 2; + const int loggedEventCount = 5; + const int expectedEventsDropped = loggedEventCount - queueLimit; + const int expectedGrowingNumber = 2; + const int expectedNewQueueSize = 8; + + int eventsDroppedCounter = 0; + int growEventsCounter = 0; + long reportedRequestsCount = 0; + long reportedNewQueueSize = 0; + var myTarget = new MyTarget(); + + var targetWrapper = new AsyncTargetWrapper() + { + WrappedTarget = myTarget, + QueueLimit = queueLimit, + TimeToSleepBetweenBatches = 500, // Make it slow + OverflowAction = AsyncTargetWrapperOverflowAction.Discard, + }; + + var logFactory = new LogFactory(); + var loggingConfig = new NLog.Config.LoggingConfiguration(logFactory); + loggingConfig.AddRuleForAllLevels(targetWrapper); + logFactory.Configuration = loggingConfig; + var logger = logFactory.GetLogger("Test"); + + try + { + // subscribe, log, assert values were reported - discards at first, then grows + if (reverseSubscribeOrder) + { + targetWrapper.LogEventDropped += OnLogEventDropped; + targetWrapper.EventQueueGrow += OnEventQueueGrow; + } + else + { + targetWrapper.EventQueueGrow += OnEventQueueGrow; + targetWrapper.LogEventDropped += OnLogEventDropped; + } + + LogEvents(); + + Assert.Equal(expectedEventsDropped, eventsDroppedCounter); + Assert.Equal(0, growEventsCounter); + + // and now grow + targetWrapper.OverflowAction = AsyncTargetWrapperOverflowAction.Grow; + + LogEvents(); + + Assert.Equal(expectedGrowingNumber, growEventsCounter); + Assert.Equal(loggedEventCount, reportedRequestsCount); + Assert.Equal(expectedNewQueueSize, reportedNewQueueSize); + Assert.Equal(expectedEventsDropped, eventsDroppedCounter); + + // unsubscribe, log, assert values were not reported + targetWrapper.LogEventDropped -= OnLogEventDropped; + targetWrapper.EventQueueGrow -= OnEventQueueGrow; + + targetWrapper.OverflowAction = AsyncTargetWrapperOverflowAction.Discard; + LogEvents(); + + targetWrapper.OverflowAction = AsyncTargetWrapperOverflowAction.Grow; + LogEvents(); + + Assert.Equal(expectedGrowingNumber, growEventsCounter); + Assert.Equal(expectedEventsDropped, eventsDroppedCounter); } finally { logFactory.Configuration = null; } + + return; + + void LogEvents() + { + for (int i = 0; i < loggedEventCount; i++) + { + logger.Info("Hello"); + } + } + + void OnLogEventDropped(object _, LogEventDroppedEventArgs logEventDroppedEventArgs) + { + eventsDroppedCounter++; + } + + void OnEventQueueGrow(object _, LogEventQueueGrowEventArgs e) + { + growEventsCounter++; + reportedRequestsCount = e.RequestsCount; + reportedNewQueueSize = e.NewQueueSize; + } } [Fact] diff --git a/tests/NLog.UnitTests/Targets/Wrappers/CommonRequestQueueTests.cs b/tests/NLog.UnitTests/Targets/Wrappers/CommonRequestQueueTests.cs new file mode 100644 index 0000000000..10ec7f3e31 --- /dev/null +++ b/tests/NLog.UnitTests/Targets/Wrappers/CommonRequestQueueTests.cs @@ -0,0 +1,128 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , Kim Christensen, Julian Verdurmen +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Jaroslaw Kowalski nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +namespace NLog.UnitTests.Targets.Wrappers +{ + using System; + using NLog.Common; + using NLog.Targets.Wrappers; + using Xunit; + + internal static class CommonRequestQueueTests + { + internal static void RaiseEventLogEventQueueGrow_OnLogItems(Func getQueue) + { + const int InitialSize = 1; + const int ExpectedFinalGrowingTimes = 3; + const int ExpectedFinalSize = 8; + + var requestQueue = getQueue(InitialSize, AsyncTargetWrapperOverflowAction.Grow); + + int growingTimesCount = 0; + long reportedRequestsCount = 0; + long reportedNewQueueSize = 0; + + requestQueue.LogEventQueueGrow += OnLogEventQueueGrow; + + requestQueue.Enqueue(new AsyncLogEventInfo()); + Assert.Equal(0, growingTimesCount); + + requestQueue.Enqueue(new AsyncLogEventInfo()); + Assert.Equal(1, growingTimesCount); + Assert.Equal(2, reportedRequestsCount); + Assert.Equal(2, reportedNewQueueSize); + + requestQueue.Enqueue(new AsyncLogEventInfo()); + Assert.Equal(2, growingTimesCount); + Assert.Equal(3, reportedRequestsCount); + Assert.Equal(4, reportedNewQueueSize); + + requestQueue.Enqueue(new AsyncLogEventInfo()); + Assert.Equal(2, growingTimesCount); + + requestQueue.Enqueue(new AsyncLogEventInfo()); + Assert.Equal(ExpectedFinalGrowingTimes, growingTimesCount); + Assert.Equal(5, reportedRequestsCount); + Assert.Equal(ExpectedFinalSize, reportedNewQueueSize); + Assert.Equal(ExpectedFinalSize, requestQueue.QueueLimit); + + requestQueue.LogEventQueueGrow -= OnLogEventQueueGrow; + + for (var i = reportedRequestsCount; i < ExpectedFinalSize + 1; i++) + { + requestQueue.Enqueue(new AsyncLogEventInfo()); + } + + Assert.Equal(ExpectedFinalGrowingTimes, growingTimesCount); + Assert.Equal(ExpectedFinalSize * 2, requestQueue.QueueLimit); + + return; + + void OnLogEventQueueGrow(object _, LogEventQueueGrowEventArgs e) + { + growingTimesCount++; + reportedRequestsCount = e.RequestsCount; + reportedNewQueueSize = e.NewQueueSize; + } + } + + internal static void RaiseEventLogEventDropped_OnLogItems(Func getQueue) + { + const int RequestsLimit = 2; + const int EventsCount = 5; + const int ExpectedDiscardedItemsCount = EventsCount - RequestsLimit; + int discardedItemsCount = 0; + + var requestQueue = getQueue(RequestsLimit, AsyncTargetWrapperOverflowAction.Discard); + requestQueue.LogEventDropped += OnLogEventDropped; + + for (int i = 0; i < EventsCount; i++) + { + requestQueue.Enqueue(new AsyncLogEventInfo()); + } + + Assert.Equal(ExpectedDiscardedItemsCount, discardedItemsCount); + + requestQueue.LogEventDropped -= OnLogEventDropped; + requestQueue.Enqueue(new AsyncLogEventInfo()); + Assert.Equal(ExpectedDiscardedItemsCount, discardedItemsCount); + + return; + + void OnLogEventDropped(object o, LogEventDroppedEventArgs e) + { + discardedItemsCount++; + } + } + } +} diff --git a/tests/NLog.UnitTests/Targets/Wrappers/ConcurrentRequestQueueTests.cs b/tests/NLog.UnitTests/Targets/Wrappers/ConcurrentRequestQueueTests.cs index 7ca818cbf8..ef0630937e 100644 --- a/tests/NLog.UnitTests/Targets/Wrappers/ConcurrentRequestQueueTests.cs +++ b/tests/NLog.UnitTests/Targets/Wrappers/ConcurrentRequestQueueTests.cs @@ -34,6 +34,8 @@ #if !NET35 using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using NLog.Common; using NLog.Targets.Wrappers; using Xunit; @@ -66,46 +68,72 @@ public void DequeueBatch_WithNonEmptyList_ReturnsValidCount(AsyncTargetWrapperOv } [Fact] - public void RaiseEventLogEventQueueGrow_OnLogItems() + public void Enqueue_WhenGrowBehaviourAndHighlyConcurrent_GrowOnce() { - const int RequestsLimit = 2; - const int EventsCount = 5; - const int ExpectedCountOfGrovingTimes = 2; - const int ExpectedFinalSize = 8; - int grovingItemsCount = 0; - - ConcurrentRequestQueue requestQueue = new ConcurrentRequestQueue(RequestsLimit, AsyncTargetWrapperOverflowAction.Grow); - - requestQueue.LogEventQueueGrow += (o, e) => { grovingItemsCount++; }; + // Arrange + var requestQueue = new ConcurrentRequestQueue(2, AsyncTargetWrapperOverflowAction.Grow); - for (int i = 0; i < EventsCount; i++) + for (int i = 0; i < 4; i++) { requestQueue.Enqueue(new AsyncLogEventInfo()); } - Assert.Equal(ExpectedCountOfGrovingTimes, grovingItemsCount); - Assert.Equal(ExpectedFinalSize, requestQueue.RequestLimit); - } - - [Fact] - public void RaiseEventLogEventDropped_OnLogItems() - { - const int RequestsLimit = 2; - const int EventsCount = 5; - int discardedItemsCount = 0; + const int initialQueueCount = 4; + const int initialQueueLimit = 4; + Assert.Equal(initialQueueCount, requestQueue.Count); + Assert.Equal(initialQueueLimit, requestQueue.QueueLimit); - int ExpectedDiscardedItemsCount = EventsCount - RequestsLimit; - ConcurrentRequestQueue requestQueue = new ConcurrentRequestQueue(RequestsLimit, AsyncTargetWrapperOverflowAction.Discard); + const int threadCount = 4; + var readyToEnqueue = new Barrier(threadCount); + var enqueued = new CountdownEvent(threadCount); - requestQueue.LogEventDropped += (o, e) => { discardedItemsCount++; }; + // Act + for (int i = 0; i < 100; i++) + { + for (int j = 0; j < threadCount; j++) + { + Task.Run(EnqueueWhenAllThreadsReady); + } + + Assert.True(enqueued.Wait(10000)); + enqueued.Reset(); + + // Assert + const int expectedQueueCount = initialQueueCount + threadCount; + const int expectedQueueLimit = initialQueueLimit * 2; + Assert.Equal(expectedQueueCount, requestQueue.Count); + Assert.Equal(expectedQueueLimit, requestQueue.QueueLimit); + + // rollback requests count and limit + requestQueue.DequeueBatch(threadCount); + requestQueue.QueueLimit = initialQueueLimit; + } - for (int i = 0; i < EventsCount; i++) + void EnqueueWhenAllThreadsReady() { - requestQueue.Enqueue(new AsyncLogEventInfo()); + var logEvent = new AsyncLogEventInfo(); + readyToEnqueue.SignalAndWait(); + + requestQueue.Enqueue(logEvent); + + enqueued.Signal(); } + } + + [Fact] + public void RaiseEventLogEventQueueGrow_OnLogItems() + { + CommonRequestQueueTests.RaiseEventLogEventQueueGrow_OnLogItems(GetConcurrentRequestQueue); + } - Assert.Equal(ExpectedDiscardedItemsCount, discardedItemsCount); + [Fact] + public void RaiseEventLogEventDropped_OnLogItems() + { + CommonRequestQueueTests.RaiseEventLogEventDropped_OnLogItems(GetConcurrentRequestQueue); } + + private static AsyncRequestQueueBase GetConcurrentRequestQueue(int size, AsyncTargetWrapperOverflowAction action) => + new ConcurrentRequestQueue(size, action); } } diff --git a/tests/NLog.UnitTests/Targets/Wrappers/FallbackGroupTargetTests.cs b/tests/NLog.UnitTests/Targets/Wrappers/FallbackGroupTargetTests.cs index 8e3acc8d70..607800cf96 100644 --- a/tests/NLog.UnitTests/Targets/Wrappers/FallbackGroupTargetTests.cs +++ b/tests/NLog.UnitTests/Targets/Wrappers/FallbackGroupTargetTests.cs @@ -294,7 +294,7 @@ public void FallbackGroupTargetAsyncTest() ManualResetEvent resetEvent = new ManualResetEvent(false); myTarget1Async.Flush((ex) => { Assert.Null(ex); resetEvent.Set(); }); - resetEvent.WaitOne(1000); + Assert.True(resetEvent.WaitOne(10000)); Assert.Equal(10, exceptions.Count); for (var i = 0; i < 10; ++i) diff --git a/tests/NLog.UnitTests/TimeSourceTests.cs b/tests/NLog.UnitTests/TimeSourceTests.cs index 3ec9e7c76e..f97a328b81 100644 --- a/tests/NLog.UnitTests/TimeSourceTests.cs +++ b/tests/NLog.UnitTests/TimeSourceTests.cs @@ -99,7 +99,7 @@ public void ToStringNoImplementationTest(Type timeSourceType) Assert.Equal(expected, actual); } - class CustomTimeSource : TimeSource + private sealed class CustomTimeSource : TimeSource { public override DateTime Time => FromSystemTime(DateTime.UtcNow); diff --git a/tests/NLog.WindowsRegistry.Tests/NLog.WindowsRegistry.Tests.csproj b/tests/NLog.WindowsRegistry.Tests/NLog.WindowsRegistry.Tests.csproj index 5ce368978c..a4a5ce6d47 100644 --- a/tests/NLog.WindowsRegistry.Tests/NLog.WindowsRegistry.Tests.csproj +++ b/tests/NLog.WindowsRegistry.Tests/NLog.WindowsRegistry.Tests.csproj @@ -3,7 +3,7 @@ 17.0 net462 - net462;net6.0 + net462;net8.0 false @@ -15,13 +15,9 @@ Full - - $(DefineConstants);NETSTANDARD - - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/NLog.WindowsRegistry.Tests/RegistryTests.cs b/tests/NLog.WindowsRegistry.Tests/RegistryTests.cs index e80cfcc5bb..c25108a5b5 100644 --- a/tests/NLog.WindowsRegistry.Tests/RegistryTests.cs +++ b/tests/NLog.WindowsRegistry.Tests/RegistryTests.cs @@ -37,7 +37,7 @@ namespace NLog.WindowsRegistry.Tests using Microsoft.Win32; using Xunit; -#if NETSTANDARD +#if !NETFRAMEWORK [System.Runtime.Versioning.SupportedOSPlatform("windows")] #endif public sealed class RegistryTests : IDisposable diff --git a/tests/PackageLoaderTestAssembly/PackageLoaderTestAssembly.csproj b/tests/PackageLoaderTestAssembly/PackageLoaderTestAssembly.csproj deleted file mode 100644 index 6d0f15f63e..0000000000 --- a/tests/PackageLoaderTestAssembly/PackageLoaderTestAssembly.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - net462;netstandard2.0 - - - - - - - diff --git a/tests/TestTrimPublish/Program.cs b/tests/TestTrimPublish/Program.cs index c215cdf3fa..eadc09056b 100644 --- a/tests/TestTrimPublish/Program.cs +++ b/tests/TestTrimPublish/Program.cs @@ -39,6 +39,9 @@ Console.WriteLine(result); +var processPath = Environment.ProcessPath ?? string.Empty; +Console.WriteLine($"ProcessPath: '{processPath}' with FileSize: {new FileInfo(processPath).Length}"); + if (result == $"{Environment.CurrentManagedThreadId}|Success{System.Environment.NewLine}") return 0; else diff --git a/tests/TestTrimPublish/TestTrimPublish.csproj b/tests/TestTrimPublish/TestTrimPublish.csproj index e0e0b802f4..1498144bea 100644 --- a/tests/TestTrimPublish/TestTrimPublish.csproj +++ b/tests/TestTrimPublish/TestTrimPublish.csproj @@ -10,12 +10,16 @@ enable enable true + true true win-x64 true true + true + false diff --git a/tools/DumpApiXml/DocFileBuilder.cs b/tools/DumpApiXml/DocFileBuilder.cs index 50e5d3cdac..15baa75e53 100644 --- a/tools/DumpApiXml/DocFileBuilder.cs +++ b/tools/DumpApiXml/DocFileBuilder.cs @@ -18,6 +18,7 @@ public class DocFileBuilder { typeof(bool).FullName, "Boolean" }, { typeof(char).FullName, "Char" }, { typeof(byte).FullName, "Byte" }, + { typeof(short).FullName, "Integer" }, { typeof(CultureInfo).FullName, "Culture" }, { typeof(Encoding).FullName, "Encoding" }, { "NLog.Layouts.Layout", "Layout" }, @@ -118,7 +119,6 @@ private string FixWhitespace(string p) private static string GetTypeName(Type type) { string simpleName; - type = GetUnderlyingType(type); if (simpleTypeNames.TryGetValue(type.FullName, out simpleName)) @@ -131,6 +131,11 @@ private static string GetTypeName(Type type) private static Type GetUnderlyingType(Type type) { + if (type.IsGenericType && type.Name.StartsWith("Layout")) + { + type = type.GetGenericArguments()[0]; + } + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { type = type.GetGenericArguments()[0]; @@ -479,6 +484,35 @@ private static bool TryGetPropertyDefaultValue(object configInstance, PropertyIn return true; } + var underlyingType = GetUnderlyingType(propInfo.PropertyType); + if (propertyValue != null && underlyingType != null && !ReferenceEquals(underlyingType, propInfo.PropertyType) && !underlyingType.Equals(propertyValue?.GetType())) + { + try + { + if (underlyingType.IsEnum) + { + var enumStringValue = propertyValue?.ToString(); + defaultValue = Enum.Parse(underlyingType, enumStringValue, true).ToString(); + return true; + } + else if (typeof(IConvertible).IsAssignableFrom(underlyingType)) + { + var convStringValue = propertyValue?.ToString(); + propertyValue = Convert.ChangeType(convStringValue, underlyingType); + } + else + { + defaultValue = null; + return false; + } + } + catch + { + defaultValue = null; + return false; + } + } + IConvertible convertibleValue = propertyValue as IConvertible; if (convertibleValue == null && propertyValue != null) { diff --git a/tools/DumpApiXml/DumpApiXml.csproj b/tools/DumpApiXml/DumpApiXml.csproj index 596b1da527..010fd9b504 100644 --- a/tools/DumpApiXml/DumpApiXml.csproj +++ b/tools/DumpApiXml/DumpApiXml.csproj @@ -1,56 +1,21 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {773C477E-2626-432D-B959-325B3B146744} + net472 Exe - Properties - DumpApiXml - DumpApiXml - v4.7.2 + false + true ..\..\build\bin\Tools\ - ..\..\build\obj\Tools\ - 512 - - - true - full - false - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - false - - - pdbonly - true - TRACE - prompt - 4 - AllRules.ruleset - false - - - - - - - - - - - - - + - - Designer - + - + + + + + + + \ No newline at end of file diff --git a/tools/DumpApiXml/DumpApiXml.sln b/tools/DumpApiXml/DumpApiXml.sln index d5244595d0..7d0b4cce80 100644 --- a/tools/DumpApiXml/DumpApiXml.sln +++ b/tools/DumpApiXml/DumpApiXml.sln @@ -1,8 +1,12 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35931.197 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DumpApiXml", "DumpApiXml.csproj", "{773C477E-2626-432D-B959-325B3B146744}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLogNugetPackages", "..\NLogNugetPackages\NLogNugetPackages.csproj", "{6C7CF943-03E5-FBA1-7103-FAFF78DEAE0F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -13,8 +17,15 @@ Global {773C477E-2626-432D-B959-325B3B146744}.Debug|Any CPU.Build.0 = Debug|Any CPU {773C477E-2626-432D-B959-325B3B146744}.Release|Any CPU.ActiveCfg = Release|Any CPU {773C477E-2626-432D-B959-325B3B146744}.Release|Any CPU.Build.0 = Release|Any CPU + {6C7CF943-03E5-FBA1-7103-FAFF78DEAE0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C7CF943-03E5-FBA1-7103-FAFF78DEAE0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C7CF943-03E5-FBA1-7103-FAFF78DEAE0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C7CF943-03E5-FBA1-7103-FAFF78DEAE0F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {003EDB45-0CA0-43AD-95CA-7C0389D2E186} + EndGlobalSection EndGlobal diff --git a/tools/DumpApiXml/Program.cs b/tools/DumpApiXml/Program.cs index 889c42c184..83d02c1fb6 100644 --- a/tools/DumpApiXml/Program.cs +++ b/tools/DumpApiXml/Program.cs @@ -63,6 +63,38 @@ private static int Main(string[] args) return 1; } + var outputDir = Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); + if (!string.IsNullOrEmpty(outputDir)) + { + var assemblyFiles = Directory.GetFiles(outputDir, "NLog*.dll"); + Console.WriteLine("Detected {0} assemblies in folder: {1}", assemblyFiles.Length, outputDir); + + foreach (var assembly in assemblyFiles) + { + if (assembly.EndsWith("NLog.dll", StringComparison.OrdinalIgnoreCase)) + continue; + + try + { + builder.LoadAssembly(assembly); + string docpath = Path.ChangeExtension(assembly, ".xml"); + if (File.Exists(docpath)) + { + builder.LoadComments(docpath); + Console.WriteLine("Loaded assembly with XML-docs: {0}", Path.GetFileName(assembly)); + } + else + { + Console.WriteLine("Loaded assembly without XML-docs: {0}", Path.GetFileName(assembly)); + } + } + catch (Exception ex) + { + Console.WriteLine("Failed loading assembly {0} - {1}", Path.GetFullPath(assembly), ex); + } + } + } + outputFile = Path.GetFullPath(outputFile); Console.WriteLine("Generating '{0}'...", outputFile); Directory.CreateDirectory(Path.GetDirectoryName(outputFile)); diff --git a/tools/NLogNugetPackages/NLogNugetPackages.csproj b/tools/NLogNugetPackages/NLogNugetPackages.csproj new file mode 100644 index 0000000000..5e8ee72428 --- /dev/null +++ b/tools/NLogNugetPackages/NLogNugetPackages.csproj @@ -0,0 +1,34 @@ + + + + net472 + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb;.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/SandcastleDocs/BuildDoc.cmd b/tools/SandcastleDocs/BuildDoc.cmd index 059e30996d..86a4f852f8 100644 --- a/tools/SandcastleDocs/BuildDoc.cmd +++ b/tools/SandcastleDocs/BuildDoc.cmd @@ -1,6 +1,6 @@ @echo off -set BuildVersion=5.0 +set BuildVersion=6.0 dotnet msbuild /t:restore,rebuild %~dp0\dll_to_doc /p:Configuration=Release /verbosity:minimal dotnet msbuild /t:restore %~dp0NLog.shfbproj /p:Configuration=Release /verbosity:minimal diff --git a/tools/SandcastleDocs/NLog.shfbproj b/tools/SandcastleDocs/NLog.shfbproj index 37ae00eb4d..1552d1d6e1 100644 --- a/tools/SandcastleDocs/NLog.shfbproj +++ b/tools/SandcastleDocs/NLog.shfbproj @@ -29,12 +29,34 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + @@ -43,20 +65,10 @@ - - - - - - - - - - NLog $(BuildVersion) Copyright %28c%29 2004-2024 NLog diff --git a/tools/SandcastleDocs/dll_to_doc/dll_to_doc.csproj b/tools/SandcastleDocs/dll_to_doc/dll_to_doc.csproj index a564157cad..a3b2c31a2e 100644 --- a/tools/SandcastleDocs/dll_to_doc/dll_to_doc.csproj +++ b/tools/SandcastleDocs/dll_to_doc/dll_to_doc.csproj @@ -1,78 +1,22 @@  - - Exe - net472 - false + + Exe + net472 + false + - + + + + true + + - - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - - - Always - - + + + + + \ No newline at end of file diff --git a/tools/SandcastleDocs/dll_to_doc/dll_to_doc.sln b/tools/SandcastleDocs/dll_to_doc/dll_to_doc.sln index 0e3de54e95..e7e4667c25 100644 --- a/tools/SandcastleDocs/dll_to_doc/dll_to_doc.sln +++ b/tools/SandcastleDocs/dll_to_doc/dll_to_doc.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.2.32516.85 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dll_to_doc", "dll_to_doc.csproj", "{AA900992-9A6B-44F4-B891-193531554B55}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLogNugetPackages", "..\..\NLogNugetPackages\NLogNugetPackages.csproj", "{97E582D4-2DC1-0776-FDE6-BBD03EFD8375}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {AA900992-9A6B-44F4-B891-193531554B55}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA900992-9A6B-44F4-B891-193531554B55}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA900992-9A6B-44F4-B891-193531554B55}.Release|Any CPU.Build.0 = Release|Any CPU + {97E582D4-2DC1-0776-FDE6-BBD03EFD8375}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97E582D4-2DC1-0776-FDE6-BBD03EFD8375}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97E582D4-2DC1-0776-FDE6-BBD03EFD8375}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97E582D4-2DC1-0776-FDE6-BBD03EFD8375}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE