I´m searching for a logging framework for c# where I can set the logfile name easy from code.
My program starts several background workers which all should log into their own log files.
They use the same classes for their work and sometimes in different threads.
e.g.
backgroundworker1:
var aboImport = new CSVImport(file1); <-- logs into backgroundWorker1.log
aboImport.Execute();
ArchiveFile(file1);
backgroundworker2:
var addressImport = new CSVImport(file2); <-- logs into backgroundWorker2.log
addressImport.Execute();
ArchiveFile(file2);
e.g.
backgroundWorker1.log
backgroundWorker2.log
backgroundWorker3.log
...
heartbeat.log <-- I´can see my workers or main thread is running an working
I´m using using log4net at the moment, but this does not seem to fit my needs.
I found (Create multiple Logfiles with dynamic Names with log4net) where they create dynamic appenders in code.
But I do not understand whow this works, because the log4net definitions are normally tied to the namespaces.
Can other logging frameworks do this more elegant? Which logging framework do you recommend for this?
Stay away from Log4Net. It's not an actively developed project. Last release was almost 1 yr ago (12/2015). NLog is much better supported.
With that being said, all the logging frameworks let you do everything template driven with a bunch of different "macros"... you might base the filename on threadId or some other dynamic value... or just pass it in through a key/value pair. You have to look at all the "macros" available and pick the one that works in your scenario.
With nlog I could easy create loggers with different file targets.
private static void Main(string[] args)
{
var config = LogManager.Configuration;
AddLogger(config, "logger1", #"C:\Temp\Logfile\Log1.txt");
AddLogger(config, "logger2", #"C:\Temp\Logfile\Log2.txt");
LogManager.Configuration = config;
var logger1 = LogManager.GetLogger("logger1");
logger1.Debug("Test1");
var logger2 = LogManager.GetLogger("logger2");
logger2.Debug("Test2");
}
private static void AddLogger(LoggingConfiguration config, string loggerName, string fileName)
{
var fileTarget = new FileTarget();
config.AddTarget(loggerName, fileTarget);
fileTarget.FileName = fileName;
var rule = new LoggingRule(loggerName, LogLevel.Debug, fileTarget);
config.LoggingRules.Add(rule);
}
Related
For a certain portion of my code, I need to log all messages to a second file, specific to each instance of the action completed in two classes.
These classes (Parser and Importer) both have the following logger definition:
private static ILog _logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
Both also have this method:
public void AddAppender(IAppender appender)
{
((Logger)_logger.Logger).AddAppender(appender);
}
The whole project (ASP.NET WebForms) logging is initialized in the AssemblyInfo.cs:
[assembly: XmlConfigurator]
Now, when the user triggers a certain action, this happens:
var logFile = new FileInfo(ConfigurationManager.AppSettings["uploadDir"] + "/" + importId + "_log.txt");
var appender = new FileAppender() {
Threshold = Level.Info,
Layout = new PatternLayout("%message%newline"),
File = logFile.FullName
};
var parser = new Parser(...);
parser.AddAppender(appender);
var candidates = parser.Parse(File.ReadAllLines(uploadFile.FullName));
var importer = new Importer(...);
importer.AddAppender(appender);
importer.Import(candidates, false, ignoreWarnings.Checked);
My expectation would be, that a file will be created at the logFile location, that would contain all INFO and above message from either of the two classes.
This, however, is not the case - no file is created at all.
What am I doing wrong and how can I make this work?
Per the documentation of FileAppender:
This appender will first try to open the file for writing when
ActivateOptions is called. This will typically be during
configuration. If the file cannot be opened for writing the appender
will attempt to open the file again each time a message is logged to
the appender. If the file cannot be opened for writing when a message
is logged then the message will be discarded by this appender.
This documentation is slightly ambiguous, since it seems to imply the file will be opened when you attempt to log a message in all cases, but this isn't true -- unless you call ActivateOptions, the appender is never considered ready for appending.
I'm using the TraceEvent library to capture ETW traces, but I'm not able to determine the name of the process that caused an event.
Here is what I have so far:
var session = new TraceEventSession(sessionName, null);
session.EnableProvider(MyEventSource.Log.Guid, TraceEventLevel.Informational,
options: TraceEventOptions.Stacks);
Task.Delay(1000).ContinueWith(t => session.Stop()); // for testing, deal with it (⌐■_■)
var src = new ETWTraceEventSource(sessionName, TraceEventSourceType.Session);
TraceLog.CreateFromSource(src, etlxFile, null);
var log = TraceLog.OpenOrConvert(etlxFile);
var process = log.Events.First().ProcessName;
// breakpoint
When the breakpoint at the end is hit, process is "". ProcessID is a proper PID, but that's the only useful information I could find from the processes in the log.
I expected process names to be captured by the log. Am I doing something wrong, or is this API just not available on my OS (Windows 7)?
I truly believe that process name is not being captured by the ETW log. Etw system event contains only process ID field. Although TraceEvent library declares this one as a part of TraceEvent, this one actually is being populated based on executable image filename and process ID, which is implemented differently for all 4 TraceEventSource implementations.
Another observation is that I was never able to have this one populated (my OS is Windows 8.1).
The simple repro is to use SimpleEventSourceMonitor sample from Microsoft TraceEvent Library Samples package.
If you suspect that this is an issue, then it is better ask its owners Vance Morrison and Cosmin Radu.
This can be done by enabling the kernel provider, and then maintaining a lookup of process id to process name. Here's a rough example - no error checking, but you get the idea.
// create a lookup collection for future use
var pidToProcessName = new Dictionary<int, string>();
var session = new TraceEventSession(...);
// enable the kernel provider - note! this most come first
session.EnableKernelProvider(KernelTraceEventParser.Keywords.Process);
...
session.Source.Kernel.ProcessStart += ProcessStart;
session.Source.Dynamic.All += TraceEvent;
...
session.Source.Procces();
void ProcessStart(ProcessTraceData obj)
{
if(obj.OpCode == TraceEventOpcode.Start)
{
pidToProcessName[obj.ProcessID] = obj.ProcessName;
}
}
void TraceEvent(TraceEvent obj)
{
// pull the process name from our lookup
var processNameOfEvent = pidToProcessName[obj.ProcessID];
}
I just found out about NRefactory 5 and I would guess, that it is the most suitable solution for my current problem. At the moment I'm developing a little C# scripting application for which I would like to provide code completion. Until recently I've done this using the "Roslyn" project from Microsoft. But as the latest update of this project requires .Net Framework 4.5 I can't use this any more as I would like the app to run under Win XP as well. So I have to switch to another technology here.
My problem is not the compilation stuff. This can be done, with some more effort, by .Net CodeDomProvider as well. The problem ist the code completion stuff. As far as I know, NRefactory 5 provides everything that is required to provide code completion (parser, type system etc.) but I just can't figure out how to use it. I took a look at SharpDevelop source code but they don't use NRefactory 5 for code completion there, they only use it as decompiler. As I couldn't find an example on how to use it for code completion in the net as well I thought that I might find some help here.
The situation is as follows. I have one single file containing the script code. Actually it is not even a file but a string which I get from the editor control (by the way: I'm using AvalonEdit for this. Great editor!) and some assemblies that needs to get referenced. So, no solution files, no project files etc. just one string of source code and the assemblies.
I've taken a look at the Demo that comes with NRefactory 5 and the article on code project and got up with something like this:
var unresolvedTypeSystem = syntaxTree.ToTypeSystem();
IProjectContent pc = new CSharpProjectContent();
// Add parsed files to the type system
pc = pc.AddOrUpdateFiles(unresolvedTypeSystem);
// Add referenced assemblies:
pc = pc.AddAssemblyReferences(new CecilLoader().LoadAssemblyFile(
System.Reflection.Assembly.GetAssembly(typeof(Object)).Location));
My problem is that I have no clue on how to go on. I'm not even sure if it is the right approach to accomplish my goal. How to use the CSharpCompletionEngine? What else is required? etc. You see there are many things that are very unclear at the moment and I hope you can bring some light into this.
Thank you all very much in advance!
I've just compiled and example project that does C# code completion with AvalonEdit and NRefactory.
It can be found on Github here.
Take a look at method ICSharpCode.NRefactory.CSharp.CodeCompletion.CreateEngine. You need to create an instance of CSharpCompletionEngine and pass in the correct document and the resolvers. I managed to get it working for CTRL+Space compltition scenario. However I am having troubles with references to types that are in other namespaces. It looks like CSharpTypeResolveContext does not take into account the using namespace statements - If I resolve the references with CSharpAstResolver, they are resolved OK, but I am unable to correctly use this resolver in code completition scenario...
UPDATE #1:
I've just managed to get the working by obtaining resolver from unresolved fail.
Here is the snippet:
var mb = new DefaultCompletionContextProvider(doc, unresolvedFile);
var resolver3 = unresolvedFile.GetResolver(cmp, loc); // get the resolver from unresolvedFile
var engine = new CSharpCompletionEngine(doc, mb, new CodeCompletionBugTests.TestFactory(resolver3), pctx, resolver3.CurrentTypeResolveContext );
Update #2:
Here is the complete method. It references classes from unit test projects, sou you would need to reference/copy them into your project:
public static IEnumerable<ICompletionData> DoCodeComplete(string editorText, int offset) // not the best way to put in the whole string every time
{
var doc = new ReadOnlyDocument(editorText);
var location = doc.GetLocation(offset);
string parsedText = editorText; // TODO: Why there are different values in test cases?
var syntaxTree = new CSharpParser().Parse(parsedText, "program.cs");
syntaxTree.Freeze();
var unresolvedFile = syntaxTree.ToTypeSystem();
var mb = new DefaultCompletionContextProvider(doc, unresolvedFile);
IProjectContent pctx = new CSharpProjectContent();
var refs = new List<IUnresolvedAssembly> { mscorlib.Value, systemCore.Value, systemAssembly.Value};
pctx = pctx.AddAssemblyReferences(refs);
pctx = pctx.AddOrUpdateFiles(unresolvedFile);
var cmp = pctx.CreateCompilation();
var resolver3 = unresolvedFile.GetResolver(cmp, location);
var engine = new CSharpCompletionEngine(doc, mb, new CodeCompletionBugTests.TestFactory(resolver3), pctx, resolver3.CurrentTypeResolveContext );
engine.EolMarker = Environment.NewLine;
engine.FormattingPolicy = FormattingOptionsFactory.CreateMono();
var data = engine.GetCompletionData(offset, controlSpace: false);
return data;
}
}
Hope it helps,
Matra
NRefactory 5 is being used in SharpDevelop 5. The source code for SharpDevelop 5 is currently available in the newNR branch on github. I would take a look at the CSharpCompletionBinding class which has code to display a completion list window using information from NRefactory's CSharpCompletionEngine.
Right now whenever I run application It creates a file (file name specified in Web.Config Logging Section).
What I want is to change the path and name of the file (based on GUID passing as query sting) at run time.
I did some research, but examples available are not working for me. Will appreciate if someone can provide a code sample.
As you've discovered, your requirements are not a natural fit for the configuration based approach of Enterprise Library. That's not to say that it can't be done.
One way to accomplish what you want is to use programmatic configuration of the logging block.
One approach would be to create an IDictionary to hold a mapping of IDs to LogWriters. When an ID comes in check to see if a LogWriter already exists in the IDictionary. If it does then use it and if it doesn't then create a new LogWriter.
Here is some sample code that uses the Logging Fluent Interface to configure a UnityContainer and then resolves a LogWriter and saves it to a Dictionary:
int id = 123123;
Dictionary<int, LogWriter> loggers = new Dictionary<int, LogWriter>();
ConfigurationSourceBuilder builder = new ConfigurationSourceBuilder();
builder.ConfigureLogging()
.WithOptions
.DoNotRevertImpersonation()
.SpecialSources.LoggingErrorsAndWarningsCategory.SendTo.FlatFile("Flat File Listener").ToFile(#"trace.log")
.LogToCategoryNamed("General")
.WithOptions.SetAsDefaultCategory()
.SendTo.FlatFile("AppSpecificFlatFile" + id)
.ToFile("logging" + id + ".log")
;
DictionaryConfigurationSource configSource = new DictionaryConfigurationSource();
builder.UpdateConfigurationWithReplace(configSource);
coreExtension = new EnterpriseLibraryCoreExtension(configSource);
IUnityContainer container = new UnityContainer();
container.AddExtension(coreExtension);
var logger = container.Resolve<LogWriter>();
loggers[id] = logger;
This is just a sample and is not thread safe or abstracted but should hopefully help.
If you just need to change the trace log file at runtime, you can simply remove the current tracelistener using
System.Diagnostics.Trace.Listener.Remove("Listener1");
Then add a new Listener with the new log file name as,
System.Diagnostics.Trace.Listener.Add(new System.Diagnostics.TextWriterTraceListener("NewLogFileName.txt","Listener2");
Now your logs will be recorded to NewLogFileName.txt
I'm facing currently a problem regarding Subsonic configuration.
What I want to achieve is using subsonic data access in a System.Web.UI.Design.ControlDesigner class.
This class is hosted in Visual Studio Environment and enables design time operations on the attached System.Web.UI.WebControls.Control.
The only problem is SubSonic seems always looking for SubSonicSection in the application configuration regardless passing connection string to it.
The relevant code snippet:
using (SharedDbConnectionScope dbScope = new SharedDbConnectionScope(new SqlDataProvider(), ConnectionString))
{
Table1 _table1 = new Select().From<..().Where(...).IsEqualTo(...).ExecuteSingle<...>();
Throws exception on ExecuteSingle() method (configuration section was not found)
while
using (SharedDbConnectionScope dbScope = new SharedDbConnectionScope(ConnectionString))
{
Throws exception on new SharedDbConnectionScope() (configuration section was not found)
So the question is:
Is there any way to pass the settings runtime to bypass the configuration section lookup as I don't want to add any subsonic specific configuration to devenv.configuration
Thanks
I don't think you can do this in 2.x without customising the templates (which can obviously give support issues when a newer version of SubSonic is released).
Sorry, don't know about 3.0
I'm assuming you're using SubSonic 2.x based on your query syntax. Have a look at the following two forum posts which should point you in the right direction. What you're trying to do is possible, in fact SubCommander does exactly this, download the source and have a look at the SetProviderManually() method.
http://forums.subsonicproject.com/forums/t/1617.aspx
http://forums.subsonicproject.com/forums/t/1502.aspx
The method to use Subsonic runtime provider configuration:
(example):
private void SetSubsonicProviderManually(string ConnectionString)
{
//clear the providers and reset
DataService.Provider = new SqlDataProvider();
DataService.Providers = new DataProviderCollection();
//instance a section - we'll set this manually for the DataService
SubSonicSection section = new SubSonicSection();
section.DefaultProvider = __SubsonicProviderName__;
//set the properties
DataProvider provider = DataService.Provider;
NameValueCollection config = new NameValueCollection();
//need to add this for now
config.Add("connectionStringName", __ConnectionString__);
//initialize the provider
provider.Initialize(__SubsonicProviderName__, config);
provider.DefaultConnectionString = ConnectionString;
DataService.Providers.Add(provider);
}