Logging With Log4Net

Jumping back to a bit of .Net development this week.

As techniques go, logging is one of the simplest and most useful in a developer's toolbox. Once your code is out and running in the wild, it becomes a lot harder to track down issues without being able to jump into the debugger. Logging to the rescue!

There are a number of logger implementations for the .Net Framework, including nLog, Microsoft's Enterprise Library, and my personal choice, log4net.

log4net is a very flexible logging library that provides the ability to log to a number of different outputs, which can be changed easily through configuration files, even at run time.

Setting Up

In your AssemblyInfo.cs add the following to have the config picked up:
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "Log4Net.config", Watch = true)]

Specifying the watch option will cause log4net to monitor the configuration file for changes. This is fantastically useful, allowing you to change the logging level dynamically without needing to restart your application.

Speaking of configuration, add a file named log4net.config to the root of your project:







This specifies a logger that will output to a file in the logs directory, along with a default logger that will log anything above an info message.

Start Logging

Once that's done, start logging!
ILog logger = log4net.LogManager.GetLogger("Logger");
logger.Info("Testing logging");

Bonus: NHibernate

NHibernate has built in support for log4net which can be really helpful when trying to debug issues.

To enable logging for NHibernate, all you need to do is add the following loggers to the log4net.config file:






Alter the level value to change how much detail you get. Info will get you a lot of detail on queries being run, whilst Warn or Error will limit output to just those messages that are fired off when things are going wrong.

*Update*

I've just been bashing my head against the wall trying to get some logging out of NHibernate, whilst not actually logging anything else.

It seems that because I wasn't instantiating a logger anywhere in code, this was preventing anything being logged by NHibernate.

All I ended up doing was placing the following in my MVCApplication class:
private static ILog logger = log4net.LogManager.GetLogger("default");

*Another Update*

Just for my own sanity so that I don't forget again, you will also need to give write access to the log folder for the user that will be writing there. In the case of ASP .Net MVC, this will be the user setup in the app pool, which is usually something like "IIS AppPool\DefaultAppPool" or "IIS AppPool\ASP.Net v4.0".

Vim and Linux C++ Development

My workday generally revolves around .Net and the Microsoft stack, so for a change I thought it would be interesting to do some development under Linux. Finding a suitable text editor was the first task, but it didn't take me long to settle on Vim, an incredibly flexible and popular text editor, available for most platforms. This post looks at working with Vim for C++ development under Linux.

Vim is very customisable, and a lot of tweaks can be made in the per user Vim.rc file, usually found in your home directory, so let's start there.

Vim.rc Tweaks

We can makes some tweaks to Vim.rc to make working with C++ a little more pleasant by dealing with indentation and tabs in a more sensible way.

Firstly, let's set up the auto-indentation:
set autoindent
set cindent

I prefer Visual Studio style tab settings of a 4 stop width and converting to spaces:
set softtabstop=4 shiftwidth=4 expandtab

Navigation

Coming from a Visual Studio background, I'm used to working with a full IDE and all it brings, including file navigation through a tree menu as part of the editor.

Vim is a world apart from Visual Studio, so despite the Project type plugins available for Vim, I'm trying to stay away from GUI like treeviews. CTags builds a list of all symbols in your project to enable quick navigation to function definitions, calls, methods etc. Ubuntu 10.10 doesn't seem to include it as part of a standard install, so start with:
sudo apt-get install ctags

Then run it against your code directory; using -R will run it recursively:
ctags -R *

Now you can use the following commands to leap around your source with glee:

Ctrl + ]Jump to selected symbol
Ctrl + OMove back in history
Ctrl + IMove forward in history

Note: in order for vim to find the tags file, it needs to be launched from the directory in which the file was generated.

Neat Commands

Learning Vim is almost like learning an instrument, commands can be combined in a way that makes text editing almost fun. There are a number of good introductions to Vim's commands, but here are a few that I find to be particularly useful:

gg=GReindent entire file
w / bNext Word / Prev Word
^ / $Start / End of line
cwChange word
viwcSelect inner word, change
😡Save changes and quit
bn / bpNext buffer / Prev buffer
bdClose buffer

Down The Rabbit Hole

This barely scratches the surface of what you can do with Vim, I haven't even looked at what you can do with buffers, tabs or any of the multitude of plugins.

So go get stuck in and see what works for you.

Getting Started with MythTV Plugin Development

I'm a big fan of MythTV, having used it for years as a very comprehensive media centre.

MythTV also provides an excellent extension framework, which is a good excuse as any to have a bash at some development under Linux for a change.

This post starts at the beginning: getting a hello world plugin compiled and running. This is based heavily on the HelloMyth page on the MythTV Wiki, with tweaks needed for a vanilla MythTV 0.23 installation under Ubuntu 10.10.

Prerequisites

Assuming that you have a working MythTV installation, you will also need the following packages:

sudo apt-get install libmyth-dev qt4-qmake build-essential ccache libqt4-dev libfreetype6-dev libmp3lame-dev libasound2-dev libjack-dev libraw1394-dev libiec61883-dev libavc1394-dev libxv-dev libxvmc-dev

Once those are installed, grab a copy of the standard MythTV plugins from the myth git repository; note that you'll want to work with the branch of MythTV you have installed (unless you're working with the trunk version).

git clone -b fixes/0.23 git://github.com/MythTV/mythtv.git

Generate a config file by running the configure script in the mythplugins directory (You may or may not need the prefix argument, this is based on an Ubuntu 10.10 install):

./configure --prefix=/usr

Code

In the mythplugins folder, create a mythhello folder and add the following file:

mythhello/mythhello.pro
TEMPLATE = subdirs

# Directories
SUBDIRS = mythhello

Now inside the mythhello folder, create another mythhello folder, and add the following files:

mythhello/mythhello/mythhello.pro
include ( ../../mythconfig.mak )
include ( ../../settings.pro )
include ( ../../programs-libs.pro )

QT += network sql xml
TEMPLATE = lib
CONFIG += plugin thread
TARGET = mythhello
target.path = $${LIBDIR}/mythtv/plugins
INSTALLS += target

uifiles.path = $${PREFIX}/share/mythtv/themes/default
uifiles.files = hello-ui.xml
installfiles.path = $${PREFIX}/share/mythtv
installfiles.files = hello-ui.xml

INSTALLS += uifiles

# Input
HEADERS += mythhello.h
SOURCES += main.cpp mythhello.cpp

macx {
QMAKE_LFLAGS += -flat_namespace -undefined suppress
}

mythhello/mythhello/main.cpp (Entry point for the plugin)
// C++ headers
#include

// QT headers
#include

// MythTV headers
#include
#include
#include
#include
#include

// MythHello headers
#include "mythhello.h"

using namespace std;

void runHello(void);
int RunHello(void);

void setupKeys(void)
{
REG_JUMP("MythHello", QT_TRANSLATE_NOOP("MythHello",
"Sample plugin"), "", runHello);
}

int mythplugin_init(const char *libversion)
{
if (!gContext->TestPopupVersion("mythhello",
libversion,
MYTH_BINARY_VERSION))
return -1;
setupKeys();
return 0;
}

void runHello(void)
{
RunHello();
}

int RunHello(void)
{
MythScreenStack *mainStack = GetMythMainWindow()->GetMainStack();

MythHello *mythhello = new MythHello(mainStack, "hello");

if (mythhello->Create())
{
mainStack->AddScreen(mythhello);
return 0;
}
else
{
delete mythhello;
return -1;
}
}

int mythplugin_run(void)
{
return RunHello();
}

int mythplugin_config(void)
{

}

mythhello/mythhello/mythhello.h (Plugin header)
#ifndef MYTHHELLO_H
#define MYTHHELLO_H

// MythTV headers

#include
#include

/** \class MythHello
* \brief Example plugin. Shows how to use Text areas and buttons
*/
class MythHello : public MythScreenType
{
Q_OBJECT

public:
MythHello(MythScreenStack *parent, QString name);
bool Create(void);
bool keyPressEvent(QKeyEvent *);

private:
MythUIText *m_titleText;
MythUIText *m_outText;
MythUIButton *m_cancelButton;
MythUIButton *m_okButton;

private slots:
void ok_clicked(void);
void cancel_clicked(void);
};

#endif /* MYTHHELLO_H */

mythhello/mythhello/mythhello.cpp (Main plugin body)
// POSIX headers
#include

// MythTV headers
#include
#include
#include
#include
#include

// MythHello headers
#include "mythhello.h"

#define LOC QString("MythHello: ")
#define LOC_WARN QString("MythHello, Warning: ")
#define LOC_ERR QString("MythHello, Error: ")

/** \brief Creates a new MythHello Screen
* \param parent Pointer to the screen stack
* \param name The name of the window
*/
MythHello::MythHello(MythScreenStack *parent, QString name) :
MythScreenType(parent, name),
m_cancelButton(NULL)

{
//example of how to find the configuration dir currently used.
QString confdir = GetConfDir();
VERBOSE(VB_IMPORTANT, LOC + "Conf dir:" + confdir);
}

bool MythHello::Create(void)
{
bool foundtheme = false;

// Load the theme for this screen
foundtheme = LoadWindowFromXML("hello-ui.xml", "hello", this);

if (!foundtheme)
return false;

bool err = false;
UIUtilE::Assign(this, m_titleText, "title", &err);
UIUtilE::Assign(this, m_outText, "outtext", &err);
UIUtilE::Assign(this, m_cancelButton, "cancel", &err);
UIUtilE::Assign(this, m_okButton, "ok", &err);

if (err)
{
VERBOSE(VB_IMPORTANT, "Cannot load screen 'hello'");
return false;
}

BuildFocusList();
SetFocusWidget(m_cancelButton);

connect(m_okButton, SIGNAL(Clicked()), this, SLOT(ok_clicked()));
connect(m_cancelButton, SIGNAL(Clicked()), this, SLOT(cancel_clicked()));

return true;
}
bool MythHello::keyPressEvent(QKeyEvent *event)
{
if (GetFocusWidget() && GetFocusWidget()->keyPressEvent(event))
return true;

bool handled = false;
QStringList actions;
handled = GetMythMainWindow()->TranslateKeyPress("Hello", event, actions);

for (int i = 0; i < actions.size() && !handled; i++) { QString action = actions[i]; handled = true; if (action == "ESCAPE") Close(); else handled = false; } if (!handled && MythScreenType::keyPressEvent(event)) handled = true; return handled; } void MythHello::ok_clicked(void) { m_outText->SetText(QString("OK clicked"));
}

void MythHello::cancel_clicked(void)
{
m_outText->SetText(QString("Cancel clicked"));
}

mythhello/mythhello/hello-ui.xml (Plugin UI definition)





Building

Change to mythplugins/mythhello and run the following:

qmake-qt4
make
sudo make install

Everything should be compiled and the plugin copied to /usr/lib/mythtv/plugins/.

To get the plugin to show up in the main menu edit /usr/share/mythtv/themes/defaultmenu/mainmenu.xml and add a new button entry:

On starting mythfrontend at this point, the plugin should show up on the main menu.

Summary

At this point we have a working MythTV plugin which will serve as a good starting point for building further functionality.

Useful Links

MythTV Wiki HelloMyth Plugin
MythTV SVN

Lambda Expressions in T4 Templates

Despite being quite well hidden, Visual Studio's T4 templating engine is a great feature.

However, whilst putting together a T4 template in Visual Studio 2008, throwing in a simple lambda expression such as:

<#=string.Join(",", aList.ConvertAll(field => field.Name).ToArray())#>

Can cause the code generation to fail. As it turns out, the T4 template language is set to C# 2.0 by default.

This can easily be fixed by changing the template directive at the head of the template file to C#v3.5:

<#@ template language="C#v3.5" hostspecific="True" #>

Ideally this would be the default in a C# 3.5 project, but I'm sure there's a reason for this...

Visual Inheritance With .NET Compact Framework

The idea

Recently I've been working on an MVC framework for the .Net compact framework, partly to aid the re-use of certain visual components, such as signature capture screens etc.

As visual inheritance has been supported for the compact framework from VS2005 this should make it very easy to provide a nice base implementation that can then be customised as required.

To this end I created a simple base frame to handle some common functionality and a number of input frames to handle different tasks.

The Problem

At this point the input frames had text overlaid on them in design mode:

Visual inheritance is currently disabled because the base class references a device-specific component or contains p/invoke

Annoying, as I wanted the design time support of being able to visually edit the frames as required.

The Solution

Initial research turned up an MSDN article that discussed this issue and mentioned a number of causes including P/Invokes and device specific code.

The article also states:

If you are sure that platform invoke will not be executed during design time, you can safely enable visual inheritance by putting a DesktopCompatible(true) attribute on the parent form or the parent user control.

This attribute can be applied in a DesignTimeAttributes.xmta file (added through the add file to solution dialog):



true

And...it still failed. 10 more minutes off hair pulling and I realised that fraLogin inherited from BaseFrame, so I added the attribute to that class too:


true

And now visual inhertance works!

Summary

So in short, if you want visual inheritance when developing with the compact framework, remember to add the DesktopCompatible attribute to any controls that will be derived from.

Visual Studio 2003 And Find In Files Under Vista

It's probably widely known by now that Microsoft do not support Visual Studio 2003 under Windows Vista (and 7).

I still have to support a legacy application that was produced with VS2003, so decided to give it a go anyway. To my surprise everthing in the winforms application that I needed basically worked, including debugging, intellsense and so on.

Everything that is until I tried to use 'Find in Files', which promptly crashed Visual Studio.

The Fix

Until that happened, I didn't realise that I use this feature so much, but there is a relatively straight forward fix; by editing the compatibility settings of Visual Studio 2003 and adjusting the following:

  • Run program in compatibility mode for XP SP2
  • Disable Visual Themes
  • Disable Desktop Composition

Alternatively you could go use Visual Basic 6, which is supported by Microsoft under Vista.