Best way to dynamically set an appender file path - c#

I am trying to find somebody smarter than me to validate some syntax I wrote up. The idea is to configure the filename of my RollingFileAppender to the name of the assembly in order to make it more re-usable for my projects.
I've seen this previous SO article but it wasn't exactly able to answer my question...
I've had a dickens of a time trying to understand the inner components of Log4net and this is what I came up with (residing in the Global.asax file - Application_Start method):
// Bind to the root hierarchy of log4net
log4net.Repository.Hierarchy.Hierarchy root =
log4net.LogManager.GetRepository()
as log4net.Repository.Hierarchy.Hierarchy;
if (root != null)
{
// Bind to the RollingFileAppender
log4net.Appender.RollingFileAppender rfa =
(log4net.Appender.RollingFileAppender)root.Root.GetAppender("RollingLogFileAppender");
if (rfa != null)
{
// Set the file name based on the assembly name
string filePath =
string.Format("~/App_Data/{0}.log", GetType().Assembly.GetName().Name);
// Assign the value to the appender
rfa.File = Server.MapPath(filePath);
// Apply changes to the appender
rfa.ActivateOptions();
}
}
Can anyone tell me, 'this is hideous', or 'this should work fine'? Also, if I set the file dynamically can I still expect the log4net behavior to rotate the files based on the log4net.config file settings?
Much appreciated!

You are doing this the hard way! Define your log4net config as XML in your application's configuration file and use %property{} to advantage:
<appender name="YourAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="~/App_Data/%property{LogName}" />
....
</appender>
This is dynamic -- you just have to set the log4net property "LogName" before you initialize log4net. Thus, in your code any time before you configure log4net, set the desired value of this property:
string LogName = GetType().Assembly.GetName().Name + ".log";
log4net.GlobalContext.Properties["LogName"] = LogName;
Of course, you may use any property name. I've chosen "LogName" for a simple example, but you can have one per application if you want, as long as your code knows what the correct property name is and what the correct value should be.

Here is way to set or change the logfile of the first appender at runtime:
var appender = (log4net.Appender.FileAppender)LogManager.GetRepository().GetAppenders()[0];
appender.File = "C:\whatever.log";
appender.ActivateOptions();

In 2015, we do it like this:
<file type="log4net.Util.PatternString">
<conversionPattern value="%appdomain.log" />
</file>
No other code required.
App domain is the executing assembly's file name.

it worked for me with the date
<file type="log4net.Util.PatternString" value="./Log/logQueueService%date{yyyy_MM_dd}.log" />

Related

Specflow - Create Pre-defined data to be shared between all scenarios in test execution with parallel execution

I am trying to Re-create my BeforeTestRun step to run my setup only once per whole execution not per thread.
I had a look a Custom Deployment steps I have implemented some already but For my setup i need to bring in some values from the app.config file I am trying something like this
my Default.srprofile file contains:
<DeploymentTransformation>
<GlobalSteps>
<Custom type="Test.CustomDeploymentStep, Test"></Custom>
</GlobalSteps>
</DeploymentTransformation>
and my CustomDeploymentStep.cs:
public class CustomDeploymentStep : IDeploymentTransformationStep
{
public static string baseUrl;
public void Apply(IDeploymentContext deploymentContext)
{
baseUrl = ConfigurationManager.AppSettings["URL"];
}
public void Restore(IDeploymentContext deploymentContext)
{
DoSomething();
}
}
My app config contains the following:
<add key="URL" value="http://google.com" />
But That does not work, The ConfigurationManager.AppSettings only returns one key and one value
"key" : "TestProjectRetargetTo35Allowed" "value":"true"
How can I load my configuration from app.config into the Apply() method in CustomDeploymentStep?
Also If there is a better/more efficient way of generating pre-defined data in specflow with thread safe execution, please do let me know
I ran into the same problem once I needed to use custom deployment steps in more than one project in a large solution. This appears to be a bug within the TechTalk.SpecRun.Framework. The error is likely "Error applying global deployment step. Global steps cannot contain test assembly specific settings." and if you look inside the TestAssembly while debugging you will see the TestAssemblyConfigFilePath is null and/or swallowing another exception.
It doesn't register a project specific configuration file. My workaround was to save the config file into debug and access what I need like so:
string appConfigFilePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\App.config";
ExeConfigurationFileMap configMap = new ExeConfigurationFileMap();
configMap.ExeConfigFilename = appConfigFilePath;
var config = ConfigurationManager.OpenMappedExeConfiguration(configMap, ConfigurationUserLevel.None);
var baseUrl = config.AppSettings.Settings["URL"].Value;

System.IO.FileInfo adds unexpected string to path when used in WIX Custom Action

I've got an WIX(V3.11.1) installer in which I'm creating an FileInfo based on value which is passed to Custom Action.
Value which was passed to Custom Action is correct, session.CustomActionData["INSTALLFOLDER"] returns proper path, which is C:\Program Files(x86)\MyApplication.
Unfortunately, when I create FileInfo targetDir = new FileInfo(session.CustomActionData["INSTALLFOLDER"]), the result of targetDir.FullName is C:\Windows\Installer\MSIE335.tmp-\C:\Program Files(x86)\MyApplication\.
I've tried to find any information about how constructor of FileInfo works, but without any result.
Do you have any ideas why C:\Windows\Installer\MSIE335.tmp-\ appears in FileInfo and how to create it with real path?
Code which is used by me to check all values:
string path = session.CustomActionData["INSTALLFOLDER"];
session.Log(path); //result is C:\Program Files(x86)\MyApplication
FileInfo targetDir = new FileInfo(path);
session.Log(targetDir.FullName); // result is C:\Windows\Installer\MSIE335.tmp-\C:\Program Files(x86)\MyApplication\
My setup-sense is guessing that the value of INSTALLFOLDER in your CustomActionData is actually the value [INSTALLFOLDER]. When logging, that syntax will get resolved to its proper value. That is why it looks good. However, what FileInfo is actually getting is a value like:
FileInfo targetDir = new FileInfo("[INSTALLFOLDER]");
Which of course is "The file named "[INSTALLFOLDER]" in the current directory". That matches your second log line.
The fix would be to ensure you're passing the value of INSTALLFOLDER in your CustomActionData. A few different ways to do that depending how you are scheduling your deferred custom action and setting the named property. For example, using SetProperty should be an easy way to fix it.
Update: Hawex provided a snippet that defined the custom action. It looked like:
<Property Id="CustomActionOnInstall" Value="INSTALLFOLDER=[INSTALLFOLDER]" />
<CustomAction Id="CustomActionOnInstall" BinaryKey="CustomActions" Execute="deferred"
Impersonate="no" DllEntry="OnInstall" Return="check" />
<InstallExecuteSequence>
<Custom Action="CustomActionOnInstall" Before="InstallFinalize">NOT Installed</Custom>
</InstallExecuteSequence>
to fix, just change the static (unevaluated) Property to a SetProperty like so:
<SetProperty Id="CustomActionOnInstall" Value="INSTALLFOLDER=[INSTALLFOLDER]"
Before="CustomActionOnInstall" Sequence="execute" />

How to set log4net create filename with logger name and date?

I would like to have files named for example:
loggername_yyyy-MM-dd.log
I try to possible:
<file type="log4net.Util.PatternString" value="Log\%logger_%date{yyyy-MM-dd}.txt" />
But he only take effect %date{yyyy-MM-dd}
How is this possible with log4net?
I want my different job use different logger, like : ILog log = LogManager.GetLogger("LogName");, he should save to 'LogName_2019-03-07.log' file
The %property{LogName} only take effect in layout

A design for adding resources to a project

I have classes Project,Resource and File.
where
A Project contains LIST of Resources.
Each Resource contains LIST of of Files of particular type.
This is mapped to an XML :
<Project>
<Resource id=1>
<File id="1" path="" type="A" />
<File id="2" path="" type="B" />
<File id="3" path="" type="B" />
<File id="4" path="" type="B" />
</Resource>
<Resource id=2>
<File id="1" path="" type="A" />
<File id="2" path="" type="B" />
<File id="3" path="" type="B" />
<File id="4" path="" type="B" />
</Resource>
</Project>
So basically every resource has to have at-most one file of type "A" and any number of files of type "B" . The file type is selected by user from a dialog where he selects the file and adds to the resource.
The problem is for every file of type "A", i need to create a new Resource and hence and new node in XML.(which my current code isn't able to do)
Initially i came with the following (generalised for brevity)
Project p =new Project("Untitled project"); //Will happen once per project
Resource res = p.CreateProjectResource("resource1");
//various params to create resource
p.AddResource(res);
//now lets add files to a resource
AddFileHelper(res,"C:\myfile1.bin","A",guid.toString());
AddFileHelper(res,"C:\myfile32.bin","B",guid.toString());
AddFileHelper(res,"C:\myfile56.bin","B",guid.toString());
//The next statement should create a new resource and add the file to
//the new created design
AddFileHelper(res,"C:\myfile4.bin","A",guid.toString()); //
//some helper class :
//Adds a file of type "type" to a resource "res" with file ID as "id"
private AddFileHelper(Resource res,string path,FileType type,string id)
{
// path is user defined file path from OpenFile dialog,
//type is selected from a Dropdown (of Enum values "A","B",...)
//id is GUID
res.AddFile(path,type,id);
//************ OR it could be also written as *******
//ResFile file =new ResFile(path,type,id);
//res.AddFile(file);
//Update XML file here..
}
The main problem is the User does not create the resources "explicitly" (except for the first resource) and creation of new Resource depends on the type of the file being added by the user.
Also due this design it is difficult to figure out the Resource given a File id.
Only way to track is using the file collection in each Resource class.
Any help??
Thanks All.
This is in reference to a question I asked before post
The problem as I understand it:
As of now, your AddFileHelper only adds files to your project resource labeled ''resource1'' which is a problem because every time a filetype “A” is passed your AddFileHelper, you to make a new resource for your project (''resource2'') and add it to that.
There is a very simple way to do this. Within AddFileHelper test the FileType of the added file and determine whether or not you need a new resource to be added to your project. If the type isn't “A” you'll call the code that you have now and add the file with:
res.AddFile(path, type, id);
If the type to add is “A” and you need a new resource, just redefine res and increment a counter variable of how many resources you have in your project:
Resource res = p.CreateProjectResource(resourceName);
resourceCounter++;
Where resourceName is the string:
string resourceName = ''resource'' + resourceCounter;
All this should be implemented as your AddFileHelper method.
Regarding the overall structure of your code, the AddFileHelper should be a project class method. Here's why:
The AddFile method, and the AddFileHelper method sound similar but do two very different things. The AddFile method belongs to the resource class because it acts on a well defined resource object. However, the AddFile method is not enough for your purposes because the resource file to append to is not immediately apparent to the client who has a file and wants to add it to your project. Bbefore the AddFile method can be called, the target resource needs to be determined. The job of the AddFileHelper method is to determine which resource will call the AddFile method. Therefore, the AddFileHelper method belongs to the project class and the AddFile method to the resource class.
The logical connection between the AddFileHelper method and the project class might be more apparent if you renamed the method to FileResourceAssignment or something like that.

Accessing App.config in a location different from the binary

In a .NET Win console application, I would like to access an App.config file in a location different from the console application binary. For example, how can C:\bin\Text.exe get its settings from C:\Test.exe.config?
using System.Configuration;
Configuration config =
ConfigurationManager.OpenExeConfiguration("C:\Test.exe");
You can then access the app settings, connection strings, etc from the config instance. This assumes of course that the config file is properly formatted and your app has read access to the directory. Notice the path is not "C:\Test.exe.config" The method looks for a config file associated with the file you specify. If you specify "C:\Test.exe.config" it will look for "C:\Test.exe.config.config" Kinda lame, but understandable, I guess.
Reference here: http://msdn.microsoft.com/en-us/library/system.configuration.configurationmanager.openexeconfiguration.aspx
It appears that you can use the AppDomain.SetData method to achieve this. The documentation states:
You cannot insert or modify system entries with this method.
Regardless, doing so does appear to work. The documentation for the AppDomain.GetData method lists the system entries available, of interest is the "APP_CONFIG_FILE" entry.
If we set the "APP_CONFIG_FILE" before any application settings are used, we can modify where the app.config is loaded from. For example:
public class Program
{
public static void Main()
{
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", #"C:\Temp\test.config");
//...
}
}
I found this solution documented in this blog and a more complete answer (to a related question) can be found here.
Use the following (remember to include System.Configuration assembly)
ConfigurationManager.OpenExeConfiguration(exePath)
You can set it by creating a new app domain:
AppDomainSetup domainSetup = new AppDomainSetup();
domainSetup.ConfigurationFile = fileLocation;
AppDomain add = AppDomain.CreateDomain("myNewAppDomain", securityInfo, domainSetup);
AppDomainSetup domainSetup = new AppDomainSetup();
domainSetup.ConfigurationFile = #"D:\Mine\Company\";
string browserName = ConfigurationManager.AppSettings["browser"];

Categories