< Previous by Date Date Index Next by Date >
< Previous in Thread Thread Index Next in Thread >

Re: [reSIProcate] Log::initialize should take log level in upper or lower case (rutil)



My proposed fix is attached: Log.cxx and Log.hxx (including the isEqualNoCase thing), changes based off of resiprocate 1.9.6.

-John Gregg




On 05/14/2014 01:33 PM, slgodin@xxxxxxxxx wrote:
Hi John,

I agree - fixing fragility is a good thing.  Can you please post a patch file for these changes and I can investigate applying when I have some time?  Please remember not to use strcasecmp as it is not portable. 

Thanks for contributing!

Scott

On May 14, 2014, at 1:21 PM, John Gregg <jgregg@xxxxxxxxx> wrote:


Great. Thanks.

While I'm at it, it seems to me that there is a not entirely obvious dependency between mDescriptions[] in Log.cxx and Log::Level in Log.hxx. That is, the position of the strings in mDescriptions[] absolutely must correspond exactly with the values defined in the Log::Level enum. Moreover, Log::Level is defined with an #ifdef in the middle of it, and with various hardcoded defines in it (stuff set equal to other symbols from linux header files pulled in from elsewhere), and it all ends up compiling just right to match up with mDescriptions[], both in linux and Windows. So it works, but it is fragile in non-obvious ways.

The quick and dirty option would be to at least warn people with comments. That is, a comment in Log.cxx saying "NOTE: This list is order-dependent and MUST be kept in sync with the values defined in Log::Level", and a corresponding comment in Log.hxx saying "NOTE: The values defined here MUST be in sync with the order of the strings in mDescriptions[]".

Even better would be to get rid of the dependence as outlined below.

In Log.hxx, within class Log:

    struct descriptionEntry
    {
        Level level;
        const char * description;   
    };

...

      static const descriptionEntry mDescriptions[];

Then, in Log.cxx:

const struct Log::descriptionEntry Log::mDescriptions[] =
{
    { None, "NONE" },
    { Crit, "CRIT" },
    { Crit, "CRITICAL" },
    { Err, "ERR" },
    { Err, "ERROR" },
    { Warning, "WARNING" },
    { Warning, "WARN" },
    { Info, "INFO" },
    { Debug, "DEBUG" },
    { Stack, "STACK" },
    { StdErr, "CERR" },
    { Bogus, NULL }
};

...

Data
Log::toString(Level l)
{
    int i;

    for (i = 0; mDescriptions[i].description; i++)
    {
        if (mDescriptions[i].level == l)
        {
            return log_ + mDescriptions[i].description;
        }
    }
    return log_ + "INVALID";
}

Log::Level
Log::toLevel(const Data& l)
{
   Data pri( l.prefix("LOG_") ? l.substr(4) : l);

   int i = 0;

   for (i = 0; mDescriptions[i].description; i++)
    {
        if (strcasecmp(pri.c_str(), mDescriptions[i].description) == 0)
      {
          return mDescriptions[i].level;
      }

   }

   cerr << "Choosing Debug level since string was not understood: " << l << endl;
   return Log::Debug;
}

Note that this includes the strcmp => strcasecmp change, and note also that it maps several strings ("warn", "warning") to the same error level, but while converting from a level back to a string, it will grab the first one it hits, so the preferred canonical string should be first in the struct (hence the "WARNING" entry appears before "WARN").

-John Gregg


On 05/14/2014 11:29 AM, Scott Godin wrote:
I can't see a downside as well.  Feel free to post "patch" files to this list for inclusion in the project.  This one is very simple though - no need for a patch file.  I can apply it.

Note:  strcasecmp is not available on all platforms - I will use resip internal isEqualNoCase instead.

Thanks!
Scott


On Wed, May 14, 2014 at 11:17 AM, John Gregg <jgregg@xxxxxxxxx> wrote:

In rutil/Log.cxx:

When application code initializes the logging system in resiprocate, it passes the log level (sensitivity) to Log::initialize() as a string. This string is translated to an internal Log::Level enum by Log::toLevel(). Log::toLevel() is smart about stripping a preceding "LOG_" (if present), but it demands that the string be upper case (it uses a straight strcmp() to compare with hardcoded strings in mDescriptions[]). People may want, e.g., to use strings from prioritynames[] in syslog.h under linux, which are lower-case. It seems we should accept "crit" just as much as "CRIT". This would be a simple change of strcmp => strcasecmp in Log::toLevel(). I don't see a downside.

I do not have permission to modify resiprocate code myself. Is this the proper way to feed suggestions into the community?

-John Gregg

_______________________________________________
resiprocate-devel mailing list
resiprocate-devel@xxxxxxxxxxxxxxx
https://list.resiprocate.org/mailman/listinfo/resiprocate-devel



#include "rutil/Socket.hxx"

#include <cassert>
#include <iostream>
#include <fstream>
#include <stdio.h>
#include "rutil/Data.hxx"

#ifndef WIN32
#include <sys/time.h>
#endif

#include <sys/types.h>
#include <time.h>

#include "rutil/Log.hxx"
#include "rutil/Logger.hxx"
#include "rutil/ParseBuffer.hxx"
#include "rutil/ThreadIf.hxx"
#include "rutil/Subsystem.hxx"
#include "rutil/SysLogStream.hxx"
#include "rutil/WinLeakCheck.hxx"

using namespace resip;
using namespace std;

const Data Log::delim(" | ");
Log::ThreadData Log::mDefaultLoggerData(0, Log::Cout, Log::Info, NULL, NULL);
Data Log::mAppName;
Data Log::mHostname;
unsigned int Log::MaxLineCount = 0; // no limit by default
unsigned int Log::MaxByteCount = 0; // no limit by default

#ifdef WIN32
int Log::mPid=0;
#else 
pid_t Log::mPid=0;
#endif

volatile short Log::touchCount = 0;


/// DEPRECATED! Left for backward compatibility - use localLoggers instead
#ifdef LOG_ENABLE_THREAD_SETTING
#if defined(__APPLE__) || defined(__CYGWIN__)
HashValueImp(ThreadIf::Id, (size_t)data);
#endif
HashMap<ThreadIf::Id, std::pair<Log::ThreadSetting, bool> > Log::mThreadToLevel;
HashMap<int, std::set<ThreadIf::Id> > Log::mServiceToThreads;
ThreadIf::TlsKey* Log::mLevelKey;
#endif
HashMap<int, Log::Level> Log::mServiceToLevel;

Log::LocalLoggerMap Log::mLocalLoggerMap;
ThreadIf::TlsKey* Log::mLocalLoggerKey;

const struct Log::descriptionEntry Log::mDescriptions[] =
{
    { None, "NONE" },
    { Crit, "CRIT" },
    { Crit, "CRITICAL" },
    { Err, "ERR" },
    { Err, "ERROR" },
    { Warning, "WARNING" },
    { Warning, "WARN" },
    { Info, "INFO" },
    { Debug, "DEBUG" },
    { Stack, "STACK" },
    { StdErr, "CERR" },
    { Bogus, NULL }
};

Mutex Log::_mutex;

extern "C"
{
   void freeThreadSetting(void* setting)
   {
      delete static_cast<Log::ThreadSetting*>(setting);
   }

   void freeLocalLogger(void* pThreadData)
   {
      if (pThreadData)
      {
         // There was some local logger installed. Decrease its use count before we
         // continue.
         Log::mLocalLoggerMap.decreaseUseCount((static_cast<Log::ThreadData*>(pThreadData))->id());
      }
   }
}

unsigned int LogStaticInitializer::mInstanceCounter=0;
LogStaticInitializer::LogStaticInitializer()
{
   if (mInstanceCounter++ == 0)
   {
#ifdef LOG_ENABLE_THREAD_SETTING
         Log::mLevelKey = new ThreadIf::TlsKey;
         ThreadIf::tlsKeyCreate(*Log::mLevelKey, freeThreadSetting);
#endif

         Log::mLocalLoggerKey = new ThreadIf::TlsKey;
         ThreadIf::tlsKeyCreate(*Log::mLocalLoggerKey, freeLocalLogger);
   }
}
LogStaticInitializer::~LogStaticInitializer()
{
   if (--mInstanceCounter == 0)
   {
#ifdef LOG_ENABLE_THREAD_SETTING
      ThreadIf::tlsKeyDelete(*Log::mLevelKey);
      delete Log::mLevelKey;
#endif

      ThreadIf::tlsKeyDelete(*Log::mLocalLoggerKey);
      delete Log::mLocalLoggerKey;
   }
}

void
Log::initialize(const char* typed, const char* leveld, const char* appName, const char *logFileName, ExternalLogger* externalLogger)
{
   Log::initialize(Data(typed), Data(leveld), Data(appName), logFileName, externalLogger);
}

void
Log::initialize(const Data& typed, const Data& leveld, const Data& appName, 
                const char *logFileName, ExternalLogger* externalLogger)
{
   Type type = Log::Cout;
   if (isEqualNoCase(typed, "cout")) type = Log::Cout;
   else if (isEqualNoCase(typed, "cerr")) type = Log::Cerr;
   else if (isEqualNoCase(typed, "file")) type = Log::File;
   else type = Log::Syslog;
   
   Level level = Log::Info;
   level = toLevel(leveld);

   Log::initialize(type, level, appName, logFileName, externalLogger);
}

void 
Log::initialize(Type type, Level level, const Data& appName, 
                const char * logFileName,
                ExternalLogger* externalLogger)
{
   Lock lock(_mutex);
   mDefaultLoggerData.reset();   
   
   mDefaultLoggerData.set(type, level, logFileName, externalLogger);

   ParseBuffer pb(appName);
   pb.skipToEnd();
#ifdef _WIN32
   pb.skipBackToChar('\\');
#else
   pb.skipBackToChar('/');
#endif
   mAppName = pb.position();
 
   char buffer[1024];  
   gethostname(buffer, sizeof(buffer));
   mHostname = buffer;
#ifdef WIN32 
   mPid = (int)GetCurrentProcess();
#else
   mPid = getpid();
#endif
}

void
Log::initialize(Type type,
                Level level,
                const Data& appName,
                ExternalLogger& logger)
{
   initialize(type, level, appName, 0, &logger);
}

void
Log::setLevel(Level level)
{
   Lock lock(_mutex);
   getLoggerData().mLevel = level; 
}

void
Log::setLevel(Level level, Subsystem& s)
{
   Lock lock(_mutex);
   s.setLevel(level); 
}

void
Log::setLevel(Level level, Log::LocalLoggerId loggerId)
{
   if (loggerId)
   {
      ThreadData *pData = mLocalLoggerMap.getData(loggerId);
      if (pData)
      {
         // Local logger found. Set logging level.
         pData->mLevel = level;

         // We don't need local logger instance anymore.
         mLocalLoggerMap.decreaseUseCount(loggerId);
         pData = NULL;
      }
   }
   else
   {
      Lock lock(_mutex);
      mDefaultLoggerData.mLevel = level;
   }
}

Log::Level 
Log::level(Log::LocalLoggerId loggerId)
{
   Level level;
   ThreadData *pData;
   if (loggerId && (pData = mLocalLoggerMap.getData(loggerId)))
   {
      // Local logger found. Set logging level.
      level = pData->mLevel;

      // We don't need local logger instance anymore.
      mLocalLoggerMap.decreaseUseCount(loggerId);
      pData = NULL;
   }
   else
   {
      Lock lock(_mutex);
      level = mDefaultLoggerData.mLevel;
   }
   return level;
}

void 
Log::setMaxLineCount(unsigned int maxLineCount)
{
   Lock lock(_mutex);
   getLoggerData().mMaxLineCount = maxLineCount; 
}

void 
Log::setMaxLineCount(unsigned int maxLineCount, Log::LocalLoggerId loggerId)
{
   if (loggerId)
   {
      ThreadData *pData = mLocalLoggerMap.getData(loggerId);
      if (pData)
      {
         // Local logger found. Set logging level.
         pData->mMaxLineCount = maxLineCount;

         // We don't need local logger instance anymore.
         mLocalLoggerMap.decreaseUseCount(loggerId);
         pData = NULL;
      }
   }
   else
   {
      Lock lock(_mutex);
      mDefaultLoggerData.mMaxLineCount = maxLineCount;
   }
}

void 
Log::setMaxByteCount(unsigned int maxByteCount)
{
   Lock lock(_mutex);
   getLoggerData().mMaxByteCount = maxByteCount; 
}

void 
Log::setMaxByteCount(unsigned int maxByteCount, Log::LocalLoggerId loggerId)
{
   if (loggerId)
   {
      ThreadData *pData = mLocalLoggerMap.getData(loggerId);
      if (pData)
      {
         // Local logger found. Set logging level.
         pData->mMaxByteCount = maxByteCount;

         // We don't need local logger instance anymore.
         mLocalLoggerMap.decreaseUseCount(loggerId);
         pData = NULL;
      }
   }
   else
   {
      Lock lock(_mutex);
      mDefaultLoggerData.mMaxByteCount = maxByteCount;
   }
}

const static Data log_("LOG_");

Data
Log::toString(Level l)
{
    int i;

    for (i = 0; mDescriptions[i].description; i++)
    {
        if (mDescriptions[i].level == l)
        {
            return log_ + mDescriptions[i].description;
        }
    }
    return log_ + "INVALID";
}

Log::Level
Log::toLevel(const Data& l)
{
   Data pri( l.prefix("LOG_") ? l.substr(4) : l);

   int i = 0;

   for (i = 0; mDescriptions[i].description; i++)
   {
       if (isEqualNoCase(pri, mDescriptions[i].description))
       {
           return mDescriptions[i].level;
       }
   }

   cerr << "Choosing Debug level since string was not understood: " << l << endl;
   return Log::Debug;
}

Log::Type
Log::toType(const Data& arg)
{
   if (arg == "cout" || arg == "COUT")
   {
      return Log::Cout;
   }
   else if (arg == "cerr" || arg == "CERR")
   {
      return Log::Cerr;
   }
   else if (arg == "file" || arg == "FILE")
   {
      return Log::File;
   }
   else
   {
      return Log::Syslog;
   }
}

EncodeStream &
Log::tags(Log::Level level,
          const Subsystem& subsystem,
          const char* pfile,
          int line,
          EncodeStream& strm)
{
   char buffer[256];
   Data ts(Data::Borrow, buffer, sizeof(buffer));
#if defined( __APPLE__ )
   strm << toString(level) << Log::delim
        << timestamp(ts) << Log::delim  
        << mAppName << Log::delim
        << subsystem << Log::delim 
        << pthread_self() << Log::delim
        << pfile << ":" << line;
#elif defined( WIN32 )
   const char* file = pfile + strlen(pfile);
   while (file != pfile &&
          *file != '\\')
   {
      --file;
   }
   if (file != pfile)
   {
      ++file;
   }
   strm << toString(level) << Log::delim
        << timestamp(ts) << Log::delim  
        << mAppName << Log::delim
        << subsystem << Log::delim 
        << GetCurrentThreadId() << Log::delim
        << file << ":" << line;
#else // #if defined( WIN32 ) || defined( __APPLE__ )
   if(resip::Log::getLoggerData().type() == Syslog)
   {
      strm << toString(level) << Log::delim
           << timestamp(ts) << Log::delim
   //        << mHostname << Log::delim
           << mAppName << Log::delim
           << subsystem << Log::delim
           << mPid << Log::delim
           << pthread_self() << Log::delim
           << pfile << ":" << line;
   }
   else
      strm << toString(level) << Log::delim
           << timestamp(ts) << Log::delim  
   //        << mHostname << Log::delim  
           << mAppName << Log::delim
           << subsystem << Log::delim 
   //        << mPid << Log::delim
           << pthread_self() << Log::delim
           << pfile << ":" << line;
#endif
   return strm;
}

Data
Log::timestamp()
{
   char buffer[256];
   Data result(Data::Borrow, buffer, sizeof(buffer));
   return timestamp(result);
}

Data&
Log::timestamp(Data& res) 
{
   char* datebuf = const_cast<char*>(res.data());
   const unsigned int datebufSize = 256;
   res.clear();
   
#ifdef WIN32 
   int result = 1; 
   SYSTEMTIME systemTime;
   struct { time_t tv_sec; int tv_usec; } tv = {0,0};
   time(&tv.tv_sec);
   GetLocalTime(&systemTime);
   tv.tv_usec = systemTime.wMilliseconds * 1000; 
#else 
   struct tm localTimeResult;
   struct timeval tv; 
   int result = gettimeofday (&tv, NULL);
#endif   

   if (result == -1)
   {
      /* If we can't get the time of day, don't print a timestamp.
         Under Unix, this will never happen:  gettimeofday can fail only
         if the timezone is invalid which it can't be, since it is
         uninitialized]or if tv or tz are invalid pointers. */
      datebuf [0] = 0;
   }
   else
   {
      /* The tv_sec field represents the number of seconds passed since
         the Epoch, which is exactly the argument gettimeofday needs. */
      const time_t timeInSeconds = (time_t) tv.tv_sec;
      strftime (datebuf,
                datebufSize,
                "%Y%m%d-%H%M%S", /* guaranteed to fit in 256 chars,
                                    hence don't check return code */
#ifdef WIN32
                localtime (&timeInSeconds));  // Thread safe call on Windows
#else
                localtime_r (&timeInSeconds, &localTimeResult));  // Thread safe version of localtime on linux
#endif
   }
   
   char msbuf[5];
   /* Dividing (without remainder) by 1000 rounds the microseconds
      measure to the nearest millisecond. */
   snprintf(msbuf, 5, ".%3.3ld", long(tv.tv_usec / 1000));

   int datebufCharsRemaining = datebufSize - (int)strlen(datebuf);
   strncat (datebuf, msbuf, datebufCharsRemaining - 1);

   datebuf[datebufSize - 1] = '\0'; /* Just in case strncat truncated msbuf,
                                       thereby leaving its last character at
                                       the end, instead of a null terminator */

   // ugh, resize the Data
   res.at(strlen(datebuf)-1);
   return res;
}

Log::Level 
Log::getServiceLevel(int service)
{
   Lock lock(_mutex);
   HashMap<int, Level>::iterator res = Log::mServiceToLevel.find(service);
   if(res == Log::mServiceToLevel.end())
   {
      //!dcm! -- should perhaps throw an exception here, instead of setting a
      //default level of LOG_ERROR, but nobody uses this yet
      Log::mServiceToLevel[service] = Err;
      return Err;
   }
   return res->second;
}
   
const Log::ThreadSetting*
Log::getThreadSetting()
{
#ifndef LOG_ENABLE_THREAD_SETTING
   return 0;
#else
   ThreadSetting* setting = static_cast<ThreadSetting*>(ThreadIf::tlsGetValue(*Log::mLevelKey));
   if (setting == 0)
   {
      return 0;
   }
   if (Log::touchCount > 0)
   {
      Lock lock(_mutex);
      ThreadIf::Id thread = ThreadIf::selfId();
      HashMap<ThreadIf::Id, pair<ThreadSetting, bool> >::iterator res = Log::mThreadToLevel.find(thread);
      assert(res != Log::mThreadToLevel.end());
      if (res->second.second)
      {
         setting->mLevel = res->second.first.mLevel;
         res->second.second = false;
         touchCount--;
//         cerr << "**Log::getThreadSetting:touchCount: " << Log::touchCount << "**" << endl;

         //cerr << "touchcount decremented" << endl;
      }
   }
   return setting;
#endif
}

void 
Log::setThreadSetting(int serv)
{
   Log::setThreadSetting(ThreadSetting(serv, getServiceLevel(serv)));
}

void 
Log::setThreadSetting(int serv, Log::Level l)
{
   Log::setThreadSetting(ThreadSetting(serv, l));
}

void 
Log::setThreadSetting(ThreadSetting info)
{
#ifndef LOG_ENABLE_THREAD_SETTING
   assert(0);
#else
   //cerr << "Log::setThreadSetting: " << "service: " << info.service << " level " << toString(info.level) << " for " << pthread_self() << endl;
   ThreadIf::Id thread = ThreadIf::selfId();
   ThreadIf::tlsSetValue(*mLevelKey, (void *) new ThreadSetting(info));
   Lock lock(_mutex);

   if (Log::mThreadToLevel.find(thread) != Log::mThreadToLevel.end())
   {
      if (Log::mThreadToLevel[thread].second == true)
      {
         touchCount--;
      }
   }
   Log::mThreadToLevel[thread].first = info;
   Log::mThreadToLevel[thread].second = false;
   Log::mServiceToThreads[info.mService].insert(thread);
#endif
}
   
void 
Log::setServiceLevel(int service, Level l)
{
   Lock lock(_mutex);
   Log::mServiceToLevel[service] = l;
#ifndef LOG_ENABLE_THREAD_SETTING
   assert(0);
#else
   set<ThreadIf::Id>& threads = Log::mServiceToThreads[service];
   for (set<ThreadIf::Id>::iterator i = threads.begin(); i != threads.end(); i++)
   {
      Log::mThreadToLevel[*i].first.mLevel = l;
      Log::mThreadToLevel[*i].second = true;
   }
   Log::touchCount += (short)threads.size();
#endif
//   cerr << "**Log::setServiceLevel:touchCount: " << Log::touchCount << "**" << endl;
}

Log::LocalLoggerId Log::localLoggerCreate(Log::Type type,
                                          Log::Level level,
                                          const char * logFileName,
                                          ExternalLogger* externalLogger)
{
   return mLocalLoggerMap.create(type, level, logFileName, externalLogger);
}

int Log::localLoggerReinitialize(Log::LocalLoggerId loggerId,
                                 Log::Type type,
                                 Log::Level level,
                                 const char * logFileName,
                                 ExternalLogger* externalLogger)
{
   return mLocalLoggerMap.reinitialize(loggerId, type, level, logFileName, externalLogger);
}

int Log::localLoggerRemove(Log::LocalLoggerId loggerId)
{
   return mLocalLoggerMap.remove(loggerId);
}

int Log::setThreadLocalLogger(Log::LocalLoggerId loggerId)
{
   ThreadData* pData = static_cast<ThreadData*>(ThreadIf::tlsGetValue(*Log::mLocalLoggerKey));
   if (pData)
   {
      // There was some local logger installed. Decrease its use count before we
      // continue.
      mLocalLoggerMap.decreaseUseCount(pData->id());
      pData = NULL;
   }
   if (loggerId)
   {
      pData = mLocalLoggerMap.getData(loggerId);
   }
   ThreadIf::tlsSetValue(*mLocalLoggerKey, (void *) pData);
   return (loggerId == 0) || (pData != NULL)?0:1;
}

std::ostream&
Log::Instance(unsigned int bytesToWrite)
{
   return getLoggerData().Instance(bytesToWrite);
}

void 
Log::reset()
{
   getLoggerData().reset();
}

#ifndef WIN32
void
Log::droppingPrivileges(uid_t uid, pid_t pid)
{
   getLoggerData().droppingPrivileges(uid, pid);
}
#endif

bool
Log::isLogging(Log::Level level, const resip::Subsystem& sub)
{
   if (sub.getLevel() != Log::None)
   {
      return level <= sub.getLevel();
   }
   else
   {
      return (level <= Log::getLoggerData().mLevel);
   }
}

void
Log::OutputToWin32DebugWindow(const Data& result)
{
#ifdef WIN32
   const char *text = result.c_str();
#ifdef UNDER_CE
   LPWSTR lpwstrText = resip::ToWString(text);
   OutputDebugStringW(lpwstrText);
   FreeWString(lpwstrText);
#else
   OutputDebugStringA(text);
#endif
#endif
}

Log::LocalLoggerId Log::LocalLoggerMap::create(Log::Type type,
                                                    Log::Level level,
                                                    const char * logFileName,
                                                    ExternalLogger* externalLogger)
{
   Lock lock(mLoggerInstancesMapMutex);
   Log::LocalLoggerId id = ++mLastLocalLoggerId;
   Log::ThreadData *pNewData = new Log::ThreadData(id, type, level, logFileName,
                                                   externalLogger);
   mLoggerInstancesMap[id].first = pNewData;
   mLoggerInstancesMap[id].second = 0;
   return id;
}

int Log::LocalLoggerMap::reinitialize(Log::LocalLoggerId loggerId,
                                      Log::Type type,
                                      Log::Level level,
                                      const char * logFileName,
                                      ExternalLogger* externalLogger)
{
   Lock lock(mLoggerInstancesMapMutex);
   LoggerInstanceMap::iterator it = mLoggerInstancesMap.find(loggerId);
   if (it == mLoggerInstancesMap.end())
   {
      // No such logger ID
      std::cerr << "Log::LocalLoggerMap::remove(): Unknown local logger id=" << loggerId << std::endl;
      return 1;
   }
   it->second.first->reset();
   it->second.first->set(type, level, logFileName, externalLogger);
   return 0;
}

int Log::LocalLoggerMap::remove(Log::LocalLoggerId loggerId)
{
   Lock lock(mLoggerInstancesMapMutex);
   LoggerInstanceMap::iterator it = mLoggerInstancesMap.find(loggerId);
   if (it == mLoggerInstancesMap.end())
   {
      // No such logger ID
      std::cerr << "Log::LocalLoggerMap::remove(): Unknown local logger id=" << loggerId << std::endl;
      return 1;
   }
   if (it->second.second > 0)
   {
      // Non-zero use-count.
      std::cerr << "Log::LocalLoggerMap::remove(): Use count is non-zero (" << it->second.second << ")!" << std::endl;
      return 2;
   }
   delete it->second.first;  // delete ThreadData
   mLoggerInstancesMap.erase(it);
   return 0;
}

Log::ThreadData *Log::LocalLoggerMap::getData(Log::LocalLoggerId loggerId)
{
   Lock lock(mLoggerInstancesMapMutex);
   LoggerInstanceMap::iterator it = mLoggerInstancesMap.find(loggerId);
   if (it == mLoggerInstancesMap.end())
   {
      // No such logger ID
      return NULL;
   }
   it->second.second++;
   return it->second.first;
}

void Log::LocalLoggerMap::decreaseUseCount(Log::LocalLoggerId loggerId)
{
   Lock lock(mLoggerInstancesMapMutex);
   LoggerInstanceMap::iterator it = mLoggerInstancesMap.find(loggerId);
   if (it != mLoggerInstancesMap.end())
   {
      it->second.second--;
      assert(it->second.second >= 0);
   }
}


Log::Guard::Guard(resip::Log::Level level,
                  const resip::Subsystem& subsystem,
                  const char* file,
                  int line) :
   mLevel(level),
   mSubsystem(subsystem),
   mFile(file),
   mLine(line),
   mData(Data::Borrow, mBuffer, sizeof(mBuffer)),
   mStream(mData.clear())
{
	
   if (resip::Log::getLoggerData().mType != resip::Log::OnlyExternalNoHeaders)
   {
      Log::tags(mLevel, mSubsystem, mFile, mLine, mStream);
      mStream << resip::Log::delim;
      mStream.flush();

      mHeaderLength = mData.size();
   }
   else
   {
      mHeaderLength = 0;
   }
}

Log::Guard::~Guard()
{
   mStream.flush();

   if (resip::Log::getExternal())
   {
      const resip::Data rest(resip::Data::Share,
                             mData.data() + mHeaderLength,
                             (int)mData.size() - mHeaderLength);
      if (!(*resip::Log::getExternal())(mLevel, 
                                        mSubsystem, 
                                        resip::Log::getAppName(),
                                        mFile,
                                        mLine, 
                                        rest, 
                                        mData))
      {
         return;
      }
   }
    
   Type logType = resip::Log::getLoggerData().mType;

   if(logType == resip::Log::OnlyExternal ||
      logType == resip::Log::OnlyExternalNoHeaders) 
   {
      return;
   }

   resip::Lock lock(resip::Log::_mutex);
   // !dlb! implement VSDebugWindow as an external logger
   if (logType == resip::Log::VSDebugWindow)
   {
      mData += "\r\n";
      OutputToWin32DebugWindow(mData);
   }
   else 
   {
      // endl is magic in syslog -- so put it here
      Instance((int)mData.size()+2) << mData << std::endl;  
   }
}

std::ostream&
Log::ThreadData::Instance(unsigned int bytesToWrite)
{
//   std::cerr << "Log::ThreadData::Instance() id=" << mId << " type=" << mType <<  std::endl;
   switch (mType)
   {
      case Log::Syslog:
         if (mLogger == 0)
         {
            std::cerr << "Creating a syslog stream" << std::endl;
            mLogger = new SysLogStream;
         }
         return *mLogger;

      case Log::Cerr:
         return std::cerr;

      case Log::Cout:
         return std::cout;

      case Log::File:
         if (mLogger == 0 ||
             (maxLineCount() && mLineCount >= maxLineCount()) ||
             (maxByteCount() && ((unsigned int)mLogger->tellp()+bytesToWrite) >= maxByteCount()))
         {
            std::cerr << "Creating a logger for file \"" << mLogFileName.c_str() << "\"" << std::endl;
            Data logFileName(mLogFileName != "" ? mLogFileName : "resiprocate.log");
            if (mLogger)
            {
               Data oldLogFileName(logFileName + ".old");
               delete mLogger;
               // Keep one backup file: Delete .old file, Rename log file to .old
               // Could be expanded in the future to keep X backup log files
               remove(oldLogFileName.c_str());
               rename(logFileName.c_str(), oldLogFileName.c_str());
            }
            mLogger = new std::ofstream(logFileName.c_str(), std::ios_base::out | std::ios_base::app);
            mLineCount = 0;
         }
         mLineCount++;
         return *mLogger;
      default:
         assert(0);
         return std::cout;
   }
}

void 
Log::ThreadData::reset()
{
   delete mLogger;
   mLogger = NULL;
}

#ifndef WIN32
void
Log::ThreadData::droppingPrivileges(uid_t uid, pid_t pid)
{
   if(mType == Log::File)
   {
      Data logFileName(mLogFileName != "" ? mLogFileName : "resiprocate.log");
      if(chown(logFileName.c_str(), uid, pid) < 0)
      {
         // Some error occurred
         std::cerr << "ERROR: chown failed on " << logFileName << std::endl;
      }
   }
}
#endif

/* ====================================================================
 * The Vovida Software License, Version 1.0 
 * 
 * Copyright (c) 2000-2005
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 
 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
 *    and "Vovida Open Communication Application Library (VOCAL)" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact vocal@xxxxxxxxxx.
 *
 * 4. Products derived from this software may not be called "VOCAL", nor
 *    may "VOCAL" appear in their name, without prior written
 *    permission of Vovida Networks, Inc.
 * 
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
 * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL VOVIDA
 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 * 
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by Vovida
 * Networks, Inc. and many individuals on behalf of Vovida Networks,
 * Inc.  For more information on Vovida Networks, Inc., please see
 * <http://www.vovida.org/>.
 *
 */
#ifndef RESIP_Log_hxx
#define RESIP_Log_hxx

#include "rutil/Data.hxx"

#ifndef WIN32
#include <syslog.h>
#include <unistd.h>
#endif

#include <set>

#include "rutil/Mutex.hxx"
#include "rutil/Lock.hxx"
#include "rutil/HashMap.hxx"
#include "rutil/ThreadIf.hxx"
#include <iostream>

// !ipse! I think that this remark is no more valid with recent changes,
// but I don't have MacOs X to test with. Someone should take this duty.
//
// NOTE: disabling thread setting code for native mac os applications.
// since some logging takes place during static initialization we can't
// be sure all the pthread stuff is ready to go. this eventually causes
// crashes in the Mac OS native API.
#if !defined(TARGET_OS_MAC)
#define LOG_ENABLE_THREAD_SETTING
// defining hash function in mac os (non-sdk api) and cygwin because
// ThreadIf::Id is a pointer,  (this assumes it's always the same pointer)
#if defined(__APPLE__) || defined(__CYGWIN__)
HashValue(resip::ThreadIf::Id);
#endif
#endif

extern "C"
{
   // Forward declaration to make it friend of Log class.
   void freeLocalLogger(void* pThreadData);
};


namespace resip
{

class ExternalLogger;
class Subsystem;

/**
   @brief Singleton that handles logging calls.

   @see Logger for usage details
*/
class Log
{
   public:
      enum Type
      {
         Cout = 0,
         Syslog, 
         File, 
         Cerr,
         VSDebugWindow,        ///< Use only for Visual Studio Debug Window logging - WIN32 must be defined
         OnlyExternal,         ///< log messages are only written to external logger
         OnlyExternalNoHeaders ///< same as OnlyExternal, only the messageWithHeaders param of the ExternalLogger
                               ///< will be empty.  This parameter usually contains a pre-formatted log entry.
      };
      
      enum Level
      {
         None = -1,
#ifdef WIN32
         Crit = 2,
         Err = 3,
         Warning = 4,
         Info = 6,
         Debug = 7,
#else
         Crit = LOG_CRIT,
// #ifdef ERR // ncurses defines a macro called ERR 
//          SIP2_ERR = LOG_ERR,
// #else
//          ERR = LOG_ERR,
// #endif
         Err,
         Warning = LOG_WARNING,
         Info = LOG_INFO,
         Debug = LOG_DEBUG,
#endif
         Stack = 8,
         StdErr = 9,
         Bogus = 666
      };

      struct descriptionEntry
      {
          Level level;
          const char * description;    
      };

      /// Thread Local logger ID type.
      typedef int LocalLoggerId;

      /**
         @brief Implementation for logging macros.

         Log::Guard(Log::Info, Subsystem::TEST, __FILE__, __LINE__) << ... ;
      */
      class Guard
      {
         public:
            /** Remember the logging values and be a a stream to receive
                the log contents. */
            Guard(Level level,
                  const Subsystem& system,
                  const char* file,
                  int line);

            /** Commit logging */
            ~Guard();

            EncodeStream& asStream() {return mStream;}
            operator EncodeStream&() {return mStream;}

         private:
            resip::Log::Level mLevel;
            const resip::Subsystem& mSubsystem;
            resip::Data::size_type mHeaderLength;
            const char* mFile;
            int mLine;
            char mBuffer[128];
            Data mData;
            oDataStream mStream;
            Guard& operator=(const Guard&);
      };

      class ThreadSetting
      {
         public:
            ThreadSetting()
               : mService(-1),
                 mLevel(Err)
            {}

            ThreadSetting(int serv, Level level)
               : mService(serv),
                 mLevel(level)
            {
            }
            
            int mService;
            Level mLevel;
      };

      /// output the loglevel, hostname, appname, pid, tid, subsystem
      static EncodeStream& tags(Log::Level level,
                                const Subsystem& subsystem, 
                                const char* file,
                                int line,
                                EncodeStream& strm);

      static Data& timestamp(Data& result);
      static Data timestamp();
      static ExternalLogger* getExternal()
      {
         return getLoggerData().mExternalLogger;
      }
      static Data getAppName()
      {
         return mAppName;
      }

      static void initialize(Type type,
                             Level level,
                             const Data& appName,
                             const char * logFileName = 0,
                             ExternalLogger* externalLogger = 0);
      static void initialize(const Data& type,
                             const Data& level,
                             const Data& appName,
                             const char * logFileName = 0,
                             ExternalLogger* externalLogger = 0);
      static void initialize(const char* type,
                             const char* level,
                             const char* appName,
                             const char * logFileName = 0,
                             ExternalLogger* externalLogger = 0);
      static void initialize(Type type,
                             Level level,
                             const Data& appName,
                             ExternalLogger& logger);

      /** @brief Set logging level for current thread.
      * If thread has no local logger attached, then set global logging level.
      */
      static void setLevel(Level level);
      /** @brief Set logging level for given subsystem. */
      static void setLevel(Level level, Subsystem& s);
      /** Set logging level for given local logger. Use 0 to set global logging level. */
      static void setLevel(Level level, LocalLoggerId loggerId);
      /** @brief Return logging level for current thread.
      * If thread has no local logger attached, then return global logging level.
      */
      static Level level() { Lock lock(_mutex); return getLoggerData().mLevel; }
      /** Return logging level for given local logger. Use 0 to set global logging level. */
      static Level level(LocalLoggerId loggerId);
      static LocalLoggerId id() { Lock lock(_mutex); return getLoggerData().id(); }
      static void setMaxLineCount(unsigned int maxLineCount);
      static void setMaxLineCount(unsigned int maxLineCount, LocalLoggerId loggerId);
      static void setMaxByteCount(unsigned int maxByteCount);
      static void setMaxByteCount(unsigned int maxByteCount, LocalLoggerId loggerId);
      static Level toLevel(const Data& l);
      static Type toType(const Data& t);
      static Data toString(Level l);

      /// DEPRECATED! Left for backward compatibility - use localLoggers instead
      static void setServiceLevel(int service, Level l);
      static Level getServiceLevel(int service);

      /// DEPRECATED! Left for backward compatibility - use localLoggers instead
      static const ThreadSetting* getThreadSetting();
      static void setThreadSetting(ThreadSetting info);
      static void setThreadSetting(int serv, Level l);
      static void setThreadSetting(int serv);

      /// Create new logger instance and return its ID (zero on error)
      static LocalLoggerId localLoggerCreate(Type type,
                                             Level level,
                                             const char * logFileName = NULL,
                                             ExternalLogger* externalLogger = NULL);

      /** Reinitialize all new setting for a local logger instance
      * @retval 0 on success
      * @retval 1 if logger does not exist
      */
      static int localLoggerReinitialize(LocalLoggerId loggerId,
                                         Type type,
                                         Level level,
                                         const char * logFileName = NULL,
                                         ExternalLogger* externalLogger = NULL);						

      /** Destroy existing logger instance.
      * @retval 0 on success
      * @retval 1 if logger does not exist
      * @retval 2 if logger is still in use
      * @retval >2 on other failures
      */
      static int localLoggerRemove(LocalLoggerId loggerId);

      /** Set logger instance with given ID as a thread local logger.
      * Pass zero \p loggerId to remove thread local logger.
      * @retval 0 on success
      * @retval 1 if logger does not exist
      * @retval >1 on other failures
      */
      static int setThreadLocalLogger(LocalLoggerId loggerId);


      static std::ostream& Instance(unsigned int bytesToWrite);
      static bool isLogging(Log::Level level, const Subsystem&);
      static void OutputToWin32DebugWindow(const Data& result);      
      static void reset(); ///< Frees logger stream
#ifndef WIN32
      static void droppingPrivileges(uid_t uid, pid_t pid);
#endif

   public:
      static unsigned int MaxLineCount; 
      static unsigned int MaxByteCount; 

   protected:
      static Mutex _mutex;
      static volatile short touchCount;
      static const Data delim;

      class ThreadData
      {
         public:
            ThreadData(LocalLoggerId id, Type type=Cout, Level level=Info,
                       const char *logFileName=NULL,
                       ExternalLogger *pExternalLogger=NULL)
               : mLevel(level),
                 mMaxLineCount(0),
                 mMaxByteCount(0),
                 mExternalLogger(pExternalLogger),
                 mId(id),
                 mType(type),
                 mLogger(NULL),
                 mLineCount(0)
            {
               if (logFileName)
               {
                  mLogFileName = logFileName;
               }
            }
            ~ThreadData() { reset(); }

            void set(Type type=Cout, Level level=Info,
                     const char *logFileName=NULL,
                     ExternalLogger *pExternalLogger=NULL)
            {
               mType = type;
               mLevel = level;

               if (logFileName)
               {
                  mLogFileName = logFileName;
               }
               mExternalLogger = pExternalLogger;
            }

            LocalLoggerId id() const {return mId;}
            unsigned int maxLineCount() { return mMaxLineCount ? mMaxLineCount : MaxLineCount; }  // return local max, if not set use global max
            unsigned int maxByteCount() { return mMaxByteCount ? mMaxByteCount : MaxByteCount; }  // return local max, if not set use global max
            Type type() const {return mType;}

            std::ostream& Instance(unsigned int bytesToWrite); ///< Return logger stream instance, creating it if needed.
            void reset(); ///< Frees logger stream
#ifndef WIN32
            void droppingPrivileges(uid_t uid, pid_t pid);
#endif

            volatile Level mLevel;
            volatile unsigned int mMaxLineCount;
            volatile unsigned int mMaxByteCount;
            ExternalLogger* mExternalLogger;

         protected:
            friend class Guard;
            const LocalLoggerId mId;
            Type mType;
            Data mLogFileName;
            std::ostream* mLogger;
            unsigned int mLineCount;
      };

      static ThreadData mDefaultLoggerData; ///< Default logger settings.
      static Data mAppName;
      static Data mHostname;
#ifndef WIN32
      static pid_t mPid;
#else   
      static int mPid;
#endif

      static const descriptionEntry mDescriptions[];

      static ThreadData &getLoggerData()
      {
         ThreadData* pData = static_cast<ThreadData*>(ThreadIf::tlsGetValue(*Log::mLocalLoggerKey));
         return pData?*pData:mDefaultLoggerData;
      }

      /// Thread Local logger settings storage
      class LocalLoggerMap
      {
      public:
         LocalLoggerMap()
            : mLastLocalLoggerId(0) {};

         /// Create new logger instance and return its ID (zero on error)
         LocalLoggerId create(Type type,
                              Level level,
                              const char * logFileName = NULL,
                              ExternalLogger* externalLogger = NULL);

         /** Reinitialize all new setting for a local logger instance
          * @retval 0 on success
          * @retval 1 if logger does not exist
          */
         int reinitialize(LocalLoggerId loggerId,
                          Type type,
                          Level level,
                          const char * logFileName = NULL,
                          ExternalLogger* externalLogger = NULL);						

         /** Remove existing logger instance from map and destroy.
         * @retval 0 on success
         * @retval 1 if logger does not exist
         * @retval 2 if logger is still in use
         * @retval >2 on other failures
         */
         int remove(LocalLoggerId loggerId);

         /** Get pointer to ThreadData for given ID and increase use counter.
         * @returns NULL if ID does not exist. */
         ThreadData *getData(LocalLoggerId loggerId);

         /// Decrease use counter for given loggerId.
         void decreaseUseCount(LocalLoggerId loggerId);

      protected:
         /// Storage for Thread Local loggers and their use-counts.
         typedef HashMap<LocalLoggerId, std::pair<ThreadData*, int> > LoggerInstanceMap;
         LoggerInstanceMap mLoggerInstancesMap;
         /// Last used LocalLoggerId
         LocalLoggerId mLastLocalLoggerId;
         /// Mutex to synchronize access to Thread Local logger settings storage
         Mutex mLoggerInstancesMapMutex;
      };

      friend void ::freeLocalLogger(void* pThreadData);
      friend class LogStaticInitializer;
      static LocalLoggerMap mLocalLoggerMap;
      static ThreadIf::TlsKey* mLocalLoggerKey;


      /// DEPRECATED! Left for backward compatibility - use localLoggers instead
#ifdef LOG_ENABLE_THREAD_SETTING
      static HashMap<ThreadIf::Id, std::pair<ThreadSetting, bool> > mThreadToLevel;
      static HashMap<int, std::set<ThreadIf::Id> > mServiceToThreads;
      static ThreadIf::TlsKey* mLevelKey;
#endif
      static HashMap<int, Level> mServiceToLevel;
};

/** @brief Interface functor for external logging.
*/
class ExternalLogger
{
public:
   virtual ~ExternalLogger() {};
   /** return true to also do default logging, false to suppress default logging. */
   virtual bool operator()(Log::Level level,
      const Subsystem& subsystem, 
      const Data& appName,
      const char* file,
      int line,
      const Data& message,
      const Data& messageWithHeaders) = 0;
};

/// Class to initialize Log class static variables.
class LogStaticInitializer {
public:
   LogStaticInitializer();
   ~LogStaticInitializer();
protected:
   static unsigned int mInstanceCounter;
    
#ifdef WIN32
   // LogStaticInitializer calls ThreadIf::tlsKeyCreate which 
   // relies on the static TlsDestructorInitializer having set
   // up mTlsDestructorsMutex which is used to Lock TLS access
   // Since the order of static initialization is not reliable,
   // we must make sure that TlsDestructorInitializer is initialized
   // before LogStaticInitializer is inizialized:
   TlsDestructorInitializer tlsDestructorInitializer;
#endif
};
static LogStaticInitializer _staticLogInit;

}

#endif

/* ====================================================================
 * The Vovida Software License, Version 1.0 
 * 
 * Copyright (c) 2000-2005.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 
 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
 *    and "Vovida Open Communication Application Library (VOCAL)" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact vocal@xxxxxxxxxx.
 *
 * 4. Products derived from this software may not be called "VOCAL", nor
 *    may "VOCAL" appear in their name, without prior written
 *    permission of Vovida Networks, Inc.
 * 
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
 * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL VOVIDA
 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 * 
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by Vovida
 * Networks, Inc. and many individuals on behalf of Vovida Networks,
 * Inc.  For more information on Vovida Networks, Inc., please see
 * <http://www.vovida.org/>.
 *
 */