Log aspect in PostSharp 5.0.23 not applied at assembly level - postsharp

I am following the steps mentioned in http://doc.postsharp.net/add-logging . The Visual studio version is 2017 and PostSharp 5.0.28 extension is installed. When the log aspect for console backend is added to the whole project, the GlobalAspect.cs file is added but the output shows no log trace. But when I apply the [Log] attribute to the method the log trace is seen. Why is the definition in GlobalAspect not applied?
using PostSharp.Patterns.Diagnostics;
namespace ConsoleApp1
{
[Log(AttributeExclude = true)]
public class Program
{
static void Main(string[] args)
{
LoggingServices.DefaultBackend = new PostSharp.Patterns.Diagnostics.Backends.Console.ConsoleLoggingBackend();
}
public static void f()
{
}
}
}
I have the following entry in GlobalAspects.cs file of console and dll. While in dll it works, the console it doesn't.
[assembly: Log(AttributeTargetTypeAttributes=MulticastAttributes.Public‌​, AttributeTargetMemberAttributes=MulticastAttributes.Public)]

Unfortunately, GlobalAspect seems to have no impact on Console application. On a class library, it does work neat.

When you apply the [Log] aspect on the assembly level, it is propagated to the methods in the assembly according to the specified rules.
You can set properties such AttributeTargetTypeAttributes, AttributeTargetMemberAttributes to specify whether you want to target public,private, instance, static methods etc. You can also specify the namespace, type and member names.
The defaults for these properties may not correspond with what you want. You need to make sure that the specified properties match the characteristics of the desired target methods.
[Log(AttributeTargetTypeAttributes = MulticastAttributes.Public, AttributeTargetMemberAttributes = MulticastAttributes.AnyVisibility)]
You can find more details on this documentation page: http://doc.postsharp.net/attribute-multicasting

Related

Can't redirect Trace.WriteLine to Serilog using SerilogTraceListener

In one of my project (.net core 3.1), I need a way to redirect System.Diagnostics.Trace.WriteLine to Serilog file. I found the SerilogTraceListener package which seems to be the right candidate.
Unfortunately until now, I haven't been able to find a way to make it works.
To reproduce it,
1) Create a .net core console project
2) Add the following nuget package : Serilog, SerilogTraceListener, Serilog.Sink.Console, Serilog.Sink.File
3) Overwrite the Program class code by the following
class Program
{
static void Main(string[] args)
{
// Works fine
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("log.txt")
.CreateLogger();
Log.Logger.Information("A string written using Logger.Information");
// Trace is written in the console but not in the file
Trace.Listeners.Add(new ConsoleTraceListener());
Trace.Listeners.Add(new global::SerilogTraceListener.SerilogTraceListener());
System.Diagnostics.Trace.WriteLine("A string written using Trace.WriteLine");
}
}
What am I doing wrong?
TL;DR; You need to set the MinimumLevel to Debug or Verbose in order to see the Trace.WriteLine messages.
SerilogTraceListener maps System.Diagnostic.TraceEventType to Serilog.LogEventLevel, and when you call Trace.WriteLine, it maps these events to the Debug log event level.
That means Serilog's logger is receiving a message of type LogEventLevel.Debug.
The minimum level configured in Serilog by default is Information, which means Debug messages are being suppressed.
You have to configure the MinimumEventLevel to Debug (or Verbose) in order to see the Trace.WriteLine messages:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
.WriteTo.Console()
.WriteTo.File("log.txt")
.CreateLogger();

methodAccessException when passing variables from ViewModel to ViewModel on WP7 using anonymous object (MVVMCross)

I've created an app using MVVMCross, the IOS and Android versions are working but when I tried to "port" to WP7 and I ran into the following problem:
throw methodAccessException.MvxWrap("Problem accessing object - most likely this is caused by an anonymous object being generated as Internal - please see http://stackoverflow.com/questions/8273399/anonymous-types-and-get-accessors-on-wp7-1");
As mentioned in the answer to my other question about this (on Android) you have to set an InternalsVisibleTo attribute in the AssemblyInfo.cs for WP7. So I did:
[assembly: InternalsVisibleTo("Cirrious.MvvmCross.WindowsPhone")]
But this doesn't make any difference. I use the following code to send two variables form my BeckhoffViewModel to my BeckhoffSensorViewModel.
BeckhoffViewModel:
public IMvxCommand BeckhoffSensor1
{
get
{
return new MvxRelayCommand(kvpSens1);
}
}
private void kvpSens1()
{
RequestNavigate<BeckhoffSensorViewModel>(new { VarType = "short", Variable = ".countertest" });
}
BeckhoffSensorViewModel:
public BeckhoffSensorViewModel(string VarType, string Variable)
{
_vartype = VarType;
_variable = Variable;
}
Anything I'm overlooking? I also looked at the other stackoverflow topic mentioned in the exception but couldn't really understand it.
The anonymous class will most definitely be created as internal by the compiler - which is why you need the line [assembly: InternalsVisibleTo("Cirrious.MvvmCross.WindowsPhone")]
Can you check that the AssemblyInfo.cs file definitely being linked into the project (and that this is the project containing the ViewModel/anonymous-class code)?
If that is the case, can you check the methodAccessException to see what the message is?
If that doesn't help, can you use a tool like Reflector to check the internalVisible attribute is actually present on the core/application assembly?

NUnit extension

Hi All i have a question regarding NUnit Extension (2.5.10).
What i am trying to do is write some additional test info to the
database. For that i have created NUnit extension using Event
Listeners.
The problem i am experiencing is that public void
TestFinished(TestResult result) method is being called twice at
runtime. And my code which writes to the database is in this method
and that leaves me with duplicate entries in the database. The
question is: Is that the expected behaviour? Can i do something about
it?
The extension code is below. Thanks.
using System;
using NUnit.Core;
using NUnit.Core.Extensibility;
namespace NuinitExtension
{
[NUnitAddinAttribute(Type = ExtensionType.Core,
Name = "Database Addin",
Description = "Writes test results to the database.")]
public class MyNunitExtension : IAddin, EventListener
{
public bool Install(IExtensionHost host)
{
IExtensionPoint listeners = host.GetExtensionPoint("EventListeners");
if (listeners == null)
return false;
listeners.Install(this);
return true;
}
public void RunStarted(string name, int testCount){}
public void RunFinished(TestResult result){}
public void RunFinished(Exception exception){}
public void TestStarted(TestName testName){}
public void TestFinished(TestResult result)
{
// this is just sample data
SqlHelper.SqlConnectAndWRiteToDatabase("test", test",
2.0, DateTime.Now);
}
public void SuiteStarted(TestName testName){}
public void SuiteFinished(TestResult result){}
public void UnhandledException(Exception exception){}
public void TestOutput(TestOutput testOutput){}
}
}
I have managed to fix the issue by simply removing my extension
assembly from NUnit 2.5.10\bin\net-2.0\addins folder. At the moment
everything works as expected but i am not sure how. I thought that you
have to have the extension/addin assembly inside the addins folder.
I am running tests by opening a solution via NUnit.exe. My extension
project is part of the solution i am testing. I have also raised this issue with NUnit guys and got the following explanation:
Most likely, your addin was being loaded twice. In order to make it easier to test addins, NUnit searches each test assembly for addins to be loaded, in addition to searching the addins directory. Normally, when you are confident that your addin works, you should remove it from the test assembly and install it in the addins folder. This makes it available to all tests that are run using NUnit. OTOH, if you really only want the addin to apply for a certain project, then you can leave it in the test assembly and not install it as a permanent addin.
http://groups.google.com/group/nunit-discuss/browse_thread/thread/c9329129fd803cb2/47672f15e7cc05d1#47672f15e7cc05d1
Not sure this answer is strictly relevant but might be useful.
I was having a play around with the NUnit library recently to read NUnit tests in so they could easily be transfered over to our own in-house acceptance testing framework.
It turns out we probably wont stick with this but thought it might be useful to share my experiences figuring out how to use the NUnit code:
It is different in that it doesn't get run by the NUnit console or Gui Runner but just by our own console app.
public class NUnitTestReader
{
private TestHarness _testHarness;
public void AddTestsTo(TestHarness testHarness)
{
_testHarness = testHarness;
var package = new TestPackage(Assembly.GetExecutingAssembly().Location){AutoBinPath = true};
CoreExtensions.Host.InitializeService();
var testSuiteBuilder = new TestSuiteBuilder();
var suite = testSuiteBuilder.Build(package);
AddTestsFrom(suite);
}
private void AddTestsFrom(Test node)
{
if (!node.IsSuite)
AddTest(node);
else
{
foreach (Test test in node.Tests)
AddTestsFrom(test);
}
}
private void AddTest(Test node)
{
_testHarness.AddTest(new WrappedNUnitTest(node, TestFilter.Empty));
}
}
The above reads NUnit tests in from the current assembly wraps them up and then adds them to our inhouse test harness. I haven't included these classes but they're not really important to understanding how the NUnit code works.
The really useful bit of information here is the static to "InitialiseService" this took quite a bit of figuring out but is necessary to get the basic set of test readers loaded in NUnit. You need to be a bit careful when looking at the tests in NUnit aswell as it includes failing tests (which I assume dont work because of the number of statics involved) - so what looks like useful documentation is actually misleading.
Aside from that you can then run the tests by implementing EventListener. I was interested in getting a one to one mapping between our tests and NUnit tests so each test is run on it's own. To achieve this you just need to implement TestStarted and TestFinished to do logging:
public void TestStarted(TestName testName)
{
}
public void TestFinished(TestResult result)
{
string text;
if (result.IsFailure)
text = "Failure";
else if (result.IsError)
text = "Error";
else
return;
using (var block = CreateLogBlock(text))
{
LogFailureTo(block);
block.LogString(result.Message);
}
}
There are a couple of problems with this approach: Inherited Test base classes from other assemblies with SetUp methods that delegate to ones in the current assembly dont get called. It also has problems with TestFixtureSetup methods which are only called in NUnit when TestSuites are Run (as opposed to running test methods on their own).
These both seem to be problems with NUnit although if you dont want to construct wrapped tests individually I think you could just put in a call to suite.Run with the appropriate parameters and this will fix the latter problem

CM+MEF: registering MEF exports from external assemblies

this post ideally continues my other post on MEF plugins, but my first post was too full of comments and this sample is more complete. Here I summarize my updated scenario, with all my findings up to this point. Hope this can be useful to other CM newbies like me.
You can download a full repro sample scenario: it's an almost do-nothing dumb skeleton for a CM + MEF plugins-based application:
VS2010 repro solution (updated)
This is a minimal stripped down solution representing my issues with CM+MEF.
There are 3 projects:
the main UI (CmRepro).
a core DLL shared among all the addins (AddinCore), with a couple of interfaces and custom attributes used for MEF metadata.
a sample addin DLL (AlphaAddin), with a view and a viewmodel implementing the interfaces.
The core contains 2 interfaces representing a viewmodel and its view, and 2 attributes to be used for decorating the viewmodels and the views. The viewmodel interface describes a class which should compose a greeting message from some person name, so it exposes a couple of properties and a method for this. The view interface just exposes a property returning its DataContext cast to the viewmodel interface.
The sample addin has an implementation for a viewmodel and a view; both are MEF-exports, decorated by the corresponding attribute. In the real-world solution several properties of these attributes are used for filtering; here I just have a dummy Language property which should allow for other plugins for different languages.
The main UI has a MEF bootstrapper which adds code for retrieving MEF exports from an Addins folder. I modified this code to include exports from MEF directories and get a better understanding of some MEF exceptions, but still I cannot figure out how to properly "register" them with CM.
The main viewmodel has 2 methods: one (A) uses a MEF catalog to retrieve a viewmodel and its view, bind them and show them into a window. Another (B) uses the same catalog to get a viewmodel, and then a CM window manager to locate, create, bind and show the corresponding view according to CM naming conventions. These methods represent two alternative ways I should deal with in my real-world code, i.e. instantiating some crucial objects "by myself" just using MEF but then let them work for CM, or letting CM (with a MEF-bootstrapper) do most of the work starting from a viewmodel.
Anyway, it seems that in both cases I am missing something as for registration with CM. Issues:
(A) how do I wire up VM+V for CM so that the conventions for databinding etc are applied? At this time I can build my MEF parts together, but CM ignores them as it was not used to instantiate none of them.
I answer to myself here:
ViewModelBinder.Bind(viewmodel, (UserControl)view, null);
(B) how do I register the exports from MEF in CM so that the CM window manager can find the view? Currently it does not manage to locate the view from the viewmodel.
Addition (21 jun)
I try to explain better for whom cannot access the repro solution. I use a "standard" MEF bootstrapper, changing the Configure override like:
_container = new CompositionContainer(
new AggregateCatalog(AssemblySource.Instance.Select(
x => new AssemblyCatalog(x)).OfType()
.Union(GetAddinDirectoryCatalogs())));
this creates a MEF composition container which aggregates the catalog from AssemblySource, with CM types like event aggregator or window manager, with a catalog from several addin directories, which contain exports for both V and VM's.
In my sample main viewmodel I create a new VM from a plugin, found in the host application directory among others, and I'd like a CM window manager to locate, instantiate and show its view in a dialog, e.g.:
viewmodel = GetMyViewModelFromAddin();
windowmanager.ShowDialog(viewmodel);
CM anyway cannot locate the view. AFAIK, naming conventions are honored: both V and VM are in the same addin assembly, marked as MEF exports, named like SomethingViewModel / SomethingView. Anyway, as Leaf pointed out in his clarification, AssemblySource.Instance is a static IObservableCollection collection of assemblies and I have not added my addins to it. But this is right the point: I would not like to add all of them in advance, because this means loading ALL the addins without yet knowing which (if any) will be ever used. A robust plugin system is the reason for using MEF, after all. I'm new to CM and not sure if it is possible (and where) to find an extension point for CM in this scenario. The window manager does not call my bootstrapper implementation at all, clearly because there is nothing to be instantiated by IoC, as no match was found in assembly source Instance. So, it seems I'm stuck here, the only solution being loading in advance all the assemblies in the Instance, but this seems defeating the whole purpose of using MEF.
The plugin-based application I'm developing loads tons of V+VM CM "pairs" representing user interface widgets, which in turn often use a window manager to popup other V+VM's pairs as dialogs. I can bypass instantiation by CM and use MEF to retrieve V+VM for each widget, but still I'm facing the same view location issue for each widget requiring a window manager. The other alternative (workaround) I can see is avoding the use of window manager and implement my own mechanism for the purpose of showing dialogs from widgets, but this makes feel me a little wrong about CM...:). Usually when I find myself writing much more code than expected I am inclined to think I'm not using the tool in the right way. Any idea?
Caliburn.Micro goes through 3 stages to find a view from a viewmodel instance.
Does text transforms to transform view type name to viewmodel type name. There are a set of standard conventions for these transformations, e.g. SomeNamespace.ViewModels.CustomerViewModel might map to SomeNamespace.Views.CustomerView.
With the view type name(s), Caliburn.Micro then uses AssemblySource.Instance (a static IObservableCollection<Assembly> collection of assemblies) to find the first matching Type.
Caliburn.Micro attempts to create an instance of that Type via one of the IoC.GetInstance() methods (which delegate to your bootstrapper and hence MEF).
I'm guessing (your file share site is blocked here) that the problem with resolving views from viewmodels is due to the second step and the AssemblySource.Instance collection not containing your dynamic assembly.
One solution might be to add each dynamically loaded addin's assembly to AssemblySource.Instance when they are loaded, or if you know all assemblies at startup then you can override the Bootstrapper.SelectAssemblies method to return list of assemblies you expect to contain views and viewmodels.
Update to show how you could pull assemblies from MEF loaded parts
If you are using DirectoryCatalog to load your parts from other assemblies then you could find the assemblies used like this:
var directoryCatalog = new DirectoryCatalog(#"./");
AssemblySource.Instance.AddRange(
directoryCatalog.Parts
.Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly)
.Where(assembly => !AssemblySource.Instance.Contains(assembly)));
If your addins folder changes during the runtime of the application then you would have to DirectoryCatalog.Refresh() the catalog and run the code to add any new assemblies to AssemblySource.Instance
I found a workaround. It's not that pretty, but it allows CM and its window manager to work. I summarize here my findings, hope this will help others or let someone point me to a better solution.
Given that (a) I do not want to load ALL the assemblies including all their dependencies in my plugins folder, to avoid polluting my app domain with unused stuff; and that (b) the only available CM extension point for this seems SelectAssemblies, my goal is add my assemblies there, but only the plugin assemblies requiring to be registered with CM.
So I started looking for a way of loading all the DLL's into a temporary app domain, scan them for some aspect marking them as plugins, and then load into the current app domain only the plugin ones, passing them to SelectAssemblies. This is far from being the optimal solution, as I cannot use a general purpose scanning way like MEF, and I feel I'm duplicating the effort, but at least it's a working solution.
First, to provide at least a way for loading only the plugins, I decorate my plugin assemblies requiring CM registration with a custom attribute marking them as plugins, and further enumerate their types looking for the ones which should be later used by MEF.
The attribute is as simple as:
[AttributeUsage(AttributeTargets.Assembly)]
public class AssemblyRegisteredWithCMAttribute : Attribute {}
I then found this very good piece of code for scanning assemblies into another temporary AppDomain:
http://sachabarber.net/?p=560
I had to modify it a bit, because it was failing when loading assemblies with dependencies, and it was scanning the assemblies types while in my case I just need to check for the attribute.
Here is my code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Policy;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
namespace CmRepro
{
///
/// Separate AppDomain assembly loader.
///
/// Modified from http://sachabarber.net/?p=560 .
public class SeparateAppDomainAssemblyLoader
{
///
/// Loads an assembly into a new AppDomain returning the names of the files
/// containing assemblies marked with the assembly attribute name matching
/// the specified name. The new AppDomain is then Unloaded.
///
/// list of files to load
/// assemblies directory
/// matching attribute name
/// list of found namespaces
/// null files, assembly directory or matching attribute
public List LoadAssemblies(string[] aFiles, string sAssemblyDirectory, string sMatchingAttribute)
{
if (aFiles == null) throw new ArgumentNullException("aFiles");
if (sAssemblyDirectory == null) throw new ArgumentNullException("sAssemblyDirectory");
if (sMatchingAttribute == null) throw new ArgumentNullException("sMatchingAttribute");
List<String> namespaces = new List<String>();
AppDomain childDomain = BuildChildDomain(AppDomain.CurrentDomain);
try
{
Type loaderType = typeof(AssemblyLoader);
if (loaderType.Assembly != null)
{
AssemblyLoader loader = (AssemblyLoader)childDomain.
CreateInstanceFrom(
loaderType.Assembly.Location,
loaderType.FullName).Unwrap();
namespaces = loader.LoadAssemblies(aFiles, sAssemblyDirectory, sMatchingAttribute);
} //eif
return namespaces;
}
finally
{
AppDomain.Unload(childDomain);
}
}
/// <summary>
/// Creates a new AppDomain based on the parent AppDomains
/// Evidence and AppDomainSetup.
/// </summary>
/// <param name="parentDomain">The parent AppDomain</param>
/// <returns>A newly created AppDomain</returns>
private AppDomain BuildChildDomain(AppDomain parentDomain)
{
Evidence evidence = new Evidence(parentDomain.Evidence);
AppDomainSetup setup = parentDomain.SetupInformation;
return AppDomain.CreateDomain("DiscoveryRegion", evidence, setup);
}
/// <summary>
/// Remotable AssemblyLoader, this class inherits from <c>MarshalByRefObject</c>
/// to allow the CLR to marshall this object by reference across AppDomain boundaries.
/// </summary>
private class AssemblyLoader : MarshalByRefObject
{
private string _sRootAsmDir;
/// <summary>
/// ReflectionOnlyLoad of single Assembly based on the assemblyPath parameter.
/// </summary>
/// <param name="aFiles">files names</param>
/// <param name="sAssemblyDirectory">assemblies directory</param>
/// <param name="sMatchingAttribute">matching attribute name</param>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
internal List<string> LoadAssemblies(string[] aFiles, string sAssemblyDirectory, string sMatchingAttribute)
{
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += OnReflectionOnlyAssemblyResolve;
_sRootAsmDir = sAssemblyDirectory;
List<string> aAssemblies = new List<String>();
try
{
sMatchingAttribute = "." + sMatchingAttribute;
foreach (string sFile in aFiles)
Assembly.ReflectionOnlyLoadFrom(sFile);
aAssemblies.AddRange(from asm in AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies()
let attrs = CustomAttributeData.GetCustomAttributes(asm)
where attrs.Any(a => a.ToString().Contains(sMatchingAttribute))
select asm.FullName);
return aAssemblies;
}
catch (FileNotFoundException)
{
/* Continue loading assemblies even if an assembly
* can not be loaded in the new AppDomain. */
return aAssemblies;
}
}
private Assembly OnReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs e)
{
// http://blogs.msdn.com/b/junfeng/archive/2004/08/24/219691.aspx
System.Diagnostics.Debug.WriteLine(e.Name);
AssemblyName name = new AssemblyName(e.Name);
string sAsmToCheck = Path.GetDirectoryName(_sRootAsmDir) + "\\" + name.Name + ".dll";
return File.Exists(sAsmToCheck)
? Assembly.ReflectionOnlyLoadFrom(sAsmToCheck)
: Assembly.ReflectionOnlyLoad(e.Name);
}
}
}
}
Now in my bootstrapper I override the SelectAssemblies method as follows:
...
protected override IEnumerable SelectAssemblies()
{
string sAddinPath = GetAbsolutePath(ADDIN_PATH);
FileCheckList list = new FileCheckList(sAddinPath);
// check only DLL files which were added or changed since last check
SeparateAppDomainAssemblyLoader loader = new SeparateAppDomainAssemblyLoader();
List<string> aAssembliesToRegister = loader.LoadAssemblies(list.GetFiles(null),
sAddinPath, "AssemblyRegisteredWithCM");
string[] aFilesToRegister = (from s in aAssembliesToRegister
select Path.Combine(sAddinPath, s.Substring(0, s.IndexOf(',')) + ".dll")).ToArray();
// update checklist
foreach (string sFile in aFilesToRegister) list.SetCheck(sFile, true);
list.UncheckAllNull();
list.Save();
// register required files
return (new[]
{
Assembly.GetExecutingAssembly(),
}).Union((from s in list.GetFiles(true)
select Assembly.LoadFrom(s))).ToArray();
}
...
As you can see, I am calling the loader not for all the DLL's in my addins path, but only for the ones which a cached files checklist tells me have been added or modified in that folder since the last full scan. This should speed up things a bit and does not require the checklist file to exist: if not found it will be recreated at startup by scanning all the files, if found only added or changed files will be scanned again (I use a CRC to detect changes). So I get the addins folder, create a files checklist for that folder, get from it a list of new or changed files, and pass it to the assemblies loader. This returns only the names of the DLL files which should be registered (i.e. the ones containing assemblies marked with my attribute); I then update the checklist for the next startup, and register only the required files. This way I can let my addin VM's use window managers and correctly locate the view for each required viewmodel. Somewhat ugly, but working. Thanks again to Leaf, who explained me the working of CM.

EventLogInstaller Full Setup with Categories?

It appears the MSDN docs are broken concerning creating an Event Log completely along with a definitions file for messages. I am also lost on how to setup Categories (I have custom numbers in the 3000's for messages).
Can anyone point me to a link or show sample code on how to make this right?
You should start (if you haven't done so already) here:
EventLogInstaller Class (System.Diagnostics)
The sample provided there is the foundation for what you want to do. To sum it up, build a public class inheriting from System.Configuration.Install.Installer in an assembly (could be the same DLL where you have the rest of your application, a separate DLL, or an EXE file), decorate it with the RunInstaller attribute, and add your setup code in the constructor:
using System;
using System.Configuration.Install;
using System.Diagnostics;
using System.ComponentModel;
[RunInstaller(true)]
public class MyEventLogInstaller: Installer
{
private EventLogInstaller myEventLogInstaller;
public MyEventLogInstaller()
{
// Create an instance of an EventLogInstaller.
myEventLogInstaller = new EventLogInstaller();
// Set the source name of the event log.
myEventLogInstaller.Source = "NewLogSource";
// Set the event log that the source writes entries to.
myEventLogInstaller.Log = "MyNewLog";
// Add myEventLogInstaller to the Installer collection.
Installers.Add(myEventLogInstaller);
}
}
When you have your assembly compiled, you may use the InstallUtil tool available through the Visual Studio Command Prompt to run the installer code.
Regarding the message definition file (which includes category definitions), the MSDN documentation for EventLogInstaller.MessageResourceFile mentions that you should create an .mc file, compile it, and add it as a resource to your assembly. Digging around, I found an excellent post which should guide you to the end, here:
C# with .NET - Event Logging (Wayback Machine)