Azure web app delete logs directory on swap - asp.net

I have multiple ASP .NET MVC and Web Api apps hosted on Azure.
I use two differents stages for deployment : stage and PROD. Basically, I want to have two different folders for logs : logs-stage and logs-PROD. This is working well based on my web.config and properties set directly in Azure.
The issue is that each time I deploy, all the previous logs are deleted. How can I avoid that ?
My NLog config looks like the following :
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target xsi:type="File" name="f" fileName="${basedir}/logs-${appsetting:name=Version:default=DEV}/website.${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${logger} - ${message}" />
<target name="email-Errors" xsi:type="Mail"
smtpServer="smtp.sendgrid.net"
smtpPort="myPort"
enableSsl="false"
smtpUsername="myUsername"
smtpPassword="myPassword"
smtpAuthentication="Basic"
from="myEmail"
to="${appsetting:name=WEBSITE_EMAIL_DEVELOPERS:default=myEmail}"
subject="[${appsetting:name=Version:default=DEV}][WEB][${uppercase:${level}}]"
layout="${longdate} ${uppercase:${level}} ${logger} - ${message}"
html="false" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="f" />
<logger name="*" minlevel="Error" writeTo="email-Errors" />
</rules>
</nlog>

The issue is that each time I deploy, all the previous logs are deleted. How can I avoid that ?
According to your description, it seems that when you publish the WebApp then remove additional file in the Azure WebApp. If it is that case please have a try to uncheck the [Remove additional files as destination] option during publish the WebApp.
Updated:
From the blog, we could know that if we swap the slots, just the DNS pointer changes, so if you have the nlog in the product after swap then the Nlog file is in the staging slot that not deleted. The following the snippet from the blog.
In short, the Swap operation will exchange the website's content between 2 deployment slots.
Swapped and what is not but note that swap is not about copying the content of the website but more about swapping DNS pointers.

Related

NLog does not create log files on ASP.NET Core project

I am new to using NLog with ASP.NET Core, so I have followed the guide here:
https://github.com/NLog/NLog.Web/wiki/Getting-started-with-ASP.NET-Core-(project.json)
I have created the following nlog.config file at the root of the project directory:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Warn"
internalLogFile="c:\temp\internal-nlog.txt">
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<!-- define various log targets -->
<targets>
<!-- write logs to file -->
<target xsi:type="File" name="allfile" fileName="${basedir}\nlog-all-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}|${message} ${exception}" />
<target xsi:type="File" name="ownFile-web" fileName="${basedir}\nlog-own-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}| ${message} ${exception}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
<target xsi:type="Null" name="blackhole" />
</targets>
<rules>
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="allfile" />
<!--Skip Microsoft logs and so log only own logs-->
<logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" />
<logger name="*" minlevel="Trace" writeTo="ownFile-web" />
</rules>
</nlog>
Inside a controller, I call a line like this one:
_logger.LogInformation("Entered CustomerRange method");
which returns the following in the output window in Visual Studio:
CustomerMgmtAPI.Controllers.CustomerController:Information: Entered CustomerRange method
However, the actual log files are never created by NLog. I was wondering if someone can point out the error in the NLog configuration here, since I have been reviewing the documentation of NLog for ASP.NET Core project and I can't find the error myself.
So the actual fix to the problem was the remove the first line from the nlog.config file:
<?xml version="1.0" encoding="utf-8" ?>
I remove this line and everything started working as expected. I also noticed that Visual Studio was giving me these errors when that line was present:
Invalid token 'Text' at root level of document.
Unexpected XML declaration. The XML declaration must be the first node in the document and no white space characters are allowed to appear before it.
It seems in this case that the NLog tutorial is broken, as I just took over this file from the sample for ASP.NET Core. I am using VS2017, so perhaps there is an incompatibility with this version of VS?
The dirty little secret about using NLog with ASP.NET Core is that you can configure and create logs just as you did in ASP.NET and ASP.NET MVC. You just use the regular NLog Nuget package like you normally would.
Just create an NLog.config in your root, etc. You don't even have to make any extra configurations in the config or elsewhere to get it to work. You just reference NLog in your class and then create a logger with the LogManager.
What this means is that you don't have all of the wireup in Program.cs etc.

Quartz.net Job not firing in deployment mode but firing in debug mode

I have a Quartz.net job that sends emails every 5 minutes. It runs perfectly fine when I run the application in debug mode locally. But when deploy to the server, the job is not running. Can someone think of a reason why?
Our deployed sites are ASP.NET websites in IIS.
Wire up these two "built in" listeners....
<add key="quartz.plugin.jobHistory.type" value="Quartz.Plugin.History.LoggingJobHistoryPlugin, Quartz" />
<add key="quartz.plugin.triggHistory.type" value="Quartz.Plugin.History.LoggingTriggerHistoryPlugin, Quartz" />
You'll need to wire up the ILog stuff.
Then you'll be able to figure out what is and is not happening.
<configSections>
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>
</configSections>
<common>
<logging>
<factoryAdapter type="Common.Logging.NLog.NLogLoggerFactoryAdapter, Common.Logging.NLog20">
<arg key="configType" value="FILE" />
<arg key="configFile" value="~/NLog.config" />
</factoryAdapter>
</logging>
</common>
NLog.config
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" internalLogFile="Nlog.log">
<targets>
<target name="logfileTargetName" xsi:type="File" layout="${longdate}|${level}|${callsite}|${logger}|${threadid}|${windows-identity:domain=false}__${message} ${exception:format=message,stacktrace:separator=*"
fileName="MyNLogLogFile.txt" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="logfileTargetName"/>
</rules>
</nlog>
I don't exactly what caused the issue. I spend quite a bit of time on this but could not find why the job was not running. Eventually I looked at the log and found that the job that was run ahead of it was throwing some kind of exception, so I moved my job from last in the list to first in the list and it started working fine. There were a couple of weird things that happened as well and I was not completely satisfied how it got resolved. Other things that I think of was
We had a number of environments (dev, staging, staging), it is possible there was some kind of conflict caused by these multiple environments however I think it is unlikely
Anyways the job start running fine after I reorder the job. The interesting thing was, I fixed the ordering in a branch and it got fixed in dev and staging environments.

Set ACLs during Web Deployment via MSBuild

I have a mostly working web build-and-deploy configuration running in TeamCity, that basically uses MSBuild to automatically deploy the site to a web server. MSDeploy sets everything to Readonly on the target server by default, and I need the AppPool identity to have write access to just one folder.
I found an article by Kevin leetham that gets me 90% of the way there. Kevin describes how it is possible to hook into the MSBuild Web Publish Pipeline by creating a file called ProjectName.wpp.targets, along these lines:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!--Extends the AfterAddIisSettingAndFileContentsToSourceManifest action do also set ACLs -->
<IncludeCustomACLs>TRUE</IncludeCustomACLs>
<AfterAddIisSettingAndFileContentsToSourceManifest Condition="'$(AfterAddIisSettingAndFileContentsToSourceManifest)'==''">
$(AfterAddIisSettingAndFileContentsToSourceManifest);
SetCustomACLs;
</AfterAddIisSettingAndFileContentsToSourceManifest>
</PropertyGroup>
<Target Name="SetCustomACLs" Condition="'$(IncludeCustomACLs)'=='TRUE'">
<Message Text="Adding Custom ACls" />
<ItemGroup>
<!-- Ensure the AppPool identity has write access to the Files directory -->
<MsDeploySourceManifest Include="setAcl" Condition="$(IncludeSetAclProviderOnDestination)">
<Path>$(_MSDeployDirPath_FullPath)\files</Path>
<setAclAccess>Read,Write,Modify</setAclAccess>
<setAclResourceType>Directory</setAclResourceType>
<AdditionalProviderSettings>setAclResourceType;setAclAccess</AdditionalProviderSettings>
</MsDeploySourceManifest>
</ItemGroup>
</Target>
</Project>
This is so nearly working that it is driving me crazy. The ACL gets added to the manifest, but the problem is that it generates an absolute path based on the build location, rather than being relative to the IIS web app on the target server. the generated manifest comes out like this (some names have been changed to protect the innocent):
<?xml version="1.0" encoding="utf-8"?>
<sitemanifest>
<IisApp path="C:\SolutionPath\IisWebAppName\src\MyProjectName\obj\Release_Deploy\Package\PackageTmp" managedRuntimeVersion="v4.0" />
<setAcl path="C:\SolutionPath\IisWebAppName\src\MyProjectName\obj\Release_Deploy\Package\PackageTmp" setAclResourceType="Directory" />
<setAcl path="C:\SolutionPath\IisWebAppName\src\MyProjectName\obj\Release_Deploy\Package\PackageTmp" setAclUser="anonymousAuthenticationUser" setAclResourceType="Directory" />
<setAcl path="C:\SolutionPath\IisWebAppName\src\MyProjectName\obj\Release_Deploy\Package\PackageTmp\files" setAclResourceType="Directory" setAclAccess="Read,Write,Modify" />
</sitemanifest>
This actually looks correct, the last line is my custom ACL from teh wpp.targets file. However, when MSDeploy sends this to the target server, here's what happens:
2>Start Web Deploy Publish the Application/package to https://webhostingprovider.biz:8172/msdeploy.axd?site=IisWebAppName ...
2>Adding sitemanifest (sitemanifest).
2>Adding ACL's for path (IisWebAppName)
2>Adding ACL's for path (IisWebAppName)
2>Adding ACL's for path (C:\SolutionPath\IisWebAppname\src\MyProjectName\obj\Release_Deploy\Package\PackageTmp\files)
2>C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v11.0\Web\Microsoft.Web.Publishing.targets(4377,5): Error ERROR_USER_NOT_AUTHORIZED_FOR_SETACL: Web deployment task failed. (Could not complete an operation with the specified provider ("setAcl") when connecting using the Web Management Service. This can occur if the server administrator has not authorized the user for this operation. setAcl http://go.microsoft.com/fwlink/?LinkId=178034
The whole thing falls over on my custom ACL path, which comes out using an absolute path name instead of being relative to IisWebAppName. I cannot figure out why!!
Help please :)
You need to create a ProviderPath parameter with a DefaultValue that takes it's value of another parameter using the {param name} syntax.
Here's a helper I included on another question that performs all the actions:
<ItemDefinitionGroup>
<AdditionalAcls>
<AclAccess>Write</AclAccess>
<ResourceType>Directory</ResourceType>
</AdditionalAcls>
</ItemDefinitionGroup>
<PropertyGroup>
<AfterAddIisSettingAndFileContentsToSourceManifest>
$(AfterAddIisSettingAndFileContentsToSourceManifest);
AddAdditionalAclsToSourceManifest;
</AfterAddIisSettingAndFileContentsToSourceManifest>
<AfterAddIisAndContentDeclareParametersItems>
$(AfterAddIisAndContentDeclareParametersItems);
AddAdditionalAclsDeclareParameterItems
</AfterAddIisAndContentDeclareParametersItems>
</PropertyGroup>
<Target Name="AddAdditionalAclsToSourceManifest">
<ItemGroup Condition="'#(AdditionalAcls)' != ''">
<MsDeploySourceManifest Include="setAcl">
<Path>$(_MSDeployDirPath_FullPath)\%(AdditionalAcls.Identity)</Path>
<setAclResourceType Condition="'%(AdditionalAcls.ResourceType)' != ''">%(AdditionalAcls.ResourceType)</setAclResourceType>
<setAclAccess>%(AdditionalAcls.AclAccess)</setAclAccess>
<AdditionalProviderSettings>setAclResourceType;setAclAccess</AdditionalProviderSettings>
</MsDeploySourceManifest>
</ItemGroup>
</Target>
<Target Name="AddAdditionalAclsDeclareParameterItems">
<ItemGroup Condition="'#(AdditionalAcls)' != ''">
<MsDeployDeclareParameters Include="Add %(AdditionalAcls.AclAccess) permission to %(AdditionalAcls.Identity) Folder">
<Kind>ProviderPath</Kind>
<Scope>setAcl</Scope>
<Match>^$(_EscapeRegEx_MSDeployDirPath)\\#(AdditionalAcls)$</Match>
<Description>Add %(AdditionalAcls.AclAccess) permission to %(AdditionalAcls.Identity) Folder</Description>
<DefaultValue>{$(_MsDeployParameterNameForContentPath)}/#(AdditionalAcls)</DefaultValue>
<DestinationContentPath>$(_DestinationContentPath)/#(AdditionalAcls)</DestinationContentPath>
<Tags>Hidden</Tags>
<ExcludeFromSetParameter>True</ExcludeFromSetParameter>
<Priority>$(VsSetAclPriority)</Priority>
</MsDeployDeclareParameters>
</ItemGroup>
</Target>
You can use it by declaring:
<ItemGroup>
<AdditionalAcls Include="MyRelativeWritableDirectory" />
</ItemGroup>
Please note that this solution only currently works if you don't need a backslash in the path (ie. if it's a root directory only). If you need a sub-directory, you'll need to steal the trick I use for "SkipDeleteItems" (later in that answer) to add regex-escaped path metadata to each item.

ASP.net Web Application Project directory structure

I've recently converted an ASP.net 2.0 Web Site Project to an ASP.net 3.5 Web Application Project and I'm still getting used to the way the files are structured.
The problem I'm running into is that when I build the corresponding Web Deployment Project it copies the obj directory. But its my understanding that the obj directory is just intermediary and doesn't provide any useful purpose for a deployed website right?
If that is correct, is there some way to configure the Web Application Project to put the intermediary obj directory elsewhere, or is there something else I should be doing to handle this properly?
Just add in the .csproj_deploy file the following
<Target Name="AfterBuild">
<Message Text="------ Deleting unwanted files ------" Importance="high" />
<CreateItem Include="$(OutputPath)obj">
<Output TaskParameter="Include" ItemName="ObjDirPattern"/>
</CreateItem>
<RemoveDir Directories="#(ObjDirPattern)">
<Output TaskParameter="RemovedDirectories" PropertyName="objdir" />
</RemoveDir>
<Message Text="Deleted obj directory: $(objdir)" Importance="high" />
</Target>
For more see http://msdn.microsoft.com/en-us/library/7z253716.aspx

How can I deploy an ASP.NET web application using Team Build?

I have managed to install Team Foundation Server 2008 and I created a separate build server (which works because my builds are currently failing).
I have created a simple "Hello World" Web application (all is the standard Default.aspx page) and have it in TFS's source control system.
Previously, prior to TFS, I'd simply precompile my web application and xcopy the results on to a pre-created IIS Virtual directory.
Scouring Google for a while, I have yet to find a step by step guide on correctly deploying an application from TFS Source via TeamBuild to a designated test web server. I know MS Build falls into this equation, so any guidance would be helpful.
I have seen bits and pieces about deployments, with folders such as _PublishedWebSites mentioned, but have yet to find anything step by step.
I've had success using a exec task in the AfterDropBuild target in the TFSBuild.proj file.
<Target Name="AfterDropBuild>
<Exec Command="xcopy /Y /E "$(DropLocation)\\$(BuildNumber)\%(ConfigurationToBuild.FlavorToBuild)\_PublishedWebsites\MyWebsite1\*.*" "\\server\MyWebsite1\"" />
<Exec Command="xcopy /Y /E "$(DropLocation)\\$(BuildNumber)\%(ConfigurationToBuild.FlavorToBuild)\_PublishedWebsites\MyWebsite2\*.*" "\\server\MyWebsite2\"" />
</Target>
Note that the permissions need to be setup correctly for the TFS service user to access the folder on the server your are copying to.
Firstly you should be using WebDeployment projects as this will do a lot more compilation and checking of your code and markup. See here for more info.
I have 4 environments setup DV [Development], PY [Prototype], PP [Pre-Production], PD [Production] all matching branches in TFS. Each of these has also has an entry in the sln configuration manager where you can setup what projects are required to be build and the build flags.
Once that is setup correctly you can then start setting up deployment scripts. I prefer use MSbuild to deploy as it will give you a lot more fine-grained approach to deployment. MSbuild is a bit strange to start with however once you get the hang of it it's quite powerful.
My deployment script which is added to the TeamBuild config is below. Basically as you can see I do a bit of post-build cleanup before I copy to the live servers. I also use 2 MSbuild frameworks (imported at the top).
<Import Project="$(MSBuildExtensionsPath)\Microsoft\SDC Tasks - Release 2.1.3155.0\Microsoft.Sdc.Common.tasks"/>
<Import Project="$(MSBuildExtensionsPath)\FreeToDev\MSBuild Tasks Suite 3.5\FreeToDev.MSBuild.tasks"/>
<PropertyGroup>
<InetpubFolder>\\PathToInetPub</InetpubFolder>
<AppFolder>AppFolder</AppFolder>
<AppFolderPath>$(InetpubFolder)$(AppFolder)</AppFolderPath>
<WebDeployName>WebDeployProjectName</WebDeployName>
<Debug>0</Debug>
<AppConfiguration>DV</AppConfiguration>
</PropertyGroup>
<Target Name="AfterDropBuild">
<Message Text="Begin Release to $(AppConfiguration) Webserver" />
<Message Text="DropLocation = $(DropLocation)" />
<CallTarget Targets="PostBuildCleanUp" />
<CallTarget Targets="DeployApp" />
</Target>
<Target Name="DeployApp">
<GetBuildProperties TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)">
<Output TaskParameter="DropLocation" PropertyName="DropLocation"></Output>
</GetBuildProperties>
<PropertyGroup>
<CodeDropLocation>$(DropLocation)\$(AppConfiguration) Release</CodeDropLocation>
</PropertyGroup>
<ItemGroup>
<AppFilesToDelete Include="$(AppFolderPath)\**\*.*" Exclude="$(AppFolderPath)\Library\*.*;$(AppFolderPath)\App_Offline.htm;$(AppFolderPath)\jobs\**\*.*" />
</ItemGroup>
<ItemGroup>
<FilesToDeploy Include="$(CodeDropLocation)\$(AppFolder)\**\*.*" Exclude="" />
</ItemGroup>
<Copy SourceFiles="$(CodeDropLocation)\$(AppFolder)\App_Offline[RemoveToActivate].htm" DestinationFiles="$(AppFolderPath)\App_Offline.htm" OverwriteReadOnlyFiles="true"/>
<Message Text="Deleting files in $(AppFolderPath)" />
<Microsoft.Sdc.Tasks.File.DeleteFiles Files="#(AppFilesToDelete)" Force="true" Condition="$(Debug)==0" />
<Message Text="Copy $(CodeDropLocation)\$(AppFolder) to $(AppFolderPath)" />
<Copy Condition="$(Debug)==0" SourceFiles="#(FilesToDeploy)" DestinationFiles="#(FilesToDeploy->'$(AppFolderPath)\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true"/>
<Message Text="Deploy to $(AppConfiguration) Completed" />
<Microsoft.Sdc.Tasks.File.DeleteFiles Files="$(AppFolderPath)\App_Offline.htm" Force="true" />
<OnError ExecuteTargets="ErrorHandler" />
</Target>
<Target Name="ErrorHandler">
<Message Text="Error encountered!!" />
</Target>
<Target Name="PostBuildCleanUp">
<GetBuildProperties TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)">
<Output TaskParameter="DropLocation" PropertyName="DropLocation"></Output>
</GetBuildProperties>
<PropertyGroup>
<CodeDropLocation>$(DropLocation)\$(AppConfiguration) Release</CodeDropLocation>
</PropertyGroup>
<ItemGroup>
<PostBuildCleanUpFilesToDelete Include="$(CodeDropLocation)\*.*;$(CodeDropLocation)\bin\*.xml;$(CodeDropLocation)\bin\*.pdb"/>
</ItemGroup>
<RemoveDir Directories="$(CodeDropLocation)\_PublishedWebsites\Web" />
<Microsoft.Sdc.Tasks.File.DeleteFiles Files="#(PostBuildCleanUpFilesToDelete)" Force="true">
<Output TaskParameter="DeletedFiles" ItemName="FilesThatWereDeleted" />
</Microsoft.Sdc.Tasks.File.DeleteFiles>
<Message Text="The files that were removed were #(FilesThatWereDeleted)" />
<FTDFolder TaskAction="Move" Path="$(CodeDropLocation)\_PublishedWebsites\$(WebDeployName)" TargetPath="$(CodeDropLocation)\$(AppFolder)"/>
<RemoveDir Directories="$(CodeDropLocation)\_PublishedWebsites" />
<RemoveDir Directories="$(CodeDropLocation)\$(AppFolder)\WebDeploy" />
<OnError ExecuteTargets="ErrorHandler" />
</Target>
Obviously you will need to modify to your system setup. Also it clears down the target folder before it starts to copy the new build accross. This is to make sure they system is clean but obviously you will need to add anything that you need to keep to the ExcludedFiles list.
I also have a folder for each environment in the main application project. This holds the web.config replacements (another feature of WebDeployment projects) and any other environement specifc files.
It will be a long process to get it working correctly but hopefully this will get you started!! (Obviously if you choose this apporach!)
This can be done via the build scripts directly, the Vertigo Software guys usually are the best source of info for a lot of TFS questions like this...unfortunately their blog posts don't usually rank that high on google. This one's by Jeff Atwood, one of the creators of this site:
Copying Web Files After a Team Build

Resources