Subversion Repositories tpanel

Rev

Rev 116 | Blame | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2020, 2021 by Andreas Theofilu <andreas@theosys.at>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */
#include <iostream>
#include <fstream>
#include <sstream>
#include <ios>
#include <time.h>
#include <mutex>

#include "terror.h"
#include "tconfig.h"

#if defined(__linux__) || defined(Q_OS_ANDROID)
#include <QMessageBox>
#include <QTimer>
#endif
#if LOGPATH == LPATH_SYSLOG || defined(__ANDROID__)
#   ifdef __ANDROID__
#       include <android/log.h>
#   else
#       include <syslog.h>
#   endif
#endif

using std::string;
std::mutex message_mutex;

bool TError::mHaveError = false;
terrtype_t TError::mErrType = TERRNONE;
TStreamError *TError::mCurrent = nullptr;
std::string TError::msError;

int TStreamError::mIndent = 1;
std::ostream *TStreamError::mStream = nullptr;
std::string TStreamError::mLogfile;
bool TStreamError::mInitialized = false;
unsigned int TStreamError::mLogLevel = HLOG_PROTOCOL;

#if LOGPATH == LPATH_SYSLOG || defined(__ANDROID__)
class androidbuf : public std::streambuf
{
    public:
        enum { bufsize = 1024 };
        androidbuf() { this->setp(buffer, buffer + bufsize - 1); }

    private:
        int overflow(int c)
        {
            if (c == traits_type::eof())
            {
                *this->pptr() = traits_type::to_char_type(c);
                this->sbumpc();
            }

            return this->sync()? traits_type::eof(): traits_type::not_eof(c);
        }

        int sync()
        {
            int rc = 0;

            if (this->pbase() != this->pptr())
            {
                char writebuf[bufsize+1];
                memcpy(writebuf, this->pbase(), this->pptr() - this->pbase());
                writebuf[this->pptr() - this->pbase()] = '\0';
                int eType;
#ifdef __ANDROID__
                switch(TError::getErrorType())
                {
                    case TERRINFO:      eType = ANDROID_LOG_INFO; break;
                    case TERRWARNING:   eType = ANDROID_LOG_WARN; break;
                    case TERRERROR:     eType = ANDROID_LOG_ERROR; break;
                    case TERRTRACE:     eType = ANDROID_LOG_VERBOSE; break;
                    case TERRDEBUG:     eType = ANDROID_LOG_DEBUG; break;
                    case TERRNONE:      eType = ANDROID_LOG_INFO; break;
                }

                rc = __android_log_print(eType, "tpanel", "%s", writebuf) > 0;
#else
                switch(TError::getErrorType())
                {
                    case TERRINFO:      eType = LOG_INFO; break;
                    case TERRWARNING:   eType = LOG_WARN; break;
                    case TERRERROR:     eType = LOG_ERROR; break;
                    case TERRTRACE:     eType = LOG_INFO; break;
                    case TERRDEBUG:     eType = LOG_DEBUG; break;
                    case TERRNONE:      eType = LOG_INFO; break;
                }

                syslog(eType, writebuf);
                rc = 1;
#endif
                this->setp(buffer, buffer + bufsize - 1);
            }

            return rc;
        }

        char buffer[bufsize];
};
#endif

TStreamError::TStreamError(const string& logFile, const std::string& logLevel)
{
    if (!TConfig::isInitialized())
        return;

    if (!logFile.empty())
        mLogfile = logFile;
    else if (!TConfig::getLogFile().empty())
        mLogfile = TConfig::getLogFile();

    if (!logLevel.empty())
        setLogLevel(logLevel);
    else if (!TConfig::getLogLevel().empty())
        setLogLevel(TConfig::getLogFile());

    _init();
}

TStreamError::~TStreamError()
{
    if (mStream && mStream != &std::cout)
    {
        delete mStream;
        mStream = nullptr;
        mInitialized = false;
    }
}

void TStreamError::setLogFile(const std::string &lf)
{
    if (mInitialized && mLogfile.compare(lf) == 0)
        return;

    mLogfile = lf;
    mInitialized = false;
    _init();
}

void TStreamError::setLogLevel(const std::string& slv)
{
    size_t pos = slv.find("|");
    size_t start = 0;
    string lv;
    mLogLevel = 0;

    while (pos != string::npos)
    {
        lv = slv.substr(start, pos - start);
        start = pos + 1;
        mLogLevel |= _getLevel(lv);
        pos = slv.find("|", start);
    }

    mLogLevel |= _getLevel(slv.substr(start));
}

bool TStreamError::checkFilter(terrtype_t err)
{
    if (!TConfig::isInitialized())
        return false;

    if (err == TERRINFO && (mLogLevel & HLOG_INFO) != 0)
        return true;
    else if (err == TERRWARNING && (mLogLevel & HLOG_WARNING) != 0)
        return true;
    else if (err == TERRERROR && (mLogLevel & HLOG_ERROR) != 0)
        return true;
    else if (err == TERRTRACE && (mLogLevel & HLOG_TRACE) != 0)
        return true;
    else if (err == TERRDEBUG && (mLogLevel & HLOG_DEBUG) != 0)
        return true;

    return false;
}

bool TStreamError::checkFilter(int lv)
{
    if (!TConfig::isInitialized())
        return false;

    if ((mLogLevel & lv) != 0)
        return true;

    return false;
}

unsigned int TStreamError::_getLevel(const std::string& slv)
{
    if (slv.compare(SLOG_NONE) == 0)
        return HLOG_NONE;

    if (slv.compare(SLOG_INFO) == 0)
        return HLOG_INFO;

    if (slv.compare(SLOG_WARNING) == 0)
        return HLOG_WARNING;

    if (slv.compare(SLOG_ERROR) == 0)
        return HLOG_ERROR;

    if (slv.compare(SLOG_TRACE) == 0)
        return HLOG_TRACE;

    if (slv.compare(SLOG_DEBUG) == 0)
        return HLOG_DEBUG;

    if (slv.compare(SLOG_PROTOCOL) == 0)
        return HLOG_PROTOCOL;

    if (slv.compare(SLOG_ALL) == 0)
        return HLOG_ALL;

    return HLOG_NONE;
}

void TStreamError::_init()
{
    if (!TConfig::isInitialized() || mInitialized)
        return;

    mInitialized = true;

#if LOGPATH == LPATH_FILE
    if (!mLogfile.empty())
    {
        try
        {
#ifndef __ANDROID__
            if (mStream && mStream != &std::cout)
                delete mStream;
#if __cplusplus < 201402L
            mStream = new std::ofstream(mLogfile.c_str(), std::ios::out | std::ios::ate);
#else
            mStream = new std::ofstream(mLogfile, std::ios::out | std::ios::ate);
#endif
            if (!mStream || mStream->fail())
                mStream = &std::cout;
#else
            char *HOME = getenv("HOME");
            bool bigLog = false;
            uint logLevel = _getLevel(TConfig::getLogLevel());

            if ((logLevel & HLOG_TRACE) || (logLevel & HLOG_DEBUG))
                bigLog = true;

            if (HOME && !bigLog && mLogfile.find(HOME) == string::npos)
            {
                if (mStream && mStream != &std::cout)
                    delete mStream;

                mStream = new std::ofstream(mLogfile.c_str(), std::ios::out | std::ios::ate);

                if (!mStream || mStream->fail())
                {
                    std::cout.rdbuf(new androidbuf);
                    mStream = &std::cout;
                }
            }
            else
            {
                std::cout.rdbuf(new androidbuf);
                mStream = &std::cout;
            }
#endif  // __ANDROID__
        }
        catch (std::exception& e)
        {
#ifdef __ANDROID__
            __android_log_print(ANDROID_LOG_ERROR, "tpanel", "ERROR: %s", e.what());
#else
            std::cerr << "ERROR: " << e.what() << std::endl;
#endif  // __ANDROID__
            mStream = &std::cout;
        }
    }
    else if (!mStream)
    {
#ifdef __ANDROID__
        std::cout.rdbuf(new androidbuf);
#endif
        mStream = &std::cout;
    }
#else  // LOGPATH == LPATH_FILE
    if (!mStream)
    {
#ifdef __ANDROID__
        std::cout.rdbuf(new androidbuf);
#endif
        mStream = &std::cout;
    }
#endif  // LOGPATH == LPATH_FILE

    if (!TConfig::isLongFormat())
        *mStream << "Logfile started at " << getTime() << std::endl;

    *mStream << TConfig::getProgName() << " version " << V_MAJOR << "." << V_MINOR << "." << V_PATCH << std::endl;
    *mStream << "(C) Copyright by Andreas Theofilu <andreas@theosys.at>" << std::endl << " " << std::endl;

    if (TConfig::isLongFormat())
        *mStream << "Timestamp           Type LNr., File name           , Message" << std::endl;
    else
        *mStream << "Type LNr., Message" << std::endl;

    *mStream << "-----------------------------------------------------------------" << std::endl << std::flush;
}

void TStreamError::logMsg(std::ostream& str)
{
    if (!TConfig::isInitialized())
        return;

    _init();

    if (!mStream || str.fail())
        return;

    // Print out the message
    std::stringstream s;
    s << str.rdbuf() << std::ends;
    *mStream << s.str() << std::flush;
    resetFlags(mStream);
}

std::ostream *TStreamError::resetFlags(std::ostream *os)
{
    *os << std::resetiosflags(std::ios::boolalpha) <<
           std::resetiosflags(std::ios::showbase) <<
           std::resetiosflags(std::ios::showpoint) <<
           std::resetiosflags(std::ios::showpos) <<
           std::resetiosflags(std::ios::skipws) <<
           std::resetiosflags(std::ios::unitbuf) <<
           std::resetiosflags(std::ios::uppercase) <<
           std::resetiosflags(std::ios::dec) <<
           std::resetiosflags(std::ios::hex) <<
           std::resetiosflags(std::ios::oct) <<
           std::resetiosflags(std::ios::fixed) <<
           std::resetiosflags(std::ios::scientific) <<
           std::resetiosflags(std::ios::internal) <<
           std::resetiosflags(std::ios::left) <<
           std::resetiosflags(std::ios::right) <<
           std::setfill(' ');
    return os;
}

void TStreamError::decIndent()
{
    if (mIndent > 0)
        mIndent--;
}

string TStreamError::getTime()
{
    time_t rawtime;
    struct tm * timeinfo;
    char buffer[80];

    time(&rawtime);
    timeinfo = localtime(&rawtime);

    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo);
    string str(buffer);
    return str;
}

std::ostream& indent(std::ostream& os)
{
    if (os.fail())
        return os;

    if (TStreamError::getIndent() > 0)
        os << std::setw(TStreamError::getIndent()) << " ";

    return os;
}

/********************************************************************/

std::mutex tracer_mutex;

TTracer::TTracer(const std::string& msg, int line, char *file)
{
    if (!TConfig::isInitialized() || !TStreamError::checkFilter(HLOG_TRACE))
        return;

    tracer_mutex.lock();

    mFile = file;
    size_t pos = mFile.find_last_of("/");

    if (pos != string::npos)
        mFile = mFile.substr(pos + 1);

    TError::setErrorType(TERRTRACE);
#if LOGPATH == LPATH_FILE
    if (!TConfig::isLongFormat())
        TError::Current()->logMsg(*TStreamError::getStream() << "TRC " << std::setw(5) << std::right << line << ", " << indent << "{entry " << msg << std::endl);
    else
        TError::Current()->logMsg(*TStreamError::getStream() << TStreamError::getTime() <<  " TRC " << std::setw(5) << std::right << line << ", " << std::setw(20) << std::left << mFile << ", " << indent << "{entry " << msg << std::endl);
#else
    std::stringstream s;

    if (!TConfig::isLongFormat())
        s  << "TRC " << std::setw(5) << std::right << line << ", " << &indents << "{entry " << msg << std::endl;
    else
        s << TStreamError::getTime() <<  " TRC " << std::setw(5) << std::right << line << ", " << std::setw(20) << std::left << mFile << ", " << &indents << "{entry " << msg << std::endl;

    TError::Current()->logMsg(s);
#endif
    TError::Current()->incIndent();
    mHeadMsg = msg;
    mLine = line;

    if (TConfig::getProfiling())
        mTimePoint = std::chrono::steady_clock::now();

    tracer_mutex.unlock();
}

TTracer::~TTracer()
{
    if (!TConfig::isInitialized() || !TStreamError::checkFilter(HLOG_TRACE))
        return;

    tracer_mutex.lock();
    TError::setErrorType(TERRTRACE);
    TError::Current()->decIndent();
    string nanosecs;

    if (TConfig::getProfiling())
    {
        std::chrono::steady_clock::time_point endPoint = std::chrono::steady_clock::now();
        std::chrono::nanoseconds difftime = endPoint - mTimePoint;
        std::chrono::seconds secs = std::chrono::duration_cast<std::chrono::seconds>(difftime);
        std::chrono::milliseconds msecs = std::chrono::duration_cast<std::chrono::milliseconds>(difftime) - std::chrono::duration_cast<std::chrono::seconds>(secs);
        std::stringstream s;
        s << std::chrono::duration_cast<std::chrono::nanoseconds> (difftime).count() << "[ns]" << " --> " << std::chrono::duration_cast<std::chrono::seconds>(secs).count() << "s " << std::chrono::duration_cast<std::chrono::milliseconds>(msecs).count() << "ms";
        nanosecs = s.str();
    }

#if LOGPATH == LPATH_FILE
    if (TConfig::getProfiling())
    {
        if (!TConfig::isLongFormat())
            TError::Current()->logMsg(*TStreamError::getStream() << "TRC      , " << indent << "}exit " << mHeadMsg << " Elapsed time: " << nanosecs << std::endl);
        else
            TError::Current()->logMsg(*TStreamError::getStream() << TStreamError::getTime() << " TRC      , " << std::setw(20) << std::left << mFile << ", " << indent << "}exit " << mHeadMsg << " Elapsed time: " << nanosecs << std::endl);
    }
    else
    {
        if (!TConfig::isLongFormat())
            TError::Current()->logMsg(*TStreamError::getStream() << "TRC      , " << indent << "}exit " << mHeadMsg << std::endl);
        else
            TError::Current()->logMsg(*TStreamError::getStream() << TStreamError::getTime() << " TRC      , " << std::setw(20) << std::left << mFile << ", " << indent << "}exit " << mHeadMsg << std::endl);
    }
#else
    std::stringstream s;

    if (!TConfig::isLongFormat())
        s << "TRC      , " << &indents << "}exit " << mHeadMsg;
    else
        s << TStreamError::getTime() << " TRC      , " << std::setw(20) << std::left << mFile << ", " << &indents << "}exit " << mHeadMsg;

    if (TConfig::getProfiling())
        s  << " Elapsed time: " << nanosecs << std::endl;
    else
        s << std::endl;

    TError::Current()->logMsg(s);
#endif
    mHeadMsg.clear();
    tracer_mutex.unlock();
}

/********************************************************************/

TError::~TError()
{
    if (mCurrent)
    {
        delete mCurrent;
        mCurrent = nullptr;
    }
}

void TError::lock()
{
    message_mutex.lock();
}

void TError::unlock()
{
    message_mutex.unlock();
}

TStreamError* TError::Current()
{
    if (!mCurrent)
        mCurrent = new TStreamError(TConfig::getLogFile(), TConfig::getLogLevel());

    return mCurrent;
}

void TError::logHex(char* str, size_t size)
{
    if (!str || !size)
        return;

    message_mutex.lock();

    if (!Current())
    {
        message_mutex.unlock();
        return;
    }
    // Print out the message
    std::ostream *stream = mCurrent->getStream();
    *stream << strToHex(str, size, 16, true, 12) << std::endl;
    *stream << mCurrent->resetFlags(stream);
    message_mutex.unlock();
}

string TError::toHex(int num, int width)
{
    string ret;
    std::stringstream stream;
    stream << std::setfill ('0') << std::setw(width) << std::hex << num;
    ret = stream.str();
    return ret;
}

string TError::strToHex(const char *str, size_t size, int width, bool format, int indent)
{
    int len = 0, pos = 0, old = 0;
    int w = (format) ? 1 : width;
    string out, left, right;
    string ind;

    if (indent > 0)
    {
        for (int j = 0; j < indent; j++)
            ind.append(" ");
    }

    for (size_t i = 0; i < size; i++)
    {
        if (len >= w)
        {
            left.append(" ");
            len = 0;
        }

        if (format && i > 0 && (pos % width) == 0)
        {
            out += ind + toHex(old, 4) + ": " + left + " | " + right + "\n";
            left.clear();
            right.clear();
            old = pos;
        }

        int c = *(str+i) & 0x000000ff;
        left.append(toHex(c, 2));

        if (format)
        {
            if (std::isprint(c))
                right.push_back(c);
            else
                right.push_back('.');
        }

        len++;
        pos++;
    }

    if (!format)
        return left;
    else if (pos > 0)
    {
        if ((pos % width) != 0)
        {
            for (int i = 0; i < (width - (pos % width)); i++)
                left.append("   ");
        }

        out += ind + toHex(old, 4)+": "+left + "  | " + right;
    }

    return out;
}

void TError::setErrorMsg(const std::string& msg)
{
    if (msg.empty())
        return;

    msError = msg;
    mHaveError = true;
    mErrType = TERRERROR;
}

void TError::setErrorMsg(terrtype_t t, const std::string& msg)
{
    if (msg.empty())
        return;

    msError = msg;
    mHaveError = true;
    mErrType = t;
}

std::ostream & TError::append(int lv, std::ostream& os)
{
    std::string prefix;
    Current();

    switch (lv)
    {
        case HLOG_PROTOCOL: prefix = "PRT    ++, "; mErrType = TERRINFO; break;
        case HLOG_INFO:     prefix = "INF    >>, "; mErrType = TERRINFO; break;
        case HLOG_WARNING:  prefix = "WRN    !!, "; mErrType = TERRWARNING; break;
        case HLOG_ERROR:    prefix = "ERR *****, "; mErrType = TERRERROR; break;
        case HLOG_TRACE:    prefix = "TRC      , "; mErrType = TERRTRACE; break;
        case HLOG_DEBUG:    prefix = "DBG    --, "; mErrType = TERRDEBUG; break;

        default:
            prefix = "           ";
            mErrType = TERRNONE;
    }

    if (!TConfig::isInitialized() && (lv == HLOG_ERROR || lv == HLOG_WARNING))
    {
        if (!TConfig::isLongFormat())
            std::cerr << prefix;
        else
            std::cerr << TStreamError::getTime() << " " << prefix << std::setw(20) << " " << ", ";
    }

    if (!TConfig::isLongFormat())
        return os << prefix;
    else
        return os << TStreamError::getTime() << " " << prefix << std::setw(20) << " " << ", ";
}

#if defined(__linux__) || defined(Q_OS_ANDROID)
void TError::displayMessage(const std::string& msg)
{
    QMessageBox m;
    m.setText(msg.c_str());

    int cnt = 10;

    QTimer cntDown;
    QObject::connect(&cntDown, &QTimer::timeout, [&m, &cnt, &cntDown]()->void
        {
            if (--cnt < 0)
            {
                cntDown.stop();
                m.close();
            }
        });

        cntDown.start(1000);
        m.exec();
}
#endif