0

I am using a simple log class to debug my code. The code has been downloaded from http://www.drdobbs.com/cpp/logging-in-c/201804215?pgno=1 and is a log class implemented entirely in a header file.

For reason of clarity and building difficulties caused by the absence of an object file, I would like to split the implementation from declarations. That is move the code implementation to a cpp file.

Once I move the implementation details to a .cpp file , I end up getting alot of "undefined reference" linker errors to all the static member functions. The errors originate from the rest of my code. Anywhere I call the logging function from.

Is the problem related to the internal linkage that static member functions in .cpp file have? If so what options do I have to achieve splitting the file? One option would be to remove the class encapsulation and reimplement it as non-OO code. I would rather avoid that too, is there something else?

main.cpp

int main(int argc, char* argv[])
{

    FILELog::ReportingLevel() = FILELog::FromString(argv[1] ? argv[1] : "DEBUG1");
        const int count = 3;
    FILE_LOG(logDEBUG) << "A loop with " << count << " iterations";
    for (int i = 0; i != count; ++i)
    {
            FILE_LOG(logDEBUG1) << "the counter i = " << i;
    }
    return 0;
}

log.hpp

#ifndef __LOG_H__
#define __LOG_H__

#include <sstream>
#include <string>
#include <stdio.h>


#ifndef FILELOG_MAX_LEVEL
#define FILELOG_MAX_LEVEL logDEBUG4
#endif

#define FILE_LOG(level) \
    if (level > FILELOG_MAX_LEVEL) ;\
    else if (level > FILELog::ReportingLevel() || !Output2FILE::Stream()) ; \
    else FILELog().Get(level)


enum TLogLevel 
{
    logERROR, 
    logWARNING, 
    logINFO, 
    logDEBUG, 
    logDEBUG1, 
    logDEBUG2, 
    logDEBUG3, 
    logDEBUG4
};

class Output2FILE
{
public:
    static FILE*& Stream();
    static void Output(const std::string& msg);
};


template <typename T>
class Log
{
public:
    Log();
    virtual ~Log();
    std::ostringstream& Get(TLogLevel level = logINFO);
public:
    static TLogLevel& ReportingLevel();
    static std::string ToString(TLogLevel level);
    static TLogLevel FromString(const std::string& level);
protected:
    std::ostringstream os;
private:
    Log(const Log&);
    Log& operator =(const Log&);
};

//class FILELog : public Log<Output2FILE> {};
typedef Log<Output2FILE> FILELog;


#endif //__LOG_H__

log.cpp

#include <sys/time.h>

#include "log.hpp"

inline std::string NowTime()
{
    char buffer[11];
    time_t t;
    time(&t);
    //tm r = {0};
    tm r = {0,0,0,0,0,0,0,0,0,0,0};
    strftime(buffer, sizeof(buffer), "%X", localtime_r(&t, &r));
    struct timeval tv;
    gettimeofday(&tv, 0);
    char result[100] = {0};
    sprintf(result, "%s.%03ld", buffer, (long)tv.tv_usec / 1000); 
    return result;
}

//----------------------------------------------------------------------

template <typename T>
Log<T>::Log()
{
}

template <typename T>
std::ostringstream& Log<T>::Get(TLogLevel level)
{
    os << "- " << NowTime();
    os << " " << ToString(level) << ": ";
    os << std::string(level > logDEBUG ? level - logDEBUG : 0, '\t');
    return os;
}

template <typename T>
Log<T>::~Log()
{
    os << std::endl;
    T::Output(os.str());
}

template <typename T>
TLogLevel& Log<T>::ReportingLevel()
{
    static TLogLevel reportingLevel = logDEBUG4;
    return reportingLevel;
}

template <typename T>
std::string Log<T>::ToString(TLogLevel level)
{
    static const char* const buffer[] = {"ERROR", "WARNING", "INFO", "DEBUG", "DEBUG1", "DEBUG2", "DEBUG3", "DEBUG4"};
    return buffer[level];
}

template <typename T>
TLogLevel Log<T>::FromString(const std::string& level)
{
    if (level == "DEBUG4")
        return logDEBUG4;
    if (level == "DEBUG3")
        return logDEBUG3;
    if (level == "DEBUG2")
        return logDEBUG2;
    if (level == "DEBUG1")
        return logDEBUG1;
    if (level == "DEBUG")
        return logDEBUG;
    if (level == "INFO")
        return logINFO;
    if (level == "WARNING")
        return logWARNING;
    if (level == "ERROR")
        return logERROR;
    Log<T>().Get(logWARNING) << "Unknown logging level '" << level << "'. Using INFO level as default.";
    return logINFO;
}

//----------------------------------------------------------------------

inline FILE*& Output2FILE::Stream()
{
    static FILE* pStream = stderr;
    return pStream;
}

inline void Output2FILE::Output(const std::string& msg)
{   
    FILE* pStream = Stream();
    if (!pStream)
        return;
    fprintf(pStream, "%s", msg.c_str());
    fflush(pStream);
}
nass
  • 1,453
  • 1
  • 23
  • 38
  • I don't see a .cpp file with the implementation details of your logger? You should probably post the stuff that *doesn't work* that you need help fixing, not the stuff that does work! – user253751 Dec 16 '15 at 20:59
  • 1
    You can't put template implementation in source files - they **have** to be in the header files. – Amit Dec 16 '15 at 21:00
  • @immibis true true. fixed that – nass Dec 16 '15 at 21:07
  • 1
    @Amit I have read that somewhere while trying to figure out how to do that. But here it says otherwise: http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file – nass Dec 16 '15 at 21:09
  • Standard rule for all online code help: post only a minimum program that indicates the problem, including all components, and the errors that you get. – KarenRei Dec 16 '15 at 21:12
  • @KarenRei Indeed I try to do that, but I honestly fail to separate this code in smaller chunks. It just looks like a solid block that I do not know how to split - and this is what I was trying to do in the 1st place. So please bear with me here. Thank you – nass Dec 16 '15 at 21:16
  • @nass - I don't see how that question contradicts my point – Amit Dec 16 '15 at 21:44

1 Answers1

1

You need to explicitly instantiate type you are using - Log<Output2FILE>, so put following line:

 template class Log<Output2FILE>;

somewhere in log.cpp file after templates are defined. You will have to explicitly instantiate all types you will use with this template, or make template code available to other compilation units.

Slava
  • 43,454
  • 1
  • 47
  • 90
  • Dude you are nothing sort of a c++ God to me! I had been lost with this the whole day! Could you elaborate what you mean with "or make template code available to other compilation units". How do I do that? (or provide a link for a tutorial. – nass Dec 16 '15 at 21:24
  • @nass make it available means to put it into header. Btw most code on your template does not depend on so you may want to make base non templated class, move such functionality there implement in .cpp regular way. Then inherit template from that base and only implement what really uses `T`. In that case you may leave that templated code in header as it would be pretty small. – Slava Dec 16 '15 at 21:36