
Tuesday, November 30, 2004
We use log4net 1.2 beta 8 in our web applications for tracing and debugging purposes. Log4Net is great, but as with any community project, you always wish it had built in functionality to do a few extra things. In our case, I wanted to be able to log all statements to a file in a folder that exists off the web root. By default, log4net will log to a file relative to the virtual directory where the application that is logging is defined. For example, the following log4net configuration will log to a file named TmsLog.txt in the same folder as the web.config that this entry exists in:
156 <log4net>
157 <appender name="TMSAppender" type="log4net.Appender.FileAppender,log4net">
158 <param name="File" value="TmsLog.txt" />
159 <param name="AppendToFile" value="true" />
160 <layout type="log4net.Layout.PatternLayout,log4net">
161 <param name="ConversionPattern" value="%d %-5p - %m%n" />
162 </layout>
163 </appender>
164 <root>
165 <priority value="DEBUG" />
166 <appender-ref ref="TMSAppender" />
167 </root>
168 </log4net>
I wanted to be able to specify the file location at run time, which lead me into a few traps.
A coworker pointed out that I could simply use relative paths in the configuration file:
158 <param name="File" value="..\\LogLocation\\TmsLog.txt" />
This is ok, but our Dev, QA, and Production environments are all a little different. We'd written a utility object that uses some logic to determine the current execution environment and return the correct folder to use for logging purposes. What I really wanted was to be able to use the information provided by this library to change the logging folder at runtime. One option was to create the configuration programmatically, but I wanted to stay with the basic DomConfigurator.Configure() solution.
The first programmatic solution that I tried was to change the file path at runtime. The following code does allow me to change the path of the configured path, but for some reason it didn't 'stick', and the path used in the configuration file was used instead:
22 ILog myLog=LogManager.GetLogger(typeof(Logger));
23 ILoggerRepository aRepos = myLog.Logger.Repository;
24 foreach(IAppender anAppender in ((log4net.Repository.Hierarchy.Hierarchy)aRepos).Root.Appenders)
25 if (anAppender is FileAppender)
26 ((FileAppender)anAppender).File = @"c:\temp\test.txt";
27 return myLog;
My shorthand approach to this was the following:
28 public static string LogFile
29 { 30 get{ return ((FileAppender)((log4net.Repository.Hierarchy.Hierarchy)LogManager.GetLoggerRepository()).Root.Appenders[0]).File;} 31 set{ ((FileAppender)((log4net.Repository.Hierarchy.Hierarchy)LogManager.GetLoggerRepository()).Root.Appenders[0]).File = value;} 32 }
Again, no dice. Finally, after a little digging, I came across an email on the log4Net discussion list that pointed me in the right direction. I created my own FileAppender class that derived from the log4net FileAppender, and overwrote the File property:
76 public class InsarioWebLogFileAppender : log4net.Appender.FileAppender
77 { 78 public override string File
79 { 80 get{return base.File;} 81 set{base.File = Web.WebEnvironment.LogDirectory + value;} 82 }
83 }
Originally I tried to store the file setting in a local variable and return it in the getter but that didn't work, probably due to caching and optimization techniques used in the internals of log4net. Instead, I had to set the property on the base entity, as you see above.

Wednesday, November 17, 2004
I just wrote an article that covers my experience building a plugin for UnleashIt this week. UnleashIt is a sweet utility that belongs in the toolbox of any .Net developer who is in charge of releasing updates. The new ability for the control to support plugins only adds to its value. Anyway, have a look at my article if you're interested.

Tuesday, October 26, 2004
I was trying to implement log4net on a computer that didn't have the SDK installed recently and couldn't find information on how to format the output log on the web. This data comes from the log4net SDK - I figured I'd blog it for someone's future benefit...
| Conversion Character |
Effect |
| a |
Used to output the frienly name of the AppDomain where the logging event was generated. |
| c |
Used to output the logger of the logging event. The logger conversion specifier can be optionally followed by precision specifier, that is a decimal constant in brackets.
If a precision specifier is given, then only the corresponding number of right most components of the logger name will be printed. By default the logger name is printed in full.
For example, for the logger name "a.b.c" the pattern %c{2} will output "b.c". |
| C |
Used to output the fully qualified class name of the caller issuing the logging request. This conversion specifier can be optionally followed by precision specifier, that is a decimal constant in brackets.
If a precision specifier is given, then only the corresponding number of right most components of the class name will be printed. By default the class name is output in fully qualified form.
For example, for the class name "log4net.Layout.PatternLayout", the pattern %C{1} will output "PatternLayout".
WARNING Generating the caller class information is slow. Thus, it's use should be avoided unless execution speed is not an issue. |
| d |
Used to output the date of the logging event. The date conversion specifier may be followed by a date format specifier enclosed between braces. For example, %d{HH:mm:ss,fff} or %d{dd MMM yyyy HH:mm:ss,fff}. If no date format specifier is given then ISO8601 format is assumed (ISO8601DateFormatter).
The date format specifier admits the same syntax as the time pattern string of the ToString.
For better results it is recommended to use the log4net date formatters. These can be specified using one of the strings "ABSOLUTE", "DATE" and "ISO8601" for specifying AbsoluteTimeDateFormatter, and respectively ISO8601DateFormatter. For example, %d{ISO8601} or %d{ABSOLUTE}.
These dedicated date formatters perform significantly better than ToString. |
| F |
Used to output the file name where the logging request was issued.
WARNING Generating caller location information is extremely slow. It's use should be avoided unless execution speed is not an issue. |
| l |
Used to output location information of the caller which generated the logging event.
The location information depends on the CLI implementation but usually consists of the fully qualified name of the calling method followed by the callers source the file name and line number between parentheses.
The location information can be very useful. However, it's generation is extremely slow. It's use should be avoided unless execution speed is not an issue. |
| L |
Used to output the line number from where the logging request was issued.
WARNING Generating caller location information is extremely slow. It's use should be avoided unless execution speed is not an issue. |
| m |
Used to output the application supplied message associated with the logging event. |
| M |
Used to output the method name where the logging request was issued.
WARNING Generating caller location information is extremely slow. It's use should be avoided unless execution speed is not an issue. |
| n |
Outputs the platform dependent line separator character or characters.
This conversion character offers practically the same performance as using non-portable line separator strings such as "\n", or "\r\n". Thus, it is the preferred way of specifying a line separator. |
| p |
Used to output the level of the logging event. |
| P |
Used to output the an event specific property. The key to lookup must be specified within braces and directly following the pattern specifier, e.g. %X{user} would include the value from the property that is keyed by the string 'user'. Each property value that is to be included in the log must be specified separately. Properties are added to events by loggers or appenders. By default no properties are defined. |
| r |
Used to output the number of milliseconds elapsed since the start of the application until the creation of the logging event. |
| t |
Used to output the name of the thread that generated the logging event. Uses the thread number if no name is available. |
| u |
Used to output the user name for the currently active user (Principal.Identity.Name).
WARNING Generating caller information is extremely slow. It's use should be avoided unless execution speed is not an issue. |
| W |
Used to output the WindowsIdentity for the currently active user.
WARNING Generating caller WindowsIdentity information is extremely slow. It's use should be avoided unless execution speed is not an issue. |
| x |
Used to output the NDC (nested diagnostic context) associated with the thread that generated the logging event. |
| X |
Used to output the MDC (mapped diagnostic context) associated with the thread that generated the logging event. The key to lookup must be specified within braces and directly following the pattern specifier, e.g. %X{user} would include the value from the MDC that is keyed by the string 'user'. Each MDC value that is to be included in the log must be specified separately. |
| % |
The sequence %% outputs a single percent sign. |

Tuesday, September 07, 2004
I spent some time this weekend on a personal project, trying to fit a web site template that I purchased from TemplateMonster.com into a DotNetNuke portal. The template that I was trying to adapt appeared to me to be a good candidate for a portal-type framework, and an opportunity to try out some of the portal and container skinning features.
The first thing I did was read the skinning documentation that came with DotNetNuke. It's decent documentation, but the problem is that it touches on so many items that it tends to leave a number of unanswered questions about specifics. I hacked around the generated HTML for a portal page and eventually came up with a better understanding of a few things:
- Don't include references to your style-sheets in your skin and container skin pages. I included these references while building the individual HTML files, and ended up leaving them in during publish. This isn't necessary. as DotNetNuke will put the css files in known locations and include {link} tags that make reference to them in the appropriate locations. Including a style-sheet reference in your container template will result in a style-sheet reference per container on your rendered portal page, which screws up the by-design hierarchy of styles.
- Don't include {body} or {head} references in your skin HTML files. Again, I had inserted them during the testing phase of my HTML build out. Leaving them in resulted in multiple {body} tags in the rendered portal page. You can (should) achieve the same results that you seek from this type of approach if you:
- Learn the built in CSS entities. The portal and containers render with pre-determined class names in various places. You must learn what they are in order to accurately build out your site. Rather than combing through the rendered HTML for class references that are not your own, instead refer to {install point}\Portals\_default\portal.css and {install point}\Portals\_default\Skins\DNN\skin.css. Some notes:
- Each portal page renders the style-sheets in the following order:
- {install point}\Portals\_default\default.css - the default install template. If you're trying to figure out there are still dark backgrounds on your portal, it's probably because you didn't override a property in this style-sheet.
- {install point}\Portals\{active portal}\skins{\active skin}\skin.css. This is the skin.css that you uploaded with your skin package
- {install point}\Portals\{active portal}\containers\{active portal container}\container.css. This is the style-sheet of the default container assigned to the portal
- {install point}\Portals\{active portal}\portal.css - Set n portal admin / Site Settings from an available skin's style-sheet
- The style that is defined closest to the bottom of the list above will be the style that renders. For instance, if you define .Head (the style for the header bar on a container) in your container or skin, it will override the default setting that is defined in the default.css file.
- The SolPartMenu global menu has a large number of settings that you can override in the .XML file that accompanies your skin or container package. That process is a pain - the quick approach for simply trying to get it to look right is to override the Menu settings in your skin css file. Refer to the css class names at the bottom of the {install point}\Portals\_default\Skins\DNN\skin.css file
- My approach was to upload the skin and container, then open the four files referenced above and edit/save/refresh to see how the site was affected by css tweaks. When I got to a point where I felt comfortable with the rendered page, I'd update the container package with the new css file and re-upload it.
- In order to help target specific page locations, I assigned IDs to some of the table rows in my container HTML file. This let me do things like define a style for a generic HTML entity such as a within a specific page area, like one named #GreenContainerContent
- Handle the spacing between your portal elements at the skin level. One-off nudging of space between containers with borders on the container.
All in all the framework that supports skinning is pretty good. I think I'll post some more in the near future about how the styles on the SolPartMenu work.

Friday, September 03, 2004
I spent some time this afternoon publishing this install of DotText to our production hosting environment. It all went fairly well, but I experienced a couple of gotchas that I want to document for future reference. In hindsight, these seem like simple solutions, but it's usually the simple problems that end up taking the longest to solve, because in the back of my mind, I know they should be easy to solve... Anyway:
We host with Intermedia.net, a fairly decent provider who has served us well over the years. We have a shared hosting plan on a Windows 2003 box, along with a number of databases on a shared SQL server. Publishing .Net apps to their servers, however, has always been a PITA. After mucking with it time and time again, I finally came up with a way to publish sub-applications underneath our main virtual directory.
Converting from Single to Multiple Blogs
To give our employees the option of hosting their own blogs, I decided to go with the MultipleBlogConfig option. As you can see from the URL of this Weblog, I installed the DotText application on a virtual called blog. My first foray into DotText (having a non-alpha character in the name of the app (.Text) is kind of annoying, no?) was with the source code for version 0.95. I fired up the solution in Visual Studio, and began reading through the code and the DotTextWiki (which is a little weak on content). My first problem was that my initial trials with DotText were on localhost with a SingleBlogConfig. I grabbed the web.config sample Single2_Web config. When it came to publish to production, I had to convert from a single blog to multiple blog. Looking through the documentation, I made a few changes, including changing the BlogConfigurationSettings, Connection string, email settings, and Aggregate settings (thanks to Chris's post for some highlights). When I published to production and the jfk blog served up a blank page, I spent some time figuring out that the MultipleBlogConfig settings have different HttpHandler patterns defined. If you are going through a conversion from a single blog on a virtual to a multiple blog on a virtual, copy that section from Multiple2_Web.config over.
Problems with the admin area when deploying .Text
The next problem I had came from the way that I deployed the application to production. I used VS.Net to do a copy web to our production servers. When I submitted my login info to the site, I kept getting errors in DotText that indicated that the app couldn't find the requested page. /blog/jfk/login.aspx was directing me to /blog/jfk/admin/default.aspx. After FTPing to the prod server, I saw that there was no /admin folder under /blog or /blog/jfk, and the DotText.Admin.Web was also missing form the /bin directory.
It turns out that the DotText VS.Net project is setup with the /admin subdirectory as a class library in a separate project, which points to an excluded directory under the DotTextWeb folder. Because of this, VS.Net copy web doesn't work properly - you have to manually FTP out the aspx pages and the admin assembly underneath your virtual (/blog/admin, in my case) in order to get the admin area to work. Maybe this is documented somewhere, but I couldn't find it.
Anyway, now that this thing is installed and working (we'll see what happens when I submit this), I'm looking forward to capturing my discoveries in the world of .Net, and documenting the cool stuff I run across. Sorry, but I'll probably also be blogging politics, especially as the November election draws closer.