Designing a Configurable Logging framework using Observer Design Pattern

Almost in all applications we need logging functionality, but requirements varies from project to project. Here we have designing a Configurable Logging framework. First of all, lets have a look at full requirements.

Functional Requirements:

It should provide,

  • A configurable option for other application modules to save logs at more than one platform like, on Console, in txt files or on network etc.
  • A Facility to Log messages in different categories like, ERROR, WARNING, GENERAL_MESSAGES and also provision to control each category independently.
  • A Facility to configure & bind category and Logging platform at run time i.e. user will be able to specify at runtime that,
    • Messages of any particular category should be logged or not etc.
    • Messages of any particular category like ERROR should be logged in error.txt and remaining categories on console only etc.

[showads ad=inside_post]
Now Lets start Designing,

High Level Design

logger

Mainly two components exists in it,

1. Logger

Its an interface layer between application and actual logging platforms.

Responsibilities:

  • Responsible for receiving different type of log messages like ERROR, WARNING & GENERAL etc from application.
  • Manages a registry map of Logging Platforms based on message types.
  • Provides a mechanism to attach and de-attach Logging Platforms with different message types at runtime.
  • On Receiving message from application, fetches the type of message and then forwards the message to the Logging Platforms registered with that message type.

   2. Logging Platform

It actually logs the message in its platform. There can be multiple logging platform.

Like,

Console Platform:   It prints the message on console.

Flat File Platform:   It writes the message in attached txt file.

Network Platform:   It forwards the message on network.

This is a perfect example of Observer Design Pattern.

Intent of Observer Design Pattern:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Here, Logger is SUBJECT and Logging Platforms are OBSERVERS. It decouples the sender and receivers i.e it decouples the different Application modules and actual logging platforms.

Logging Platforms registers itself with the Subject (i.e. Logger) on the basis of message types and when Logger (i.e. Subject) receives any message it notifies the Platforms registered with that message type by forwarding the message. Then those platforms take action on that message.

Low Level Design:

Class Diagram,

logger_class_diagram

Declaring a Message Type Enum to differentiate between messages.

typedef enum MessageType
{
	ERROR,
	WARNING,
	GENERAL,
}MessageType;

Designing and implementing the the Subject class.

It maintains a map of event Ids and list of Observers to keep of track of the Observers registered the any event Id.
It also provides the APIs to attach, deattach observers with event Ids and also a notification API that updates the registered Observers with triggering event Id.

Declaration of Subject class is as follows,

class Subject
{
	std::map<int , std::vector<Observer *> > registryMap;
	std::string data;
public:
	std::string getData() 	{ return data; }
	void setData(std::string value) { data = value; }
	void attach( int event, Observer * pObsrvr);
	void deattach( Observer * pObsrvr);
	void notify(int event);
};

API to attach Observers,

void Subject::attach( int event, Observer * pObsrvr)
{
	registryMap[event].push_back(pObsrvr);
}

API to deattach Observers,

void Subject::deattach( Observer * pObsrvr)
{
	for (std::map<int , std::vector<Observer *> >::iterator it=registryMap.begin(); it!=registryMap.end(); ++it)
	{
		std::vector<Observer *>::iterator itV = std::find(it->second.begin(), it->second.end(), pObsrvr);
		if(itV != it->second.end())
		{
			it->second.erase(itV);
			continue;
		}
	}
}

API to notify registered Observers,

void Subject::notify(int event)
{
	std::map<int , std::vector<Observer *> >::iterator it = registryMap.find(event);
	if(it != registryMap.end())
	{
		for(std::vector<Observer *>::iterator itV = it->second.begin(); itV != it->second.end(); itV++)
		{
			(*itV)->update(this);
		}
	}
}

Designing Logger Class :
It inherits the Subject Class and also provide APIs to enable and disable logging for any particular message type,

class Logger : public Subject
{
	std::map<MessageType, int> messageStatusMap;
public:
	Logger()
	{
		messageStatusMap[ERROR] = true;
		messageStatusMap[WARNING] = true;
		messageStatusMap[GENERAL] = true;
	}
	void writeLog(MessageType type, std::string message)
	{
		if(messageStatusMap[type])
		{
			setData(message);
			notify(type);
		}
	}
	void enableLoggingOfMessage(MessageType type)
	{
		messageStatusMap[type] = true;
	}
	void disableLoggingOfMessage(MessageType type)
	{
		messageStatusMap[type] = false;
	}
};

Designing Observer class,

It acts as a base class for different Logging Platform classes. It just defines an interface that all derived Logging Platform classes need to implement.

class Observer  // Logging Platform
{
public:
	virtual void update(Subject * pSubject) = 0;
	virtual ~Observer(){}

};

Designing different Logging Platform Classes,

Console Logging Platform:

class ConsoleLoggingPlatform : public Observer
{
	public:
	void update(Subject * pSubject)
	{
		std::cout<<pSubject->getData()<<std::endl;
	}
};

Flat File Logging Platform:

class FlatFileLoggingPlatform : public Observer
{
	std::string fileName;
public:
	FlatFileLoggingPlatform(std::string name) : fileName(name) {}
	void update(Subject * pSubject)
	{
		std::string data = pSubject->getData();
		// Add data to file
	}
};

Network Logging Platform:

class NetworkLoggingPlatform : public Observer
{
	public:
	void update(Subject * pSubject)
	{
		std::string data = pSubject->getData();
		// Send data in network
	}
};

Using Logger and different Logging Platfroms,

Creating a Logger and attaching different Logging Platforms with it,
i.e.

  • Console Logging Platform will attach itself with Logger for all type of messages i.e. ERROR, WARNING, GENERAl.
  • Flat File Logging Platform will attach itself with Logger for ERROR type of messages.
  • Network Logging Platform will attach itself with Logger for GENERAL type of messages.
	Logger * pLogger = new Logger();
	ConsoleLoggingPlatform * pConsolePlatform = new ConsoleLoggingPlatform();
	FlatFileLoggingPlatform * pFilePlatform = new FlatFileLoggingPlatform("temp");
	NetworkLoggingPlatform * pNetworkPlatform = new NetworkLoggingPlatform();

	pLogger->attach(ERROR, pConsolePlatform);
	pLogger->attach(WARNING, pConsolePlatform);
	pLogger->attach(GENERAL, pConsolePlatform);
	pLogger->attach(ERROR, pFilePlatform);
	pLogger->attach(GENERAL, pNetworkPlatform);

Logs writing by other application modules,

pLogger->writeLog(ERROR, "This is error");
pLogger->writeLog(WARNING, "This is warning");
pLogger->writeLog(GENERAL, "This is general message");

Disabling the logging of ERROR type of messages,

pLogger->disableLoggingOfMessage(ERROR);

De-attaching the Console Logging Platform,

pLogger->deattach(pConsolePlatform);

I hope you like the article.

Complete compiling source code is as here,

[code language=”css” collapse=”true”]

#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
#include <iterator>
#include <functional>

typedef enum MessageType
{
ERROR,
WARNING,
GENERAL,
}MessageType;

class Subject;

class Observer // Logging Platform
{
public:
virtual void update(Subject * pSubject) = 0;
virtual ~Observer(){}

};

class Subject
{
std::map<int , std::vector<Observer *> > registryMap;
std::string data;
public:
std::string getData() { return data; }
void setData(std::string value) { data = value; }
void attach( int event, Observer * pObsrvr);
void deattach( Observer * pObsrvr);
void notify(int event);
};

void Subject::attach( int event, Observer * pObsrvr)
{
registryMap[event].push_back(pObsrvr);
}
void Subject::deattach( Observer * pObsrvr)
{
for (std::map<int , std::vector<Observer *> >::iterator it=registryMap.begin(); it!=registryMap.end(); ++it)
{
std::vector<Observer *>::iterator itV = std::find(it->second.begin(), it->second.end(), pObsrvr);
if(itV != it->second.end())
{
it->second.erase(itV);
continue;
}
}
}
void Subject::notify(int event)
{
std::map<int , std::vector<Observer *> >::iterator it = registryMap.find(event);
if(it != registryMap.end())
{
for(std::vector<Observer *>::iterator itV = it->second.begin(); itV != it->second.end(); itV++)
{
(*itV)->update(this);
}
}
}

class Logger : public Subject
{
std::map<MessageType, int> messageStatusMap;
public:
Logger()
{
messageStatusMap[ERROR] = true;
messageStatusMap[WARNING] = true;
messageStatusMap[GENERAL] = true;
}
void writeLog(MessageType type, std::string message)
{
if(messageStatusMap[type])
{
setData(message);
notify(type);
}
}
void enableLoggingOfMessage(MessageType type)
{
messageStatusMap[type] = true;
}
void disableLoggingOfMessage(MessageType type)
{
messageStatusMap[type] = false;
}
};

class ConsoleLoggingPlatform : public Observer
{
public:
void update(Subject * pSubject)
{
std::cout<<pSubject->getData()<<std::endl;
}
};
class FlatFileLoggingPlatform : public Observer
{
std::string fileName;
public:
FlatFileLoggingPlatform(std::string name) : fileName(name) {}
void update(Subject * pSubject)
{
std::string data = pSubject->getData();
std::cout<<"Written in file "<<data<<std::endl;
// Add data to file
}
};
class NetworkLoggingPlatform : public Observer
{
public:
void update(Subject * pSubject)
{
std::string data = pSubject->getData();
std::cout<<"Sent on network "<<data<<std::endl;
// Send data in network
}
};

int main()
{
Logger * pLogger = new Logger();
ConsoleLoggingPlatform * pConsolePlatform = new ConsoleLoggingPlatform();
FlatFileLoggingPlatform * pFilePlatform = new FlatFileLoggingPlatform("temp");
NetworkLoggingPlatform * pNetworkPlatform = new NetworkLoggingPlatform();

pLogger->attach(ERROR, pConsolePlatform);
pLogger->attach(WARNING, pConsolePlatform);
pLogger->attach(GENERAL, pConsolePlatform);
pLogger->attach(ERROR, pFilePlatform);
pLogger->attach(GENERAL, pNetworkPlatform);

pLogger->writeLog(ERROR, "This is error");
pLogger->writeLog(WARNING, "This is warning");
pLogger->writeLog(GENERAL, "This is general message");

pLogger->disableLoggingOfMessage(ERROR);
pLogger->deattach(pConsolePlatform);

pLogger->writeLog(ERROR, "This is error 2");
pLogger->writeLog(GENERAL, "This is general message 2");

delete pNetworkPlatform;
delete pFilePlatform;
delete pConsolePlatform;
delete pLogger;

return 0;
}

[/code]

1 thought on “Designing a Configurable Logging framework using Observer Design Pattern”

  1. How do u manage the multithreading in your design? suppose two threads are writing to a single file system, what will be the effects and how its managed ?

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top