Environment variables not being used when debugging through a Service Fabric project - c#

When creating an ASP.NET Core app an environment variable called ASPNETCORE_ENVIRONMENT=Development will be set for you and when debugging you will see that the IHostingEnvironment is set to Development.
The problem is that when I use the same project in a solution set up for Service Fabric the environment variables don't seem to get injected and IHostingEnvironment just returns "Production".
How can I resolve this?
Note: I've set a breakpoint in the startup class to observe the IHostingEnvironment variable.

Reference for this answer: https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-manage-multiple-environment-app-configuration
I ran into the same issue with the default template. The following is similar to Duncan's answer but with two important differences: 1) You will not have to change any template code within the service, and 2) the IHostingEnvironment will be properly set.
First, add the ASPNETCORE_ENVIRONMENT variable to the <CodePackage> element of the PackageRoot\ServiceManifest.xml file of the application service:
<CodePackage Name="Code" Version="1.0.0">
<EntryPoint>
<ExeHost>
<Program>MyService.exe</Program>
<WorkingFolder>CodePackage</WorkingFolder>
</ExeHost>
</EntryPoint>
<EnvironmentVariables>
<EnvironmentVariable Name="ASPNETCORE_ENVIRONMENT" Value=""/>
</EnvironmentVariables>
</CodePackage>
As in Duncan's response, there are two changes you'll make to the ApplicationManifest.xml of your Service Fabric Application project. First, setup a parameter (variable) so that it can be modified when the ApplicationParameters files are substituted based on the way you deploy the project. Then, add an EnvironmentalOverrides section to your ServiceManifestImport element. The results of the two additions will look something like this:
<Parameters>
<Parameter Name="MyService_InstanceCount" DefaultValue="-1" />
<Parameter Name="AspNetCoreEnvironment" DefaultValue="" />
</Parameters>
<ServiceManifestImport>
<ServiceManifestRef ServiceManifestName="MyServicePkg" ServiceManifestVersion="1.0.0" />
<EnvironmentOverrides CodePackageRef="Code">
<EnvironmentVariable Name="ASPNETCORE_ENVIRONMENT" Value="[AspNetCoreEnvironment]" />
</EnvironmentOverrides>
</ServiceManifestImport>
Finally, you can add in the proper values in the individual ApplicationParameters files:
<Parameters>
<Parameter Name="MyService_InstanceCount" Value="-1" />
<Parameter Name="AspNetCoreEnvironment" Value="Development" />
</Parameters>
At this point, you can remove the variable from your service's Properties - Debug environmental variables.

I ran into the same issue and was able to create a solution that worked for me.
If you look at your ASP.NET Core project, you should see a Program.cs file. At the bottom of it you should see the following interface implementation:
Task<string> ICommunicationListener.OpenAsync(CancellationToken cancellationToken)
{
...
}
You're going to first want to change it to something like the following:
Task<string> ICommunicationListener.OpenAsync(CancellationToken cancellationToken)
{
var context = FabricRuntime.GetActivationContext();
var endpoint = context.GetEndpoint(_endpointName);
var config = context.GetConfigurationPackageObject("Config");
var environment = config.Settings.Sections["Environment"].Parameters["ASPNETCORE_ENVIRONMENT"].Value;
var serverUrl = $"{endpoint.Protocol}://{FabricRuntime.GetNodeContext().IPAddressOrFQDN}:{endpoint.Port}";
_webHost = new WebHostBuilder().UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseEnvironment(environment)
.UseUrls(serverUrl)
.Build();
_webHost.Start();
return Task.FromResult(serverUrl);
}
The key portion is the .UseEnvironment(environment) call, along with the supporting retrieval of the environment from the configuration. This will give ASP.NET Core the necessary information it needs to choose the environment.
Having done this, you'll obviously need to add the ASPNETCORE_ENVIRONMENT setting to the config section. That looks like the following:
Under your ASP.NET Core project you'll find a directory called PackageRoot/Config. Inside of that there should be a Settings.xml file. Add the following code inside the <Settings> tag...
<Section Name="Environment">
<Parameter Name="ASPNETCORE_ENVIRONMENT" Value="" />
</Section>
Next, you're going to want to look at the ApplicationPackageRoot/ApplicationManifest.xml file inside the actual Service Fabric Project (this is NOT the ASP.NET Core project). Two file changes are required.
Add the ASPNETCORE_ENVIRONMENT parameter inside the <Parameters> tag at the top of the file like so:
<Parameter Name="ASPNETCORE_ENVIRONMENT" DefaultValue="" />
Modify your <ServiceManifestImport> tag to include a <ConfigOverrides> section like so:
<ConfigOverrides>
<ConfigOverride Name="Config">
<Settings>
<Section Name="Environment">
<Parameter Name="ASPNETCORE_ENVIRONMENT" Value="[ASPNETCORE_ENVIRONMENT]" />
</Section>
</Settings>
</ConfigOverride>
</ConfigOverrides>
Finally, modify your ApplicationParameters/Local.1Node.xml and friends to contain the ASPNETCORE_ENVIRONMENT parameter:
<Parameter Name="ASPNETCORE_ENVIRONMENT" Value="Development" />
It's a lot of steps to add a freaking variable you can retrieve, but it does allow you a great deal of flexibility and follows the standard Service Fabric pattern to make deployments simple. I hope this helps!

The answer from Duncan worked for me, but there is a small variation for me, maybe due to the version of ASP.NET Core and Service Fabric I use.
I need to override the method CreateServiceInstanceListeners in my Web Stateless Service.
So that means I'll have this code:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[]
{
new ServiceInstanceListener(serviceContext =>
new WebListenerCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
{
ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting WebListener on {url}");
var environment = FabricRuntime.GetActivationContext()
?.GetConfigurationPackageObject("Config")
?.Settings.Sections["Environment"]
?.Parameters["ASPNETCORE_ENVIRONMENT"]?.Value;
return new WebHostBuilder().UseWebListener()
.ConfigureServices(
services => services
.AddSingleton<StatelessServiceContext>(serviceContext))
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseEnvironment(environment)
.UseApplicationInsights()
.UseUrls(url)
.Build();
}))
};
}
Of course, you need to set the ASPNETCORE_ENVIRONMENT variable as explained by Duncan.

Related

NLog MySql .Net Core 3.0 logging not saving to database

I have setup NLog on my project that will save the logging to my audit database (separate from the default database). However, it is failing to write to the database. I have a console logger target as well and that is logging as expected. No errors are shown or given. It merely fails to write to the database.
I have tried various methods provided from google, but none seemed to have worked. I have also tried using NLogBuilder to configure the config for that specific controller but still, it doesn't write to the database
nlog.config file:
<configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
</configSections>
<nlog internalLogLevel="Trace">
<targets>
<target name="ConsoleLogger" type="Console"
layout="${longdate}|${level:uppercase=true}|${logger}|${message}"/>
<target name="DatabaseLog" type="Database">
<commandtext>
INSERT INTO Logs
(LogDate, LogLevel, Message, Exception)
VALUES
(#log_date, #log_level, #message, #exception)
</commandtext>
<parameter name="#log_date"
layout="${log_date}"
dbType="DateTime"/>
<parameter name="#thread"
layout="${thread}"
dbType="String"
size="255"/>
<parameter name="#log_level"
layout="${log_level}"
dbType="String"
size="20" />
<parameter name="#logger"
layout="${logger}"
dbType="String"
size="250" />
<parameter name="#message"
layout="${message}"
dbType="String"
size="4000" />
<parameter name="#exception"
layout="${exception}"
dbType="String"
size="4000" />
<dbProvider>MySql.Data.MySqlClient.MySqlConnection, MySql.Data</dbProvider>
<connectionString>User Id=username;Password=password;Host=localhost;Database=audit_database;TreatTinyAsBoolean=false</connectionString>
</target>
</targets>
<rules>
<logger name="*" minlevel="Debug" maxlevel="Fatal" writeTo="ConsoleLogger,DatabaseLog" />
</rules>
</nlog>
Controller method:
public static Logger _logger = LogManager.GetCurrentClassLogger(typeof(ActionerController));
_logger.Info("text");
Main.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddNLog();
}).ConfigureAppConfiguration((hostingContext, config) =>
{
config
.AddJsonFile($"environment-mount/appsettings.json", optional: true)
.AddEnvironmentVariables();
}).ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
For MySQL, make sure you have installed the MySql.Data package.
There are also some mistakes in your config and code:
${log_date}, ${log_level} and ${thread} don't exist. I guess you mean ${date}, ${level} and ${threadid}. See all options here.
LogManager.GetCurrentClassLogger with an argument expects a logger type - so inherits from NLog.Logger. I think you are looking for LogManager.GetCurrentClassLogger() or LogManager.GetLogger(typeof(ActionerController).FullName). See API docs
Of course there could be other errors, e.g. a wrong query, mistyped colum names or wrong column types. NLog could of course tell you the problem:
Or enable exceptions. In your config <nlog throwExceptions=true >
Or enable the internal log: <nlog internalLogFile="c:\log.txt" internalLogLevel="Warn">. Read more here

How to set a parameter value based on the app settings key value inside a web.config

I have the following code inside a web.config and I'd like some way of setting the Parameter "NoHeader" to the value of the app settings key "ShowHeader" such that the former would be set to true. The best guess I have is that I use code to read the value from the ConfigurationManager and somehow write it back to NoHeader but I don't know how to do that if that's the case.
<Telerik.Reporting>
<Extensions>
<Render>
<Extension name="CSV">
<Parameters>
<Parameter name="NoHeader" value="false" />
</Parameters>
</Extension>
</Render>
</Extensions>
</Telerik.Reporting>
<appSettings>
<add key="ShowHeader" value="true" />
</appSettings>
We use a method that changes the showHeader key on deployment, but I'm not able to modify the NoHeader portion directly. I'm using C# for my code, any clues would be appreciated.

Weird NLog behavior across threads with MDLC and NDLC

I'm trying to setup a CorrelationID across threads to establish a link between calls to my server, and the corresponding calls that I make to external web services. The correlation ID is a GUID which I'm saving in Logical Context structures of NLog (logical contexts work fine across threads, supposedly).
The idea is to have a GUID that is shared between any request to my server, and the corresponding requests that I issue to various web services due to this request. I tried using both MDLC and NDLC.
The problem is that the value is getting stored correctly only for the first request, and it's saving blank values for all subsequent ones, even though a GUID is correctly generated for each new request to my server.
I tried logging either to a database or in a file. The problem seems to solve itself if I add a breakpoint within the code, or if I add a System.Threading.Sleep anywhere around the logging method. What's also weird is that I can add the Sleep either before or after the method that sets the value in the logical context, and it still works either way. Removing the Sleep/breakpoint would cause it to break again.
I am using NLog v4.5.2.
Logging module:
using System;
using System.Web;
using NLog;
namespace Shift.Stardust.Engine.Modules
{
public class LoggingHttpModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += HandleBeginRequest;
}
public void Dispose()
{
}
private void HandleBeginRequest(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(500);
var guid = Guid.NewGuid().ToString();
NestedDiagnosticsLogicalContext.Push(guid);
}
}
}
Placing a breakpoint anywhere in HandleBeginRequest produces correct output. Similarly for adding System.Threading.Thread.Sleep(500). Naturally, I wouldn't want to add such a line in my code just to resolve this issue.
NLog config:
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" internalLogFile="c:\temp\nlog-internal.txt" internalLogLevel="Trace">
<variable name="logDirectory" value="${basedir}/logs"/>
<targets>
<target name="asyncdatabase"
xsi:type="AsyncWrapper"
queueLimit="5000"
overflowAction="Block">
<target xsi:type="Database"
connectionStringName="ConnectionStringHere"
keepConnection="true">
<commandText>[db].[P_Log_Insert] #CreateDate, #ApplicationName, #MachineName, #LoggerName, #LogLevel, #Message, #Exception, NULL, #EngineSessionId, #CorrelationId</commandText>
<parameter name="#CreateDate" layout="${date}"/>
<parameter name="#ApplicationName" layout="${appsetting:name=Shift.Stardust.ApplicationName}"/>
<parameter name="#MachineName" layout="${machinename}"/>
<parameter name="#LoggerName" layout="${logger}"/>
<parameter name="#LogLevel" layout="${level}"/>
<parameter name="#Message" layout="${message}"/>
<parameter name="#Exception" layout="${exception:format=tostring}"/>
<parameter name="#EngineSessionId" layout="${aspnet-sessionid}"/>
<parameter name="#CorrelationId" layout="${ndlc}"/>
</target>
</target>
</targets>
<rules>
<logger name="Http.*" minlevel="Info" writeTo="asyncdatabase" final="true" />
</rules>
</nlog>
I expect to have a different CorrelationID for each incoming request, but this is only true for the first one. All of the subsequent ones have an empty string as value.
I think it's better to write to the HTTP context for this case.
e.g.
HttpContext.Current.Items["myvariable"] = 123;
and usage:
${aspnet-item:variable=myvariable} - produces "123"
See docs
You need the package NLog.Web (ASP.NET non-core) for that.
Note: ASP.NET Core uses should use NLog.Web.AspNetCore instead of NLog.Web

NLOG: how to use variable to specify target

Is it possible to use a variable to specify a specific target?
I want to switch between database and filelogging depending on the environment i'm running my application on.
This however does not seem to work:
<logger name="MyLogger" minlevel="Warn" writeTo="${var:myTargetName}" />
In my application startup I have (error occurs on first line):
var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
var config = LoadConfiguration();
NLog.LogManager.Configuration.Variables["myTargetName"] = config.GetSection("LogTargets:TargetName").Value;
NLog.LogManager.KeepVariablesOnReload = true;
NLog.LogManager.Configuration.Reload();
When startin the application the following exception is thrown:
"Target ${var:myTargetName} not found."
I guess the variables are not available when parsing the config file.
How can I set the variables so NLOG uses them while parsing the config-file?
Is it possible to use a variable to set an attribute value?
Or is this not supported by NLOG?
Note: I do have another variable in the NLOG config which does work
<target xsi:type="Database" name="databaseTarget">
<connectionString>${var:connectionString}</connectionString>
...........
</target>
I guess the loggers are checked once on startup and the actual target is evaluated when a logevent occurs.
Update: got it working without variable in config file.
I had to remove the logger from the config and create it through code
var myCustomTarget = NLog.LogManager.Configuration.FindTargetByName(config.GetSection("LogTargets:TargetName").Value);
NLog.LogManager.Configuration.AddRuleForAllLevels(myCustomTarget , "MyLogger", true);
One way to do it is to have a different Web.config file based on the environment and there you will change the connection string. That is the method I am using.
Also NLOG is initialized once the application starts thus you cannot change what's written in the NLOG.config
Setting variables via NLog.LogManager.Configuration.Variables does not work for all attributes in nlog.config file. I don't know why, but that's a known problem and that is unfortunate how it works. But there is a simple workaround, here is an example how I solved this problem for the attribute connectionString of a target.
<target xsi:type="Database" name="tDatabase"
dbProvider="Microsoft.Data.Sqlite.SqliteConnection, Microsoft.Data.Sqlite"
connectionString="Data Source =.\${environment:DATABASE_FILE_NAME};"
commandText="INSERT INTO ...">
<parameter name="#MachineName" layout="${machinename}" />
...
</target>
You can than simple set the environment variable in your code like this
Environment.SetEnvironmentVariable("DATABASE_FILE_NAME", "foo.db");
So you just use ${environment:DATABASE_FILE_NAME} and
Environment.SetEnvironmentVariable("DATABASE_FILE_NAME", "foo.db");
instead of ${var:DATABASE_FILE_NAME} and
NLog.LogManager.Configuration.Variables["DATABASE_FILE_NAME"] = "foo.db";
NLog 4.6.7 makes it easier to update the logging-rules at runtime like this:
<nlog>
<variable name="fileMinLevel" value="Off" />
<variable name="databaseMinLevel" value="Off" />
<rules>
<logger minLevel="${var:fileMinLevel}" writeTo="fileTarget" />
<logger minLevel="${var:databaseMinLevel}" writeTo="databaseTarget" />
</rules>
</nlog>
Then you can do this:
if (IsDevelopment())
LogManager.Configuration.Variables["fileMinLevel"] = "Debug";
else
LogManager.Configuration.Variables["databaseMinLevel"] = "Debug";
LogManager.ReconfigExistingLoggers();
See also: https://github.com/NLog/NLog/wiki/Filtering-log-messages#semi-dynamic-routing-rules
See also: https://github.com/NLog/NLog/wiki/Environment-specific-NLog-Logging-Configuration

Using quartz.net on medium trust hosting

I need scheduling functionality on my .NET MVC website and I came across Quartz.net library which can do exactly what I need.
The problem is I'm running my site on a hosting (GoDaddy) and when I added Quartz.net 2.0.1 to my project I've got "that assembly does not allow partially trusted callers" exception. After some research I found out that many people have the same problem and some solved it by removing Common.Logging library from Quartz.net.
I followed some of the advice and removed all references to Common.Logging but I still have problems. It looks like it's not enough and now I'm getting Inheritance security rules violated while overriding member exception, more details:
Inheritance security rules violated while overriding member:
Quartz.Util.DirtyFlagMap`2<TKey,TValue>.GetObjectData
(System.Runtime.Serialization.SerializationInfo,
System.Runtime.Serialization.StreamingContext)'.
Security accessibility of the overriding method must match the
security accessibility of the method being overriden.
It looks like I really need to change something in Quartz.net to make it work.
Has anyone run Quartz.net on medium trust? If so what needs to be done? May be someone can suggest some alternatives?
Steinar's answer sent me in the right direction. Sharing the steps here that got QuartZNet to work in a medium trust hosting environment.
QuartzNet initially ran into permissions issues on medium trust, we needed to the do the following to fix the issue
(1) Downloaded QuartzNet code ( 2.1.0.400 ) from github and build it after making the following changes to AssemblyInfo.cs
Replaced
#if !NET_40
[assembly: System.Security.AllowPartiallyTrustedCallers]
#endif
with
[assembly: AllowPartiallyTrustedCallers]
#if NET_40
[assembly: SecurityRules(SecurityRuleSet.Level1)]
#endif
(2) Downloaded C5 code (v 2.1) and built it with
[assembly: AllowPartiallyTrustedCallersAttribute()
Ensure C5 is compiled in the same .NET version as Qartznet.
(3) Added the quartz section to web.config within TGH, section had requirepermission set to false. Common logging section also had requirepermission set to false, also configured it to use Common.Logging.Simple.NoOpLoggerFactoryAdapter.
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" requirePermission="false" />
</sectionGroup>
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<common>
<logging>
<factoryAdapter type="Common.Logging.Simple.NoOpLoggerFactoryAdapter, Common.Logging">
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="level" value="OFF" />
<arg key="dateTimeFormat" value="HH:mm:ss:fff" />
</factoryAdapter>
</logging>
</common>
<quartz>
<add key="quartz.scheduler.instanceName" value="QuartzScheduler" />
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.threadPool.threadCount" value="10" />
<add key="quartz.threadPool.threadPriority" value="2" />
<add key="quartz.jobStore.misfireThreshold" value="60000" />
<add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz" />
</quartz>
(4) Initialised the scheduler using constructor with namecollection as the parameter, namecollection was the quartz section picked up from web.config.
In global.asax
QuartzScheduler.Start();
The class
public class QuartzScheduler
{
public static void Start()
{
ISchedulerFactory schedulerFactory = new StdSchedulerFactory((NameValueCollection)ConfigurationManager.GetSection("quartz"));
IScheduler scheduler = schedulerFactory.GetScheduler();
scheduler.Start();
IJobDetail inviteRequestProcessor = new JobDetailImpl("ProcessInviteRequest", null, typeof(InviteRequestJob));
IDailyTimeIntervalTrigger trigger = new DailyTimeIntervalTriggerImpl("Invite Request Trigger", Quartz.TimeOfDay.HourMinuteAndSecondOfDay(0, 0, 0), Quartz.TimeOfDay.HourMinuteAndSecondOfDay(23, 23, 59), Quartz.IntervalUnit.Second, 1);
scheduler.ScheduleJob(inviteRequestProcessor, trigger);
}
}
public class InviteRequestJob : IJob
{
public void Execute(IJobExecutionContext context)
{
RequestInvite.ProcessInviteRequests();
}
}
I recommend building Common.Logging yourself rather than removing it from the project. You can get the latest source from http://netcommon.sourceforge.net/downloads.html.
I guess the second problem had to do with that the C5.dll wasn't trusted either. I would also just build that myself. The source can be found here: http://www.itu.dk/research/c5/.
Although there are other options than building the dlls (http://stackoverflow.com/questions/3072359/unblocking-a-dll-on-a-company-machine-how) I personally prefer to build the dlls myself unless I absolutely trust the downloaded product.

Categories

Resources