muduo網路庫學習之Logger類、LogStream類、LogFile類封裝中的知識點

s1mba發表於2013-11-02

一、Logger類、LogStream類

1、日誌作用

開發過程中:
除錯錯誤
更好的理解程式
執行過程中:
診斷系統故障並處理
記錄系統執行狀態

2、日誌級別

TRACE
指出比DEBUG粒度更細的一些資訊事件(開發過程中使用)
DEBUG
指出細粒度資訊事件對除錯應用程式是非常有幫助的。(開發過程中使用)
INFO
表明訊息在粗粒度級別上突出強調應用程式的執行過程。
WARN
系統能正常執行,但可能會出現潛在錯誤的情形。
ERROR
指出雖然發生錯誤事件,但仍然不影響系統的繼續執行。
FATAL
指出每個嚴重的錯誤事件將會導致應用程式的退出。


class Logger
{
public:
enum LogLevel
  {
    TRACE,
    DEBUG,
    INFO,
    WARN,
    ERROR,
    FATAL,
    NUM_LOG_LEVELS,
  };
// compile time calculation of basename of source file
class SourceFile { };
private:
       class Impl { };
};


template<int SIZE>
class FixedBuffer : boost::noncopyable

class LogStream : boost::noncopyable
{
typedef LogStream self;
public:                                        // 4000
 typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer;
};

class Fmt // : boost::noncopyable
{
public:
  template<typename T>
  Fmt(const char* fmt, T val)
  {     // 按照fmt 格式將val 格式化成字串放入buf_中
      length_ = snprintf(buf_, sizeof buf_, fmt, val);
  };
};

3、formatInteger()  //是把數字或者指標的值當作字串寫入
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
 

const char digits[] = "9876543210123456789";
const char* zero = digits + 9;
const char digitsHex[] = "0123456789ABCDEF";

LogStream& LogStream::operator<<(int v)
{
  formatInteger(v);
  return *this;
}

template<typename T>
void LogStream::formatInteger(T v)
{                       //32
  if (buffer_.avail() >= kMaxNumericSize)
  {
    size_t len = convert(buffer_.current(), v);
    buffer_.add(len);
  }
}

// Efficient Integer to String Conversions, by Matthew Wilson.
template<typename T>
size_t convert(char buf[], T value)
{
  T i = value;
  char* p = buf;

  do
  {
    int lsd = static_cast<int>(i % 10);
    i /= 10;
    *p++ = zero[lsd];
  } while (i != 0);

  if (value < 0)
  {
    *p++ = '-';
  }
  *p = '\0';
  std::reverse(buf, p);

  return p - buf;
}

LogStream& LogStream::operator<<(const void* p)
{
  uintptr_t v = reinterpret_cast<uintptr_t>(p);
  if (buffer_.avail() >= kMaxNumericSize)
  {
    char* buf = buffer_.current();
    buf[0] = '0';
    buf[1] = 'x';
    size_t len = convertHex(buf+2, v);
    buffer_.add(len+2);
  }
  return *this;
}

// Efficient Pointer to String Conversions
// uintptr_t on 32bit as unsigned int; on 64bit as unsigned long int
size_t convertHex(char buf[], uintptr_t value)
{
  uintptr_t i = value;
  char* p = buf;

  do
  {
    int lsd = i % 16;
    i /= 16;
    *p++ = digitsHex[lsd];
  } while (i != 0);

  *p = '\0';
  std::reverse(buf, p);

  return p - buf;
}


4、Logger使用時序圖:


#define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) \
  muduo::Logger(__FILE__, __LINE__).stream()

LOG_INFO<<“info ...”;     // 使用方式
muduo::Logger(__FILE__, __LINE__).stream()<<“info”;

// LogStream& stream() { return impl_.stream_; }

Logger => Impl => LogStream => operator<< FixedBuffer => g_output => g_flush

棧上匿名的Logger物件使用完就要析構,在~Logger()中呼叫 g_output,即 g_output(buf.data(), buf.length());
如果是FATAL錯誤,還要呼叫g_flush,最後abort()程式。

如果沒有呼叫g_flush,會一直輸出到緩衝區(標準輸出緩衝區,檔案FILE緩衝區)滿才會真的輸出在標準輸出,或者寫入到檔案中去。注:可以使用setvbuf設定緩衝區的大小。
int setvbuf ( FILE * stream, char * buffer, int mode, size_t size );

預設日誌資訊輸出到標準輸出(g_output = defaultOutput、g_flush = defaultFlush),也可以輸出到檔案,使用SetOutput、SetFlush 設定。

測試程式:
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
#include <muduo/base/Logging.h>
#include <errno.h>
#include <stdio.h>

using namespace muduo;

FILE *g_file;

void dummyOutput(const char *msg, int len)
{
    if (g_file)
    {
        fwrite(msg, 1, len, g_file);
    }
}

void dummyFlush()
{
    fflush(g_file);
}

int main()
{
    g_file = ::fopen("/tmp/muduo_log""ae");
    Logger::setOutput(dummyOutput);
    Logger::setFlush(dummyFlush);

    LOG_TRACE << "trace ...";
    LOG_DEBUG << "debug ...";
    LOG_INFO << "info ...";
    LOG_WARN << "warn ...";
    LOG_ERROR << "error ...";
    //LOG_FATAL<<"fatal ...";
    errno = 13;
    LOG_SYSERR << "syserr ...";
    //LOG_SYSFATAL<<"sysfatal ...";

    ::fclose(g_file);

    return 0;
}

程式執行後檢視寫入檔案的內容:
simba@ubuntu:~/Documents/build/debug/bin$ cat /tmp/muduo_log 
20131102 12:33:29.812878Z  4021 TRACE main trace ... - Log_test2.cc:28
20131102 12:33:29.813108Z  4021 DEBUG main debug ... - Log_test2.cc:29
20131102 12:33:29.813112Z  4021 INFO  info ... - Log_test2.cc:30
20131102 12:33:29.813114Z  4021 WARN  warn ... - Log_test2.cc:31
20131102 12:33:29.813117Z  4021 ERROR error ... - Log_test2.cc:32
20131102 12:33:29.813120Z  4021 ERROR Permission denied (errno=13) syserr ... - Log_test2.cc:35
simba@ubuntu:~/Documents/build/debug/bin$ 

5、StringPiece類

 // We provide non-explicit singleton constructors so users can pass
  // in a "const char*" or a "string" wherever a "StringPiece" is
  // expected.

只是複製字串的指標,故不涉及具體字串記憶體的拷貝,高效地傳遞字串。

class StringPiece {
private:
  const char*   ptr_;
  int           length_;
....
 C++ Code 
1
2
3
4
5
6
7
8
9
10
 
#define STRINGPIECE_BINARY_PREDICATE(cmp,auxcmp)                             \
  bool operator cmp (const StringPiece& x) const {                           \
    int r = memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_); \
    return ((r auxcmp 0) || ((r == 0) && (length_ cmp x.length_)));          \
  }
STRINGPIECE_BINARY_PREDICATE( < ,  < );
STRINGPIECE_BINARY_PREDICATE( <= , < );
STRINGPIECE_BINARY_PREDICATE( >= , > );
STRINGPIECE_BINARY_PREDICATE( > ,  > );
#undef STRINGPIECE_BINARY_PREDICATE

};

二、LogFile類

日誌滾動條件
檔案大小(例如每寫滿1G換下一個檔案)
時間(每天零點新建一個日誌檔案,不論前一個檔案是否寫滿)
一個典型的日誌檔名
logfile_test.20130411-115604.popo.7743.log
// 執行程式.時間.主機名.執行緒名.log

class LogFile : boost::noncopyable

const string basename_; // 日誌檔案 basename

const size_t rollSize_; // 日誌檔案達到rollSize_換一個新檔案

const int flushInterval_; // 日誌寫入檔案間隔時間

time_t startOfPeriod_; // 開始記錄日誌時間(調整到零時時間)

time_t lastRoll_; // 上一次滾動日誌檔案時間

time_t lashFlush_; // 上一次日誌寫入檔案時間

                                  // 60*60*24
time_t start = now / kRollPerSeconds_ * kRollPerSeconds_;
表示start對齊到kR的整數倍,也就是時間調整到當天零時

// not thread safe
class LogFile::File : boost::noncopyable

 ::setbuffer(fp_, buffer_, sizeof buffer_);

測試程式:

LogFile_test.cc:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 
#include <muduo/base/LogFile.h>
#include <muduo/base/Logging.h>

boost::scoped_ptr<muduo::LogFile> g_logFile;

void outputFunc(const char *msg, int len)
{
    g_logFile->append(msg, len);
// scoped_ptr<T> 過載operator->,呼叫LogFile::append(), 
// 間接呼叫File::append(); 最後 ::fwrite_unlocked(fp_);
}

void flushFunc()
{
    g_logFile->flush();
// scoped_ptr<T> 過載operator->,呼叫LogFile::flush(), 
//間接呼叫File::flush(),最後::fflush(fp_);
}

int main(int argc, char *argv[])
{
    char name[256];
    strncpy(name, argv[0], 256);
    g_logFile.reset(new muduo::LogFile(::basename(name), 200 * 1000));
    muduo::Logger::setOutput(outputFunc);
    muduo::Logger::setFlush(flushFunc);

    muduo::string line = "1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ ";

    for (int i = 0; i < 10000; ++i)
    {
        LOG_INFO << line << i;
// 不斷地構造匿名的Logger物件,在~Logger()中呼叫dummyOutput,將日誌資訊寫入檔案

        usleep(1000);
    }
}

執行程式後檢視建立的日誌檔案:

simba@ubuntu:~/Documents/build/debug/bin$ ls -lh *.log
-rw-rw-r-- 1 simba simba 196K Nov  2 05:37 logfile_test.20131102-123753.ubuntu.4044.log
-rw-rw-r-- 1 simba simba 196K Nov  2 05:37 logfile_test.20131102-123756.ubuntu.4044.log
-rw-rw-r-- 1 simba simba 196K Nov  2 05:38 logfile_test.20131102-123759.ubuntu.4044.log
-rw-rw-r-- 1 simba simba 196K Nov  2 05:38 logfile_test.20131102-123802.ubuntu.4044.log
-rw-rw-r-- 1 simba simba 196K Nov  2 05:38 logfile_test.20131102-123805.ubuntu.4044.log
-rw-rw-r-- 1 simba simba 196K Nov  2 05:38 logfile_test.20131102-123808.ubuntu.4044.log
-rw-rw-r-- 1 simba simba  87K Nov  2 05:38 logfile_test.20131102-123811.ubuntu.4044.log

因為我們設定的滾動日誌檔案大小為200 *1000/1024 = 196k,所以現在即使沒有到另一個零時,因為檔案大小已到上限,也會自動滾動檔案。


參考:
muduo manual.pdf
《linux 多執行緒伺服器程式設計:使用muduo c++網路庫》


相關文章