How to send e-mails using NLog? - c#

I'm using NLog, and I'm pretty new at logging. I created the log files but somehow I have a problem whit sending e-mails. I followed all instructions but couldn't make it.
Mail settings in configuration tags in web.config:
<system.net>
<mailSettings>
<smtp deliveryMethod="SpecifiedPickupDirectory" from="some#some.org">
<network host="localhost"/>
<specifiedPickupDirectory pickupDirectoryLocation="d:\tmp\email"/>
</smtp>
</mailSettings>
</system.net>
And this is the target inside the nlog.config:
<target name="Mail" xsi:type="Mail" html="true" subject="Error Received"
body="${message}"
to="some1#some.com"
from="some#gmail.com"
encoding="UTF-8"
smtpUserName="some#some.com"
enableSsl="false"
smtpPassword="password"
smtpAuthentication="Basic"
smtpServer="smtp.some.com"
smtpPort="25" />
The rule I used:
<logger name="*" minlevel="Error" writeTo="Mail" />
And I called the logger like this:
Logger logger = LogManager.GetCurrentClassLogger();
try{ //something }
catch(Exception ex){ logger.Error(ex); }
And also I'm pretty confused about the places of the settings and configurations. Thank you.

The body is a layout format, not sure if it will process the ${message} tag. Change ${message] to a layout name that is in nlog.config or leave it off for the ${message}${newline} default.
You can turn on the Internal Debugging
Change the top XML parent to
<nlog internalLogFile="c:\log.txt" internalLogLevel="Trace">
This might give you an idea on the issue. Could be an authentication issue with the server or another issue.
Since you are specifying all the server information in the target, you don't need the web.config settings.
Not sure if it is fixed yet, but you should add a timeout="10000" to the target so it closes the connection.
There is a pretty good example in the NLog Wiki for GMail that works

Related

AWS CloudWatch logging working from one machine, not another

I have 2 EC2 Windows 10 instances. Using C# and AWSSDK.CloudWatchLogs, I'm able to write to AWS CloudWatch from one of them, but not the other. I can duplicate this with the code below:
C# code:
using System;
namespace AllPurposeDuck
{
class Program
{
private static Logger _log = LogManager.GetCurrentClassLogger();
static void Main(string[] args)
{
try
{
_log.Info("Quack!");
_log.Debug("Quack!!");
_log.Trace("Quack!!!");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
app.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
</configSections>
<nlog throwExceptions="true"
internalLogFile="c:\temp\allpurposeducklog.txt"
internalLogLevel="Info"
xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="console" xsi:type="Console" layout="${longdate} | ${level} | ${machinename}| ${message}" />
<target name="aws" xsi:type="AWSTarget" logGroup="Ducks_and_stuff" region="us-west-2" layout="${machinename} ${longdate} | ${level} | ${message}" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="console, aws" />
</rules>
</nlog>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
On the machine that otherwise silently fails, I notice a short pause and see the following warning in my c:\temp\allpurposeducklog.txt local log file:
2021-03-20 01:36:40.7679 Info Shutting down logging...
2021-03-20 01:36:42.2809 Warn Target flush timeout. One or more targets did not complete flush operation, skipping target close.
2021-03-20 01:36:42.2809 Info Logger has been shut down.
The other machine quacks happily to CloudWatch (both quack to the console).
The failing machine is in the same VPC, with the same IAM role, availability zone, and security groups as the working machine. Both machines are working properly with CloudWatch Agent, which monitoring Windows memory and triggering alerts when it gets too low.
I'm having a hard time narrowing it down any further than that. Ideas? Is there somewhere in AWS where the reason for the failure might be getting recorded? Thanks.
Because the program exits soon after the logging, NLog could still be busy with writing the logs. If the target use asynchronous logging, NLog needs to know it should wait on writing.
It's always recommend to give some time just before the program exit. Add at the end of Main:
LogManager.Shutdown();
This will give NLog 15 seconds at max.
If that isn't enough time, you could flush first and wait for example max 30 sec.
LogManager.Flush(TimeSpan.FromSeconds(30));
LogManager.Shutdown();
See also https://github.com/NLog/NLog/wiki/Logging-troubleshooting

Protecting your Connection String on Shared Hosting

I am creating a website using MVC5 & EF6. I am also using a shared hosting to publish this website. Now the problem that I have is that my connection string at the moment is sitting in plain text in the web.config file. I am having a very hard time finding a "direct" answer on how I should deal with this.
I have come upon many articles such as this one. The article shows me how to encrypt the Connection Section of my web.config. So I tried following its example and encrypted the mail section it shows in that example. After I ran my code I noticed that my entire web.config file changed.
It use to be like this:
<system.net>
<mailSettings>
<smtp from="info#Site.com">
<network
host="mail.Site.com"
port="25"
userName="info#site.com"
password="password" />
</smtp>
</mailSettings>
</system.net>
and now it is like this:
<mailSettings>
<smtp configProtectionProvider="RsaProtectedConfigurationProvider">
<EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<KeyName>Rsa Key</KeyName>
</KeyInfo>
<CipherData>
<CipherValue>odapFFPDF1Fgsk2wyvbwVC4SNISqhWc9lXiAq+I/OW3wVVqBCPowxyen9M7c9+KUBkXmGSfaUVxDMlqutChv6g6VU8h4TWG3W6Tw/istjfw/UYrRsGguPiOqdvRsl9XLBmnS37v99+VX7FEA9TKb6ufC0a3Defp2MNpGTvTIR20=</CipherValue>
</CipherData>
</EncryptedKey>
</KeyInfo>
<CipherData>
<CipherValue>lHPPFRJAH2hIm/Ya+ABRMP5mo5rEYwL2aBJQ/DT4Q+1OZXaftutiddxxJZ4LSgw3pzi1QJpU8eOPwFVebvqFVA4cjs27l8Iqz50E/R/tBfS7e2oqdWTRsc8IFfE/xOIieMp22BuFsYEDbgnIbLdbHJnw+92zyt2lUlzJpW9epNpnb29sVQhtNJ9cPjAaYAaU</CipherValue>
</CipherData>
</EncryptedData>
</smtp>
</mailSettings>
My only problem right now is how do I read those values inside my code without having to decrypt and save the config file. I do not want to rewrite the webconfig file ever time I need to read the mail setting section or even the connection string section.
If I have a method like this:
public static string DecryptMailSettings()
{
var config = WebConfigurationManager.OpenWebConfiguration("~");
ConfigurationSection section = config.GetSection("system.net/mailSettings/smtp");
if (section.SectionInformation.IsProtected)
{
section.SectionInformation.UnprotectSection();
return section.???;
}
return "Nothing was read";
}
How do I get the value of lets say "host" from the example above.
From the documentation:
ASP.NET automatically decrypts the contents of the Web.config file when it processes the file. Therefore, no additional steps are required to decrypt the encrypted configuration settings for use by other ASP.NET features or to access the values in your code.
https://msdn.microsoft.com/en-us/library/dtkwfdky.aspx
I am suggesting that if the encryption/decryption is working fine, and it seems like that 'untangling' the Host Name is troublesome then just add a value to your web.config .
Like This:
<appSettings>
<add key="MAILHOST" value="mail.Site.com" /> ,
and then read that in your code.
Ex:
string HostName = ConfigurationManager.AppSettings["MAILHOST"].ToString();

Configure NLog programmatically, web.config

I have following Nlog config section in my web.config (modified to show only relevant info)
<nlog>
<targets async="true">
<target name="mail" type="Mail"
body="${date:format=yyyy-MM-dd HH\:mm\:ss} - ${level} [${logger}] - ${message} ${newline}${newline}${event-context:item=OtherInfo}${newline}${newline}${exception:maxInnerExceptionLevel=2:format=ToString}${newline}${newline}"
subject="[${machinename}] ${logger}"
to="mail#domain.com" encoding="UTF-8" from="anotheremail#domain.com" smtpServer="" enableSsl="true" smtpAuthentication="Basic"
/>
</targets>
<targets>
<target name="mailsync" type="Mail" body="${date:format=yyyy-MM-dd HH\:mm\:ss} - ${level} [${logger}] - ${message} ${newline}${newline}${event-context:item=OtherInfo}${newline}${newline}${exception:maxInnerExceptionLevel=2:format=ToString}${newline}${newline}" subject="[${machinename}] ${logger}"
to="mail#domain.com" encoding="UTF-8" from="anotheremail#domain.com" smtpServer="" enableSsl="true" smtpAuthentication="Basic"
/>
</targets>
<rules>
<logger name="*" level="Error" writeTo="mail" />
</rules>
</nlog>
I am updating the configuration via code in Application_Start,
var config = LogManager.Configuration;
const string targetName = "mail";
var wrapper = (AsyncTargetWrapper) config.FindTargetByName(targetName);
wrapper.WrappedTarget.SmtpServer = "hostname";
wrapper.WrappedTarget.SmtpUserName = "username";
wrapper.WrappedTarget.SmtpPassword = "password";
config.RemoveTarget(targetName);
config.AddTarget(targetName, wrapper);
LogManager.Configuration = config;
However, when I log any errors, the emails are not being sent. I have an additional file target (not shown in the code snippet), that contains the error message. That tells me the errors are getting logged, but somehow not sent via email.
If instead of updating the config via code, if I hard code the values in the web.config, then the emails get sent. I have verified that the smtp values that I am using via code are valid.
I did search through many similar questions on SO, but I haven't yet found one that mentions a solution that works for me.
EDIT:
Based on Xharze's answer, I enabled exceptions, internal logging and I also outputted the values of the target after I had made my changes. The internal log showed an exception about a MailAdrress being the incorrect format. So I checked all target values that accept an email address and I found the issue. The from property of the target accepts an email address whereas I was providing it a display name!
Could you try enabling throw exceptions and the interalnal log, and post it? It may provide more information. See github.com/NLog/NLog/wiki/Logging-troubleshooting.

Nlog, Not sending mails to Outlook

Mail Configuration is Nlog.config file
<target name="mail" xsi:type="Mail" smtpServer="smtp.emailsrvr.com" smtpPort="25" smtpUserName="samplemail#samplemail.com" smtpPassword="Password"
from="sample-test#intsof.com" to="vinay#sample.com" subject="Hello Mail from Nlog" html="false" encoding="UTF8"/>
<rules>
<logger name="*" level="Error" writeTo="mail"/>
Configuration in App.config.
<configuration>
<system.net>
<mailSettings>
<smtp from="Sample-test#sample.com" deliveryMethod="Network">
<network defaultCredentials="true" userName="vinay-test#sample.com" password="Password" host="smtp.emailsrvr.com" port="25"/>
</smtp>
</mailSettings>
</system.net>
</configuration>
If i use the delivery method SpecifiedPickupDirectory and give a local path then its sending mail, but not to outlook.
For this question, the accepted answer recommended changing encoding="UTF8" to encoding="UTF-8". Try that and see if it helps.
This can be a solution as well:
Config:
from="${event-context:item=Sender}"
Logging code:
eventInfo.Properties.Add("Sender", UserPrincipal.Current.EmailAddress);

How can I save an email instead of sending when using SmtpClient?

I am using SmtpClient to send an email with an attachment.
However for a certain batch we need to somehow save the MailMessage instead of sending them.
We are then thinking/hoping to manually upload the messages to the users drafts folder.
Is it possible to save these messages with the attachment intact (impossible, I would have thought). Or alternatively upload the messages to a folder in the users account?
If anyone has any experience of this, I'd much appreciate a bit of help or a pointer.
When testing in ASP.NET we save our emails to a folder rather then send them through an email server. Maybe you could change yourweb.config settings like this for your batch?
<system.net>
<mailSettings>
<smtp deliveryMethod="SpecifiedPickupDirectory">
<specifiedPickupDirectory pickupDirectoryLocation="c:\Temp\mail\"/>
</smtp>
</mailSettings>
</system.net>
Additional Info:
MSDN: <specifiedPickupDirectory> Element (Network Settings)
Configuring SmtpClient to drop emails in a folder on disk
As well as the SpecifiedPickupDirectory information of the other answers, if you want to ensure your emails are sent to a folder relative to the site root - handy in testing on build servers where you don't know the paths - you can add a quick check in your email sending code:
SmtpClient client = new SmtpClient();
...
// Add "~" support for pickupdirectories.
if (client.DeliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory && client.PickupDirectoryLocation.StartsWith("~"))
{
string root = AppDomain.CurrentDomain.BaseDirectory;
string pickupRoot = client.PickupDirectoryLocation.Replace("~/", root);
pickupRoot = pickupRoot.Replace("/",#"\");
client.PickupDirectoryLocation = pickupRoot;
}
And your tests will look something like this (make sure you use App_Data so IIS can write to the folder):
// Arrange - get SitePath from AppDomain.Current.BaseDirectory + ..\
string pickupPath = Path.Combine(SitePath, "App_Data", "TempSmtp");
if (!Directory.Exists(pickupPath))
Directory.CreateDirectory(pickupPath);
foreach (string file in Directory.GetFiles(pickupPath, "*.eml"))
{
File.Delete(file);
}
// Act (send some emails)
// Assert
Assert.That(Directory.GetFiles(pickupPath, "*.eml").Count(), Is.EqualTo(1));
This can help - Adding Save() functionality to Microsoft.Net.Mail.MailMessage
The main ideia, make an extension to MailMessage ,that by reflection making a save method.
You can configure this with the system.net setting in your web.config / app.config file.
<system.net>
<mailSettings>
<smtp deliveryMethod="Network">
<network host="mail.mydomain.com" port="25" />
</smtp>
<!-- Use this setting for development
<smtp deliveryMethod="SpecifiedPickupDirectory">
<specifiedPickupDirectory pickupDirectoryLocation="C:\Temp" />
</smtp>
-->
</mailSettings>
</system.net>
Also, here's a link with info on migrating from System.Web.Mail to System.Net.Mail.
A bug also requires adding as a workaround in some versions of the framework. So the completed version looks like:
<system.net>
<mailSettings>
<smtp deliveryMethod="SpecifiedPickupDirectory">
<specifiedPickupDirectory pickupDirectoryLocation="c:\Temp\mail\"/>
<network host="localhost" />
</smtp>
</mailSettings>
</system.net>

Categories

Resources