C++ – Best Creational Pattern for loggers in a multi-threaded system

cdesign-patternsloggingmultithreading

This is a follow up question on my past questions :
Concurrency pattern of logger in multithreaded application

As suggested by others, I am putting this question separately.

As the learning from the last question. In a multi-threaded environment, the logger should be made thread safe and probably asynchronous (where in messages are queued while a background thread does writing releasing the requesting object thread). The logger could be signleton or it can be a per-group logger which is a generalization of the above.

Now, the question that arise is how does logger should be assigned to the object? There are two options I can think of:

1. Object requesting for the logger:

Should each of the object call some global API such as get_logger()? Such an API returns "the" singleton or the group logger. However, I feel this involves assumption about the Application environment to implement the logger -which I think is some kind of coupling. If the same object needs to be used by other application – this new application also need to implement such a method.

2. Assign logger through some known API
The other alternative approach is to create a kind of virtual class which is implemented by application based on App's own structure and assign the object sometime in the constructor.

This is more generalized method. Unfortunately, when there are so many objects – and rather a tree of objects passing on the logger objects to each level is quite messy.

My question is there a better way to do this? If you need to pick any
one of the above, which approach is would you pick and why?

Other questions remain open about how to configure them:

  1. How do objects' names or ID are assigned so that will be used for printing on the log messages (as the module names)

  2. How do these objects find the appropriate properties (such as log levels, and other such parameters)

In the first approach, the central API needs to deal with all this varieties.
In the second approach – there needs to be additional work.

Hence, I want to understand from the real experience of people, as to how to write logger effectively in such an environment.

Best Answer

This is not an easy question to answer succinctly.

First, the approach you choose depends on your very specific goals and constraints. To what degree will you log object messages or activities? And is logging an intrinsic activity (something that is a key, integral responsibility of the objects, or is it a secondary, really nice to have, book-keeping activity)?

There are situations where logging is intrinsic, and pervasive, but often it is a secondary concern (a book-keeping activity). This suggests that you do not want to strongly couple the logger to each object that you are tracking.

One approach to consider is something along the lines of an application global message queue, or enterprise service bus. Something that is closer to the first alternative rather than the second alternative you suggested.

Each object that you are interested in tracking with a logger would produce object or activity specific messages that each implement a common message interface that supports the common properties you are interested in each object recording in the log for identification. In each object or activity specific message, define and add the specific details to that message structure. Finally, send the message to the application's message listener queue. If acknowledgement of receipt is required, give it to the sender, so it may continue. Generally in a closed, single-host system, explicit acknowledgement of message receipt is not required.

In the application scoped message processor (globally available within your application), you would 'register' message handlers. This could be done 'lazily', as needed, or pre-emptively at start up, as part of 'bootstrapping'. Perhaps it would include a generic handler that works on any message that it receives. It may have a handler for that common logger message. It may also have a handler for several or all specific logger messages, if other activities need to occur in conjunction with that message. In any regard, the message processor would successively dequeue messages, handle messages with the registered message handlers, appropriately discard them, and then grab the next message.

Part of the implication of an application scoped message processor in a multi-threaded application is that the only guarantee to message order is that the order the message enters the message queue will play a large role in the order that it appears in a particular log.

There are strategies for making message order more deterministic, but none will be perfect in all cases. One way is to add a global message sequencer, and 'get_next' from the sequencer when crafting each and every message, then insert into an indexed queue, and wait to dequeue until the next message in the sequence arrives. This too has implications, most notably lost or discarded messages blocking the message queue.