different loggers used with libraries
Asked Answered
S

2

8

My problem concerns logging of library classes (classes that are used inside libraries), We are currently using log4cxx but the log4j library implements the same concepts.

Say i have a process that have several entities, A,B and C. Each of them use many different classes and functions, clearly separated in the code.

A,B and C use many library classes, functions, objects, resources and sometimes even globals (legacy code, nothing i can do about it...) - let us call them all foo

Logging A,B and C turned out to be a performance issue, the log gets blasted when we set the log level to debug. After viewing our system we came to these conclusions:

  1. We want to be able to change debug level for only one of the classes at a time (or all of them, using root)
  2. When all kind of foo print to log, we need to see which entity called it, A, B or C.
  3. As there are many foo we want to be able to change the debug level separately for each foo
  4. A foo should be treated as a common library, it can not depend directly on A,B or C.
  5. A,B and C might use the same instance of foo (for example, the same instance of our resource handling class is used A,B and C), in the log we would like to see which class used foo.
  6. A could use B (or C), but we dont have to see it in the log...

This is what we came up with so far -

A, B and C will have separate loggers. A global variable(kept in a different library with all of our logging helpers and wrappers) will always keep the current log reporting. Every time an entity starts handling it's logic, it sets the global variable to the proper logger. When a foo wants to report to the log, it reports through the global variable and adding it's name (and context) to the log message.

Problem is, it feels like there must be something that does this already, the solution does not feel clean, holding a global variable like that...

Are we doing something wrong here? Is there a better solution to this?

Stalinsk answered 29/12, 2013 at 22:13 Comment(16)
This doesn't sound thread safeParkland
We don't need it thread safe, making it thread safe is quite easy by making the global variable unique for each thread.Stalinsk
I believe the separation can be done with markers in logback without the hack you describe. The slf4j project has the necessary glue.Parkland
Why are you globally enabling debug? Unless you want the behavior you're seeing (e.g. during development), set the apache libraries to info if not warn or error.Craze
@ThorbjørnRavnAndersen the problem knowing how to determine from which entity the message came from, so i don't see how markers are relevant...Stalinsk
@ElliottFrisch it does not matter which level is reported, i need to know which entity logged the event, not which class or function (remember, the library don't know which entities exist)Stalinsk
@Stalinsk How are your projects obtaining Loggers? Also, you should narrow this down to one language... it's a very broad question.Craze
@ElliottFrisch well, the language is an issue, we are actually using c++, but since the log4cxx and log4j, etc are very similar and implement the same concepts, i wanted to direct the question for the more commonly used languages... As for obtaining the loggers, as for now our libraries don't report to the log, or they report using cout (or System.out.print for say). Most of our classes obtain the logger via a designated singletonStalinsk
@Stalinsk In log4j you would obtain the Logger with your classes package intact; in that way, you can limit your messages to the appropriate level by the package. You should edit your question to reflect the realities, or you'll keep getting well meaning comments that are of no value to your situation.Craze
You can put information in the MDC/NDC about which module you are in and access it in your actual logging pattern.Parkland
@ElliottFrisch please correct me if i'm wrong but the package is the library in java, meaning that my DBConnection class for example will be in DB package, then how do i know if it's used from A,B or C? I'll remove csharp and remove log4cxx, i'll revise the question itself in the morning...Stalinsk
@Stalinsk I would say it's more akin to the classes namespace. I'm not certain if you're looking for a syntactical, semantic or philosophical answer.Craze
@ElliottFrisch, i'm looking for a practical solution, i think that this is what you mean by syntactical or semantical. A philosophical solution will be good too, but it leaves me to find the details of implementation. P.S, an acceptable answer will also be to tell me that our requirements are too damn high and we can't do all of these while keeping the design cleanStalinsk
@Stalinsk I still don't follow your question, and you never did edit it. What are the namespaces of your classes "A", "B" and "C"?Craze
@ElliottFrisch A, B and C could be on the same namespace and could be on different namespaces. I edited some of the text, anything particular that needs to be edited (changed it to log4cxx for example)?Stalinsk
@Stalinsk Perhaps a self contained example, no one can answer with the classes "a", "b" and "c". Also, tag the question with the language you want an answer for.Craze
C
5

I do not know of an existing solution. I would probably come up with a logger with an interface like the following (whether it is a standalone implementation or just a wrapper on your existing one):

class Logger {
public:
    enum Level {
        ...
    };
    static Logger* Instance();
    void Log(Level level, const char* module, const char* msg);
    void AddModuleFilter(const char* context, const char* module, Level level);
    void SetThreadLocalContext(const char* context);
    ...
};

The main deviation from common log libraries are the context-sensitive module filter. We may have setups like the following to set different levels according to who makes the call (the context):

// foo calls by entity A have the DEBUG log level
Logger::Instance()->AddModuleFilter("A", "foo", Logger::DEBUG);
// foo calls by entity B have the WARNING log level
Logger::Instance()->AddModuleFilter("B", "foo", Logger::WARNING);

Then, the call flow will be like:

// In entity A
Logger::Instance()->SetThreadLocalContext("A");
// Call function foo
Logger::Instance()->Log(log_level, "foo", log_msg);

Not sure whether such an interface meets your purpose. Anyway, I always regard interface first a good approach. Once you have a clear interface, implementing it should be an easy job.

Chronogram answered 8/1, 2014 at 8:54 Comment(2)
Due to multi-threading, it would be wise (read: mandatory) to combine SetThreadLocalContext and Log into one function. When thread 'A' is paused by the Operating System, one other logger could call SetThreadLocalContext(...)...Dave
@M.Mimpen Since this is thread-local context, switching to another thread will not affect the previous thread's context. In the implementation, one would need to use thread_local, __thread, or __declspec(thread) in C/C++, and ThreadLocal in Java.Chronogram
X
1

If the separate library is itself a class, you can use a class-level variable in the library to hold the logger instance reference. I don't often program in C++ or Java, but I think a class-level variable is 'static' in C++ and Java.

It is still a global variable underneath, but at least it's class-scoped global variable (like a class variable named debugLog.log where debugLog is the class name).

Xylem answered 6/1, 2014 at 2:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.