From f5e2b959334d3b62166dc0f68c3a035e30d76283 Mon Sep 17 00:00:00 2001
From: Rolf Kristensen
Date: Sat, 25 Mar 2023 12:27:51 +0100
Subject: [PATCH] Added support for MailHeaders (align with NLog MailTarget)
---
azure-pipelines.yml | 2 +-
src/NLog.MailKit/MailTarget.cs | 249 +++++++--------
src/NLog.MailKit/NLog.MailKit.csproj | 8 +-
src/NLog.MailKit/Util/ExceptionHelper.cs | 107 -------
src/NLog.MailKit/Util/SortHelpers.cs | 366 -----------------------
5 files changed, 111 insertions(+), 621 deletions(-)
delete mode 100644 src/NLog.MailKit/Util/ExceptionHelper.cs
delete mode 100644 src/NLog.MailKit/Util/SortHelpers.cs
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 0398ccf..6cf68d2 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -13,7 +13,7 @@ variables:
Solution: 'src/NLog.MailKit.sln'
BuildPlatform: 'Any CPU'
BuildConfiguration: 'Release'
- Version: '5.0.2'
+ Version: '5.1.3'
FullVersion: '$(Version).$(Build.BuildId)'
steps:
diff --git a/src/NLog.MailKit/MailTarget.cs b/src/NLog.MailKit/MailTarget.cs
index 1eacb3b..af26068 100644
--- a/src/NLog.MailKit/MailTarget.cs
+++ b/src/NLog.MailKit/MailTarget.cs
@@ -34,7 +34,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
-using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using System.Text;
using MailKit.Net.Smtp;
using MailKit.Security;
@@ -43,26 +43,24 @@
using NLog.Common;
using NLog.Config;
using NLog.Layouts;
-using NLog.MailKit.Util;
using NLog.Targets;
namespace NLog.MailKit
{
///
- /// Sends log messages by email using SMTP protocol with MailKit.
+ /// Sends log messages by email using SMTP protocol.
///
+ ///
+ /// See NLog Wiki
+ ///
/// Documentation on NLog Wiki
///
///
- /// To set up the target in the configuration file,
+ /// To set up the target in the configuration file,
/// use the following syntax:
///
///
///
- /// This assumes just one target and a single rule. More configuration
- /// options are described here.
- ///
- ///
/// To set up the log target programmatically use code like this:
///
///
@@ -71,7 +69,7 @@ namespace NLog.MailKit
/// which lets you send multiple log messages in single mail
///
///
- /// To set up the buffered mail target in the configuration file,
+ /// To set up the buffered mail target in the configuration file,
/// use the following syntax:
///
///
@@ -81,7 +79,7 @@ namespace NLog.MailKit
///
///
[Target("Mail")]
- [SuppressMessage("ReSharper", "RedundantStringFormatCall")]
+ [Target("MailKit")]
public class MailTarget : TargetWithLayoutHeaderAndFooter
{
private static readonly Encoding DefaultEncoding = System.Text.Encoding.UTF8;
@@ -93,24 +91,18 @@ public class MailTarget : TargetWithLayoutHeaderAndFooter
/// Initializes a new instance of the class.
///
///
- /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message}
+ /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}
///
public MailTarget()
{
Body = "${message}${newline}";
- Subject = "Message from NLog on ${machinename}";
- Encoding = DefaultEncoding;
- SmtpPort = 25;
- SmtpAuthentication = SmtpAuthenticationMode.None;
- SecureSocketOption = DefaultSecureSocketOption;
- Timeout = 10000;
}
///
/// Initializes a new instance of the class.
///
///
- /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message}
+ /// The default value of the layout is: ${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}
///
/// Name of the target.
public MailTarget(string name) : this()
@@ -122,6 +114,7 @@ public MailTarget(string name) : this()
/// Gets or sets sender's email address (e.g. joe@domain.com).
///
///
+ [RequiredParameter]
public Layout From { get; set; }
///
@@ -147,24 +140,21 @@ public MailTarget(string name) : this()
/// 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.
- ///
- [DefaultValue(false)]
- public Layout AddNewLines { get; set; }
+ ///
+ public bool AddNewLines { get; set; }
///
/// Gets or sets the mail subject.
///
///
- [DefaultValue("Message from NLog on ${machinename}")]
[RequiredParameter]
- public Layout Subject { get; set; }
+ public Layout Subject { get; set; } = "Message from NLog on ${machinename}";
///
/// Gets or sets mail message body (repeated for each log message send in one mail).
///
/// Alias for the Layout property.
///
- [DefaultValue("${message}${newline}")]
public Layout Body
{
get => Layout;
@@ -174,29 +164,27 @@ public Layout Body
///
/// Gets or sets encoding to be used for sending e-mail.
///
- ///
- [DefaultValue("UTF8")]
- public Layout Encoding { get; set; }
+ ///
+ public Layout Encoding { get; set; } = DefaultEncoding;
///
/// Gets or sets a value indicating whether to send message as HTML instead of plain text.
///
- ///
- [DefaultValue(false)]
+ ///
public Layout Html { get; set; }
///
/// Gets or sets SMTP Server to be used for sending.
///
///
+ [RequiredParameter]
public Layout SmtpServer { get; set; }
///
/// Gets or sets SMTP Authentication mode.
///
///
- [DefaultValue("None")]
- public Layout SmtpAuthentication { get; set; }
+ public Layout SmtpAuthentication { get; set; } = SmtpAuthenticationMode.None;
///
/// Gets or sets the username used to connect to SMTP server (used when is set to "basic").
@@ -215,9 +203,7 @@ public Layout Body
///
/// See also
///
- ///
- /// .
- [DefaultValue(false)]
+ /// .
public Layout EnableSsl { get; set; }
///
@@ -227,80 +213,75 @@ public Layout Body
///
[DefaultValue(DefaultSecureSocketOption)]
[CLSCompliant(false)]
- public Layout SecureSocketOption { get; set; }
+ public Layout SecureSocketOption { get; set; } = DefaultSecureSocketOption;
///
/// Gets or sets the port number that SMTP Server is listening on.
///
///
- [DefaultValue(25)]
- public Layout SmtpPort { get; set; }
+ public Layout SmtpPort { get; set; } = 25;
///
/// Gets or sets a value indicating whether SmtpClient should ignore invalid certificate.
///
///
- /// .
- [DefaultValue(false)]
public Layout SkipCertificateValidation { get; set; }
///
/// Gets or sets the priority used for sending mails.
///
+ ///
public Layout Priority { get; set; }
///
- /// Gets or sets a value indicating whether NewLine characters in the body should be replaced with
tags.
+ /// Gets or sets a value indicating whether NewLine characters in the body should be replaced with
tags.
///
- /// Only happens when is set to true.
- [DefaultValue(false)]
+ /// Only happens when is set to true.
public Layout ReplaceNewlineWithBrTagInHtml { get; set; }
///
/// Gets or sets a value indicating the SMTP client timeout.
///
/// Warning: zero is not infinite waiting
- [DefaultValue(10000)]
- public Layout Timeout { get; set; }
+ public Layout Timeout { get; set; } = 10000;
///
- /// Renders the logging event message and adds it to the internal ArrayList of log messages.
+ /// Gets the array of email headers that are transmitted with this email message
///
- /// The logging event.
+ ///
+ [ArrayParameter(typeof(MethodCallParameter), "mailheader")]
+ public IList MailHeaders { get; } = new List();
+
+ ///
protected override void Write(AsyncLogEventInfo logEvent)
{
Write(new[] { logEvent });
}
- ///
- /// Renders an array logging events.
- ///
- /// Array of logging events.
+ ///
protected override void Write(IList logEvents)
{
- var buckets = logEvents.BucketSort(c => GetSmtpSettingsKey(c.LogEvent));
- foreach (var bucket in buckets)
+ if (logEvents.Count == 1)
{
- var eventInfos = bucket.Value;
- ProcessSingleMailMessage(eventInfos);
+ ProcessSingleMailMessage(logEvents);
+ }
+ else
+ {
+ var buckets = logEvents.GroupBy(l => GetSmtpSettingsKey(l.LogEvent));
+ foreach (var bucket in buckets)
+ {
+ var eventInfos = bucket;
+ ProcessSingleMailMessage(eventInfos);
+ }
}
}
- ///
- /// Initializes the target. Can be used by inheriting classes
- /// to initialize logging.
- ///
+ ///
protected override void InitializeTarget()
{
InternalLogger.Debug("Init mailtarget with mailkit");
CheckRequiredParameters();
- var smtpAuthentication = RenderLogEvent(SmtpAuthentication, LogEventInfo.CreateNullEvent());
- if (smtpAuthentication == SmtpAuthenticationMode.Ntlm)
- {
- throw new NLogConfigurationException("NTLM not yet supported");
- }
-
base.InitializeTarget();
}
@@ -308,18 +289,17 @@ protected override void InitializeTarget()
/// Create mail and send with SMTP
///
/// event printed in the body of the event
- private void ProcessSingleMailMessage(IList events)
+ private void ProcessSingleMailMessage(IEnumerable events)
{
try
{
- if (events.Count == 0)
+ LogEventInfo firstEvent = events.FirstOrDefault().LogEvent;
+ LogEventInfo lastEvent = events.LastOrDefault().LogEvent;
+ if (firstEvent is null || lastEvent is null)
{
throw new NLogRuntimeException("We need at least one event.");
}
- var firstEvent = events[0].LogEvent;
- var lastEvent = events[events.Count - 1].LogEvent;
-
// unbuffered case, create a local buffer, append header, body and footer
var bodyBuffer = CreateBodyBuffer(events, firstEvent, lastEvent);
@@ -327,7 +307,6 @@ private void ProcessSingleMailMessage(IList events)
using (var client = new SmtpClient())
{
- CheckRequiredParameters();
client.Timeout = RenderLogEvent(Timeout, lastEvent);
var renderedHost = SmtpServer.Render(lastEvent);
@@ -383,13 +362,10 @@ private void ProcessSingleMailMessage(IList events)
catch (Exception exception)
{
//always log
- InternalLogger.Error(exception, "Error sending mail.");
+ InternalLogger.Error(exception, "{0}: Error sending mail.", this);
- if (exception.MustBeRethrown())
- {
+ if (LogManager.ThrowExceptions)
throw;
- }
-
foreach (var ev in events)
{
@@ -411,80 +387,56 @@ private StringBuilder CreateBodyBuffer(IEnumerable events, Lo
var addNewLines = RenderLogEvent(AddNewLines, firstEvent, false);
if (Header != null)
{
- bodyBuffer.Append(Header.Render(firstEvent));
+ bodyBuffer.Append(RenderLogEvent(Header, firstEvent));
if (addNewLines)
{
- bodyBuffer.Append("\n");
+ bodyBuffer.Append('\n');
}
}
foreach (var eventInfo in events)
{
- bodyBuffer.Append(Layout.Render(eventInfo.LogEvent));
+ bodyBuffer.Append(RenderLogEvent(Layout, eventInfo.LogEvent));
if (addNewLines)
{
- bodyBuffer.Append("\n");
+ bodyBuffer.Append('\n');
}
}
if (Footer != null)
{
- bodyBuffer.Append(Footer.Render(lastEvent));
+ bodyBuffer.Append(RenderLogEvent(Footer, lastEvent));
if (addNewLines)
{
- bodyBuffer.Append("\n");
+ bodyBuffer.Append('\n');
}
}
-
return bodyBuffer;
}
private void CheckRequiredParameters()
{
- if (SmtpServer == null)
- {
- throw new NLogConfigurationException(string.Format(RequiredPropertyIsEmptyFormat, nameof(SmtpServer)));
- }
-
- if (From == null)
+ var smtpAuthentication = RenderLogEvent(SmtpAuthentication, LogEventInfo.CreateNullEvent());
+ if (smtpAuthentication == SmtpAuthenticationMode.Ntlm)
{
- throw new NLogConfigurationException(string.Format(RequiredPropertyIsEmptyFormat, nameof(From)));
+ throw new NLogConfigurationException("NTLM not yet supported");
}
}
///
/// Create key for grouping. Needed for multiple events in one mail message
///
- /// event for rendering layouts
+ /// event for rendering layouts
/// string to group on
private string GetSmtpSettingsKey(LogEventInfo logEvent)
{
- var sb = new StringBuilder();
-
- AppendLayout(sb, logEvent, From);
- AppendLayout(sb, logEvent, To);
- AppendLayout(sb, logEvent, Cc);
- AppendLayout(sb, logEvent, Bcc);
- AppendLayout(sb, logEvent, SmtpServer);
- AppendLayout(sb, logEvent, SmtpPassword);
- AppendLayout(sb, logEvent, SmtpUserName);
-
- return sb.ToString();
- }
-
- ///
- /// Append rendered layout to the stringbuilder
- ///
- /// append to this
- /// event for rendering
- /// append if not null
- private static void AppendLayout(StringBuilder sb, LogEventInfo logEvent, Layout layout)
- {
- sb.Append("|");
- if (layout != null)
- {
- sb.Append(layout.Render(logEvent));
- }
+ return $@"{RenderLogEvent(From, logEvent)}
+{RenderLogEvent(To, logEvent)}
+{RenderLogEvent(Cc, logEvent)}
+{RenderLogEvent(Bcc, logEvent)}
+{RenderLogEvent(SmtpServer, logEvent)}
+{RenderLogEvent(SmtpPassword, logEvent)}
+{RenderLogEvent(SmtpUserName, logEvent)}";
}
///
@@ -494,13 +446,12 @@ private MimeMessage CreateMailMessage(LogEventInfo lastEvent, string body)
{
var msg = new MimeMessage();
- var renderedFrom = From?.Render(lastEvent);
+ var renderedFrom = RenderLogEvent(From, lastEvent);
if (string.IsNullOrEmpty(renderedFrom))
{
throw new NLogRuntimeException(string.Format(RequiredPropertyIsEmptyFormat, "From"));
}
-
msg.From.Add(MailboxAddress.Parse(renderedFrom));
var addedTo = AddAddresses(msg.To, To, lastEvent);
@@ -512,7 +463,7 @@ private MimeMessage CreateMailMessage(LogEventInfo lastEvent, string body)
throw new NLogRuntimeException(string.Format(RequiredPropertyIsEmptyFormat, "To/Cc/Bcc"));
}
- msg.Subject = Subject == null ? string.Empty : Subject.Render(lastEvent).Trim();
+ msg.Subject = (RenderLogEvent(Subject, lastEvent) ?? string.Empty).Trim();
if (Priority != null)
{
@@ -520,25 +471,33 @@ private MimeMessage CreateMailMessage(LogEventInfo lastEvent, string body)
msg.Priority = ParseMessagePriority(renderedPriority);
}
- TextPart CreateBodyPart()
+ var newBody = body;
+ var html = RenderLogEvent(Html, lastEvent);
+ var replaceNewlineWithBrTagInHtml = RenderLogEvent(ReplaceNewlineWithBrTagInHtml, lastEvent);
+ if (html && replaceNewlineWithBrTagInHtml)
{
- var newBody = body;
- var html = RenderLogEvent(Html, lastEvent);
- var replaceNewlineWithBrTagInHtml = RenderLogEvent(ReplaceNewlineWithBrTagInHtml, lastEvent);
- if (html && replaceNewlineWithBrTagInHtml)
- {
- newBody = newBody?.Replace(Environment.NewLine, "
");
- }
+ newBody = newBody?.Replace(Environment.NewLine, "
");
+ }
- var encoding = RenderLogEvent(Encoding, lastEvent, DefaultEncoding);
- return new TextPart(html ? TextFormat.Html : TextFormat.Plain)
+ var encoding = RenderLogEvent(Encoding, lastEvent, DefaultEncoding);
+ msg.Body = new TextPart(html ? TextFormat.Html : TextFormat.Plain)
+ {
+ Text = newBody,
+ ContentType = { Charset = encoding?.WebName }
+ };
+
+
+ if (MailHeaders?.Count > 0)
+ {
+ for (int i = 0; i < MailHeaders.Count; i++)
{
- Text = newBody,
- ContentType = { Charset = encoding?.WebName }
- };
- }
+ string headerValue = RenderLogEvent(MailHeaders[i].Layout, lastEvent);
+ if (headerValue is null)
+ continue;
- msg.Body = CreateBodyPart();
+ msg.Headers.Add(MailHeaders[i].Name, headerValue);
+ }
+ }
return msg;
}
@@ -584,18 +543,22 @@ internal static MessagePriority ParseMessagePriority(string priority)
/// layout with addresses, ; separated
/// event for rendering the
/// added a address?
- private static bool AddAddresses(InternetAddressList mailAddressCollection, Layout layout, LogEventInfo logEvent)
+ private bool AddAddresses(InternetAddressList mailAddressCollection, Layout layout, LogEventInfo logEvent)
{
- if (layout == null)
- {
- return false;
- }
-
var added = false;
- foreach (var mail in layout.Render(logEvent).Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
+ var mailAddresses = RenderLogEvent(layout, logEvent);
+
+ if (!string.IsNullOrEmpty(mailAddresses))
{
- mailAddressCollection.Add(MailboxAddress.Parse(mail));
- added = true;
+ foreach (string mail in mailAddresses.Split(';'))
+ {
+ var mailAddress = mail.Trim();
+ if (string.IsNullOrEmpty(mailAddress))
+ continue;
+
+ mailAddressCollection.Add(MailboxAddress.Parse(mail));
+ added = true;
+ }
}
return added;
diff --git a/src/NLog.MailKit/NLog.MailKit.csproj b/src/NLog.MailKit/NLog.MailKit.csproj
index afe9b1a..49b1a8b 100644
--- a/src/NLog.MailKit/NLog.MailKit.csproj
+++ b/src/NLog.MailKit/NLog.MailKit.csproj
@@ -29,9 +29,9 @@ If the mail target was already available on your platform, this package will ove
NLog.snk
false
-- NLog 5 compatible
-- All properties are now layoutable. See 'NLog Layout for everything' on https://nlog-project.org/2021/08/25/nlog-5-0-preview1-ready.html
-- Updated MailKit
+- Added support for email headers
+- Added target-alias mailkit
+- Updated to NLog v5.1.3
See https://github.com/NLog/NLog.MailKit/releases
@@ -50,7 +50,7 @@ See https://github.com/NLog/NLog.MailKit/releases
-
+
diff --git a/src/NLog.MailKit/Util/ExceptionHelper.cs b/src/NLog.MailKit/Util/ExceptionHelper.cs
deleted file mode 100644
index 4980862..0000000
--- a/src/NLog.MailKit/Util/ExceptionHelper.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-//
-// Copyright (c) 2004-2022 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 NLog.Common;
-
-namespace NLog.MailKit.Util
-{
- ///
- /// Helper class for dealing with exceptions.
- ///
- internal static class ExceptionHelper
- {
- private const string LoggedKey = "NLog.ExceptionLoggedToInternalLogger";
-
- ///
- /// Is this exception logged to the ?
- ///
- ///
- /// trueif the has been logged to the .
- public static bool IsLoggedToInternalLogger(this Exception exception)
- {
- if (exception != null)
- {
- return exception.Data[LoggedKey] as bool? ?? false;
- }
- return false;
- }
-
-
- ///
- /// Determines whether the exception must be rethrown and logs the error to the if is false.
- ///
- /// Advised to log first the error to the before calling this method.
- ///
- /// The exception to check.
- /// trueif the must be rethrown, false otherwise.
- public static bool MustBeRethrown(this Exception exception)
- {
- if (exception.MustBeRethrownImmediately())
- {
- //no further logging, because it can make severe exceptions only worse.
- return true;
- }
-
- var isConfigError = exception is NLogConfigurationException;
-
- //we throw always configuration exceptions (historical)
- if (!exception.IsLoggedToInternalLogger())
- {
- var level = isConfigError ? LogLevel.Warn : LogLevel.Error;
- InternalLogger.Log(exception, level, "Error has been raised.");
- }
-
- //if ThrowConfigExceptions == null, use ThrowExceptions
- var shallRethrow = isConfigError ? (LogManager.ThrowConfigExceptions ?? LogManager.ThrowExceptions) : LogManager.ThrowExceptions;
- return shallRethrow;
- }
-
- ///
- /// Determines whether the exception must be rethrown immediately, without logging the error to the .
- ///
- /// Only used this method in special cases.
- ///
- /// The exception to check.
- /// trueif the must be rethrown, false otherwise.
- public static bool MustBeRethrownImmediately(this Exception exception)
- {
- if (exception is OutOfMemoryException)
- {
- return true;
- }
-
- return false;
- }
- }
-}
diff --git a/src/NLog.MailKit/Util/SortHelpers.cs b/src/NLog.MailKit/Util/SortHelpers.cs
deleted file mode 100644
index 2d3bbd1..0000000
--- a/src/NLog.MailKit/Util/SortHelpers.cs
+++ /dev/null
@@ -1,366 +0,0 @@
-//
-// Copyright (c) 2004-2022 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.Collections.Generic;
-using NLog.Common;
-
-namespace NLog.MailKit.Util
-{
- ///
- /// 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 ReadOnlySingleBucketDictionary> BucketSort(this IList inputs, KeySelector keySelector)
- {
- Dictionary> buckets = null;
- bool singleBucketFirstKey = false;
- TKey singleBucketKey = default;
- EqualityComparer c = EqualityComparer.Default;
- for (int i = 0; i < inputs.Count; i++)
- {
- TKey keyValue = keySelector(inputs[i]);
- if (!singleBucketFirstKey)
- {
- singleBucketFirstKey = true;
- singleBucketKey = keyValue;
- }
- else if (buckets == null)
- {
- if (!c.Equals(singleBucketKey, keyValue))
- {
- // Multiple buckets needed, allocate full dictionary
- buckets = new Dictionary>();
- var bucket = new List(i);
- for (int j = 0; j < i; j++)
- {
- bucket.Add(inputs[j]);
- }
- buckets[singleBucketKey] = bucket;
- bucket = new List { inputs[i] };
- buckets[keyValue] = bucket;
- }
- }
- else
- {
- if (!buckets.TryGetValue(keyValue, out var eventsInBucket))
- {
- eventsInBucket = new List();
- buckets.Add(keyValue, eventsInBucket);
- }
- eventsInBucket.Add(inputs[i]);
- }
- }
-
- if (buckets != null)
- {
- return new ReadOnlySingleBucketDictionary>(buckets);
- }
- else
- {
- return new ReadOnlySingleBucketDictionary>(new KeyValuePair>(singleBucketKey, inputs));
- }
- }
-
- ///
- /// 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 struct ReadOnlySingleBucketDictionary : IDictionary
- {
- readonly KeyValuePair? _singleBucket;
- readonly Dictionary _multiBucket;
- readonly IEqualityComparer _comparer;
-
- public ReadOnlySingleBucketDictionary(KeyValuePair singleBucket)
- : this(singleBucket, EqualityComparer.Default)
- {
- }
-
- public ReadOnlySingleBucketDictionary(Dictionary multiBucket)
- : this(multiBucket, EqualityComparer.Default)
- {
- }
-
- public ReadOnlySingleBucketDictionary(KeyValuePair singleBucket, IEqualityComparer comparer)
- {
- _comparer = comparer;
- _multiBucket = null;
- _singleBucket = singleBucket;
- }
-
- public ReadOnlySingleBucketDictionary(Dictionary multiBucket, IEqualityComparer comparer)
- {
- _comparer = comparer;
- _multiBucket = multiBucket;
- _singleBucket = default;
- }
-
- ///
- 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;
- if (_singleBucket.HasValue)
- return new[] { _singleBucket.Value.Key };
- return Array.Empty();
- }
- }
-
- ///
- public ICollection Values
- {
- get
- {
- if (_multiBucket != null)
- return _multiBucket.Values;
- if (_singleBucket.HasValue)
- return new[] { _singleBucket.Value.Value };
- return Array.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];
- if (_singleBucket.HasValue && _comparer.Equals(_singleBucket.Value.Key, key))
- return _singleBucket.Value.Value;
- throw new KeyNotFoundException();
- }
- set
- {
- throw new NotSupportedException("Readonly");
- }
- }
-
- ///
- /// Non-Allocating struct-enumerator
- ///
- public struct Enumerator : IEnumerator>
- {
- bool _singleBucketFirstRead;
- readonly KeyValuePair _singleBucket;
- readonly IEnumerator> _multiBuckets;
-
- internal Enumerator(Dictionary multiBucket)
- {
- _singleBucketFirstRead = false;
- _singleBucket = default;
- _multiBuckets = multiBucket.GetEnumerator();
- }
-
- internal Enumerator(KeyValuePair singleBucket)
- {
- _singleBucketFirstRead = false;
- _singleBucket = singleBucket;
- _multiBuckets = null;
- }
-
- public KeyValuePair Current
- {
- get
- {
- if (_multiBuckets != null)
- return new KeyValuePair(_multiBuckets.Current.Key, _multiBuckets.Current.Value);
- else
- return new KeyValuePair(_singleBucket.Key, _singleBucket.Value);
- }
- }
-
- object IEnumerator.Current => Current;
-
- public void Dispose()
- {
- _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);
- if (_singleBucket.HasValue)
- return new Enumerator(_singleBucket.Value);
- return new Enumerator(new Dictionary());
- }
-
- ///
- IEnumerator> IEnumerable>.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- ///
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- ///
- public bool ContainsKey(TKey key)
- {
- if (_multiBucket != null)
- return _multiBucket.ContainsKey(key);
- if (_singleBucket.HasValue)
- return _comparer.Equals(_singleBucket.Value.Key, key);
- 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);
- }
- if (_singleBucket.HasValue && _comparer.Equals(_singleBucket.Value.Key, key))
- {
- value = _singleBucket.Value.Value;
- return true;
- }
-
- value = default;
- 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);
- if (_singleBucket.HasValue)
- return _comparer.Equals(_singleBucket.Value.Key, item.Key) && EqualityComparer.Default.Equals(_singleBucket.Value.Value, item.Value);
- 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
- }
- }
- }
-}