LoggingAppBlock and reconfiguration at runtime - doesn't work?

Topics: Logging Application Block
Mar 30, 2007 at 8:35 PM
Hello, everyone!

First of all a nod to 'patterns and practices' team, who developed such an interesting tool for us to use and tinker with! :)
I'm new here, so might not be aware about possible solutions already found - so just point me in the right direction.

That said, here's my puzzle:

I'm working on a logging component for a client/server application written in C# .NET 2.0 (VS2005). We decided to utilize EntLib 2.0's LAB to provide the underlying mechanics based on high flexibility of configuration options and other features. The client applications are deployed via ClickOnce, and are subject to certain restrictions, one being we can't write (save) any information to local computers (.NET internal operations don't count). Design requirements are that every instance of the client app should have logging configurable independently (e.g. when we're trying to trace a particular issue appearing only on specific workstations we don't want to fill logging DB with entries from the rest of the park). Also we need to find a solution that would not ask administrators to rummage through each machine's hard disk in search of .config files (because ClickOnce places all app-files into unguessable locations). Thus, main app.config must be identical from one workstation to the next (and even if it isn't - ClickOnce will overwrite it at least with next update, so no point trying to use them). Also nature of the system is that applications' lifetime is fairly long (days and more), so I can't rely on a simple 'reload on start' rule; we need to be able to modify configuration while the apps are running.

My understanding is that EntLib primarily works with configuration presented as a physical file. So far I have successfully implemented the following approach. At startup the app would read an XML-string from a DB-table (based on workstation name - thus tied to specific machine) and save it to a network file, whose name is defined in app.config in <enterpriseLibrary.ConfigurationSource> section like this: filePath="\\SERVER\...\%COMPUTERNAME%.config". This definition also takes care of telling EntLib to look for its configuration in the external file, thus I achieve both objectives with one shot! Of course the initialization (reading XML from DB and saving it to the file) must happen prior to the first call into EntLib, but that's a simple thing to control. In order for LAB to work with environment variables I've made a very small change in FileConfigurationSource.RootConfigurationFilePath( string ) to add a call to Environment.ExpandEnvironmentVariables( configurationFile ) - that did the trick.

I did not like the SqlConfiguration QuickStart - IMHO it's heavy, and data schema is clumsy (storing individual pieces in different tables and rows). I think my approach of saving entire XML string as a single entity is a lot cleaner and simpler (from using-app's stand point). I'll still need to think on "Configurator" UI implementation, but that's next step and it is fairly easier with a particular app in mind (not a generic case).

Now here's where I got stuck: I'm trying to verify that when we change configuration at runtime (in my case overwrite the .config file with new contents) the new settings are going to be picked up by the LAB. And the results are very inconclusive: yesterday I thought that a category filter did work a few times, but to the end of the day I was not able to replicate it. Today I'm consistently seeing that LAB ignores priority filter (min=10, max=255, default LogEntry.Priority value is set to 0, entries are passed and written to logs .. what gives?) and I haven't tried to play with category filter yet. But I'm pretty sure it won't be much help, because .. priority filter is ignored even if I specify entire configuration in local app.config prior to starting the executable (no reading XML from DB, no saving to external .config file - just as a simplest example shows).

My thought is that whatever changes are applied to .config file should be picked up by LAB (polling interval is 15 sec, and that thread is working like heartbeat). But today I do not see any effect from modifying <logFilters> section. BTW what's the difference between LogEnabledFilter and loggingConfiguration.tracingEnabled attribute?

I traced the calls within LAB to ConfigurationChangeWatcher.OnConfigurationChanged( ) and BaseFileConfigurationSourceImplementation.ConfigSourceChanged( string ). The last seems to be the main dispatcher figuring what to update, but stepping from there through RefreshAndValidateSections(..) doesn't seem to involve any TraceListeners that are utilized. Could someone shed some light on how this part is supposed to work? Also the source code has a comment preceding ConfigSourceChanged(..) function with 'Rationale' but its logic seems to escape me. Again if someone understands how this should work and what the rationale means - I'd appreciate!

I guess some of the filters can be accessed programmatically, but I see two arguments against this:
1) so far I haven't found that many intuitive ways to affect current configuration (even something simple like Logger.EnableLogging( bool ) that would affect its .IsLoggingEnabled property and appropriate behavior inside the class), and
2) if LAB is meant to work with its .config file why should I reinvent this process by trying to parse pieces and fire off individual objects? - I'd rather modify the file and let LAB do its job.

I have found several articles (many by David Hayden) with examples of "configuration file-less" use, but am confused: ok, so we declare all these subclasses, and instantiate a few of them e.g. in our Main( ) function - how do we pass these objects to LAB? I don't see any lines saying smth like Logger.AddTraceListener( myTraceListener ), etc. How does LAB figure out that I mean it to use myTraceListener? Does it happen simply by the "effect of birth" - when I instantiate a subclass of TraceListener it ties itself into LAB's internals? Sounds cool, but somehow I don't think it actually does that. At least when I tried to follow these examples nothing happened - no crashes, no exceptions, and .. no logging - new objects get created and that's all. Reckon I'm missing something, does anyone out there have an advice or explanation?

Hopefully I described everything, but in case a code sample or smth else can help you understand what I'm talking about - just ask. I certainly want to help you help me! :))

Thanks in advance,
Dmitri
Apr 17, 2007 at 4:37 PM
Edited Apr 17, 2007 at 9:36 PM

Astrogator wrote:
ok, so we declare all these subclasses, and instantiate a few of them e.g. in our Main( ) function - how do we pass these objects to LAB? I don't see any lines saying smth like Logger.AddTraceListener( myTraceListener ), etc. How does LAB figure out that I mean it to use myTraceListener? Does it happen simply by the "effect of birth" - when I instantiate a subclass of TraceListener it ties itself into LAB's internals? Sounds cool, but somehow I don't think it actually does that.


I was bummed by the absence of simple Logger.AddTraceListener( myTraceListener ), too :(

The answer seems to be that, if you want to use the default Logger class, you are not supposed to instantiate CustomTraceListeners yourself - Logging block will do that. All you need to do is to change app.config so it points to the assembly containing that CustomTraceListener of yours. Unfortunately that is all you can do with Logger, it seems. I dont see any easy way to attach CustomTraceListeners to it at runtime.

You can create and use custom trace listeners at runtime, but you have to make your own LogWriter for that.