views:

479

answers:

4

HI,

Consider the following code:

void Foo() {
  ......
  LOG_ERROR("I'm error 1")   // call 1
  .....
  LOG_ERROR("I'm error 2")  // call 2
  .....

}

LOG_ERROR() is a macro. LOG_ERROR() should print string identifying it in code, while the assumption is that code can change, but A::Foo() will remain unchanged. The identifier should retain while code changes.

This can be solved by adding error code as argument to LOG_ERROR(), but we want to remove from the programmer the burden to manage error codes.

Using __LINE__ is not an answer, since Foo() can move from build to build.

Therefore I thought about identifying LOG_ERROR() relative to start of Foo():

  • a. Identify by file name (__FILE__) + function name (__FUNCTION__) + line number of LOG_ERROR() relative to Foo() start.
  • b. Identify by file name (__FILE__) + function name (__FUNCTION__) + LOG_ERROR() call number in Foo().

The solution should be work with VC++ 2008 and g++ 4.1.1 at least.

One proposed solution (link text) is:

#define ENABLE_LOG_ERROR static const int LOG_ERROR_start_line = __LINE__
#define LOG_ERROR(s) cerr << "error #" << (__LINE__ - LOG_ERROR_start_line) \
    << " in " << __func__ << ": " << s << endl

void Foo() {
     ENABLE_LOG_ERROR;
     //...
     LOG_ERROR("error 1");
     int i;
     LOG_ERROR("error 2");
}

This will force user to write ENABLE_LOG_ERROR in start of each function containing LOG_ERROR() and there're many such functions.

Is there other way to accomplish the task?

Thanks Dima

A: 

Here's a little something I threw together. It works by maintaining a global "stack", which means the number will still increase between function calls. If you're ok with putting in ENABLE_LOG_ERROR, then it works as desired:

#include <iostream>
#include <string>

// appends a line number to a string
#define APPENDLINE_IMPL(x, l) x##l
#define APPENDLINE_DETAIL(x, l) APPENDLINE_IMPL(x, l)
#define APPENDLINE(x) APPENDLINE_DETAIL(x, __LINE__)

// make a unique log error object using the line number
#define LOG_ERROR(s) LogError APPENDLINE(LOGERROR)(s); (void)0
#define ENABLE_LOG_ERROR LogErrorEnabler LOGERRORENABLER;

// Logs an error
class LogError
{
public:
    // log the error, increment the number
    LogError(const std::string& error)
    {
     std::cerr << "Error #" << _errorCount << ": " << error << std::endl;

     ++_errorCount;
    }

    // going out of scope, decrement
    ~LogError(void)
    {
     --_errorCount;
    }

private:
    // let the enabler handle the number
    friend class LogErrorEnabler;

    // current error number (in function)
    static unsigned _errorCount;
};

// resets & restores the error count
class LogErrorEnabler
{
public:
    // reset but save the error count
    LogErrorEnabler(void)
    {
     _oldErrorCount = LogError::_errorCount;

     LogError::_errorCount = 0;
    }

    // reset error count (function is ending)
    ~LogErrorEnabler(void)
    {
     LogError::_errorCount = _oldErrorCount;
    }

private:
    // old error number
    unsigned _oldErrorCount;
};

// would be in LogError.cpp
unsigned LogError::_errorCount = 0; 



void foo(void)
{
    ENABLE_LOG_ERROR;

    LOG_ERROR("Top of foo function");
    LOG_ERROR("Middle of foo function");
    LOG_ERROR("Bottom of foo function");
}

void bar(void)
{
    ENABLE_LOG_ERROR;

    LOG_ERROR("Top of bar function");
    foo();
    LOG_ERROR("Bottom of bar function");
}

int main(void)
{
    bar();

    return 0;
}

Edit

The following code works as intended, but without the ENABLE_LOG_ERROR requirement. :)

It works by letting the first LogErrorEnabler do it's job, but if any are instantiated after the initial Enabler is created, they are put into a zombie state and do nothing.

The only trick was to detect when an Enabler was being created in a new scope, with an Enabler already active in the previous scope. This was done using the (non-standard but soon to be, so is supported) __FUNCTION__ macro. This macro is passed into the Enabler, upon where it can check if it's being created in a different function than the original Enabler.

Here is the code:

#include <iostream>
#include <string>

// appends a line number to a string
#define APPENDLINE_IMPL(x, l) x##l
#define APPENDLINE_DETAIL(x, l) APPENDLINE_IMPL(x, l)
#define APPENDLINE(x) APPENDLINE_DETAIL(x, __LINE__)

// make a unique log error object using the line number
#define LOG_ERROR(s) LogErrorEnabler APPENDLINE(LOGERRORENABLER)(__FUNCTION__); \
         LogError APPENDLINE(LOGERROR)(s); \
         (void)0

// Logs an error
class LogError
{
public:
    // log the error, increment the number
    LogError(const std::string& error)
    {
     std::cerr << "Error #" << _errorCount << ": " << error << std::endl;

     ++_errorCount;
    }

    // going out of scope, decrement
    ~LogError(void)
    {
     --_errorCount;
    }

private:
    // let the enabler handle the number
    friend class LogErrorEnabler;

    // current error number (in function)
    static unsigned _errorCount;
};

// resets & restores the error count
class LogErrorEnabler
{
public:
    // reset but save the error count
    LogErrorEnabler(const std::string& functionName)
    {
     if (functionName != _currentFunctionName)
     {
      // entered new function
      _oldErrorCount = LogError::_errorCount;
      LogError::_errorCount = 0;

      // store function name
      _oldFunctionName = _currentFunctionName;
      _currentFunctionName = functionName;

      // this enabler is active
      _zombie = false;
     }
     else
     {
      // make this enabler do nothing
      _zombie = true;
     }
    }

    // reset error count (function is ending)
    ~LogErrorEnabler(void)
    {
     if (!_zombie)
     {
      // restore old state
      LogError::_errorCount = _oldErrorCount;
      _currentFunctionName = _oldFunctionName;
     }
    }

private:
    // old error number
    unsigned _oldErrorCount;

    // old function name
    std::string _oldFunctionName;

    // zombie state
    bool _zombie;

    // current function
    static std::string _currentFunctionName;
};

// would be in LogError.cpp
unsigned LogError::_errorCount = 0; 

// would be in LogErrorEnabler.cpp
std::string LogErrorEnabler::_currentFunctionName = "";



void foo(void)
{
    LOG_ERROR("Top of foo function");
    LOG_ERROR("Middle of foo function");
    LOG_ERROR("Bottom of foo function");
}

void bar(void)
{
    LOG_ERROR("Top of bar function");
    foo();
    LOG_ERROR("Bottom of bar function");
}

int main(void)
{
    bar();

    return 0;
}

Let me know if there are more modifications you require. :)

GMan
This doesn't compile for me in VS 2005. These errors appear for each `LOG_ERROR()`: error C2501: 'LOGERRORENABLER' : missing storage-class or type specifiers error C2143: syntax error : missing ';' before '(' error C2501: 'LOGERROR' : missing storage-class or type specifiers error C2143: syntax error : missing ';' before '('As well as complaints about redefinitions if I have more than one.Also, it will not assign unique IDs if any of the `LOG_ERRORS` are in a different scope: e.g., in the body of an if.
Geerad
Sorry, that was VS 2003.It DOES compile in VS 2005, but my point about if statements and other scopes stands.
Geerad
I'm pretty sure that will be impossible. This solution handles all the requirements (No `ENABLE_LOG_ERRORS`, restart counts at [most] scope changes, insensitive to function location) otherwise.
GMan
I think the 2nd solution won't work: - it requires all LOG_ERROR() to be called in order to increment _errorCount, but don't forget about flow control structure (e.g. if, for statements) so some LOG_ERROR() will not be called or will be called several times. - class LogErrorEnabler remembers current function name. But in case function A() calls to B() and later control returns back to A(), the _errorCount will be reset.
dimba
A: 

By modification of the stack idea, use a std::map mapping from std::string to the count, and look-up the function name.

std::map<std::string, int> LOG_ERROR_count_map;
#define LOG_ERROR(s) {\
  int count = ++LOG_ERROR_count_map[ __func__ ];\
  std::cout << count << " in " __func__ ": " s << std::endl;\
}

This means you don't need the ENABLE_LOG_ERROR, but at the cost of a map look-up for each log. (It's a trade-off between ease-of-use and time.)

Benjamin Titmus
Both my solution and GMan's don't give the nth instance of LOG_ERROR in the function, but the nth call of LOG_ERROR in the function. Therefore they cannot be used to simply count down the uses (which is probably the intended use) when branching occurs. Any form of dynamic count method will not work.
Benjamin Titmus
It's probably obvious, but worth pointing out that you can't use the preprocessor for static counts based on function name because there's no way to detect when the function has changed - strings cannot be compared.
Benjamin Titmus
+1  A: 

This solution is non-standard, but both MSVC and GCC support __COUNTER__, which is incremented every time it is invoked.

#define LOG_ERROR(s) cerr << "error #" << (__COUNTER__) << " in " \
<< __func__ << ": " << s << endl

Note that __COUNTER__ will be reset in each compilation unit, and ONLY in each compilation unit. So if Foo() has 7 LOG_ERROR() macros, in a later function Bar() the value of __COUNTER__ will be 7 for the first use of LOG_ERROR().

Geerad
This won't work in between functions, however.
GMan
Actual problem is that if you insert a new message, the ones below it are all renumbered.
Daniel Earwicker
@GMan: You're correct that it won't reset for each function. I've edited to clarify that.@Earwicker: Also correct. This is a fairly brittle solution. It assumes that `LOG_ERROR()`s are only ever added after all previous invocations in the same compilation unit.
Geerad
The problem is that __COUINTER__ is only supported from gcc 4.3.3, while I'm using 4.1.1
dimba
technically cpp is the pre-processor, so you could try updating just that to get support for __COUNTER__
KitsuneYMG
+1  A: 

While the question is about ways to generate unique line identifiers within a function for logging purposes, I'm going to take a step back and look at the actual problem to be solved: How to generate log output which can easily identify the source code line without putting the burden on the writer of the code.

Let's assume you are embedding a unique build version in each release of your program (which is a good idea in general). Let's also assume you are using a source code control mechanism which keeps the history of your source code (which is also a very good idea to be doing anyway) and which can present you with the source as it was for any requested build version of the program.

If those assumptions hold true, then a solution is to have your program write it's current version into the log file. Then each individual logging entry can simply record the line number via __LINE__.

Thus, when someone needs to use the log: they can look at the version number in the log, grab the corresponding source from the source code control repository, and use the line numbers from the log to go to the proper source lines. This puts a bit more burden on the person using the log output. However, if logged code depends on or is influenced by other code which could change from version to version, then the historical state of the source code might be required anyway.

Furthermore, a benefit of working this way is that it removes to need to assume that any given function will remain unchanged, as was originally part of the question. So this method has a much broader application.


As far as implementation goes, you could either log the program version when the program starts up or you could make the logging macro include it in each entry.

If the program version is normally stored somewhere not easily accessible in normal source code, then you could create a pre-build step which would extract the version and write it into a simple version.h file as a #define or const string. Then the logging code or macro could automatically use that to always output the current version of the program.

TheUndeadFish