程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 寫一個Windows上的守護進程(4)日志其余,windows守護進程

寫一個Windows上的守護進程(4)日志其余,windows守護進程

編輯:C++入門知識

寫一個Windows上的守護進程(4)日志其余,windows守護進程


寫一個Windows上的守護進程(4)日志其余

這次把和日志相關的其他東西一並說了。

 

一、vaformat

C++日志接口通常有兩種形式:流輸入形式,printf形式。

我采用printf形式,因為流輸入不好控制格式。

printf形式要求日志接口支持不定長參數,我沒有直接在日志實現類裡邊支持不定長參數,而是只接受一個字符串參數,可以參見第一篇。

為什麼呢?

如果要成為不定長參數,就是這樣

bool log_string(const LOG_LEVEL level, const char* file, const int line, const char *s, ...);

那麼在每一個log_xxx的變體裡就都要寫_vsnprintf_s那一套代碼了,而且是完全一樣的(我不知道__VA_ARGS__宏是否可以傳遞),這顯然是不好的做法。

我把不定長參數的處理放在了宏定義裡,類似:

#define ErrorLog(s, ...) _Log(LOG_ERROR, __FILE__, __LINE__, vaformat(MAX_LOG_BUFFER, s, __VA_ARGS__))

vaformat就是處理不定長參數的:

std::string vaformat(const size_t max_size, const char* msg, ...);
std::wstring vaformat(const size_t max_size, const wchar_t* wmsg, ...);

因為並不知道格式化後有多長,所以要指定最大長度,如果格式化後的長度大於最大長度,則截斷。vaformat裡還有一個小技巧,當指定的max_size小於1024的時候,使用棧空間,否則申請堆內存,這是從std::string的實現中學來的——SSO短字符串優化。

可以看下vaformat的實現,兩個版本的代碼基本一樣,這樣當然是不好的,但是我不知道怎樣把他們合並起來,這是一個todo。類似的問題下面還有。

 

二、CLastErrorFormat

這個東西是用來解決第一篇裡提到的記錄LastErrorCode的問題的。

它的主要功能就是把error code轉換成文本描述,合適的構造也可以省去GetLastError的調用:

class CLastErrorFormat : public boost::noncopyable
{
public:
    CLastErrorFormat()
        : m_code(GetLastError())
    {
    }

    CLastErrorFormat(const DWORD code)
        : m_code(code)
    {
    }

    ~CLastErrorFormat()
    {
    }

public:
    const DWORD code() const
    {
        return m_code;
    }

    const std::string& str()
    {
        //...
    }

    const std::wstring& wstr()
    {
        //...
    }

private:
    //...
};

日志實現類裡對應的接口:

bool log_last_error(const LOG_LEVEL level, const char* file, const int line, CLastErrorFormat& e, const std::string& prefix);

接受一個CLastErrorFormat的引用,然後在記錄日志的時候,把error code和其對應的描述也記錄下來:xxx, error code: 999, error msg: yyy

最終的日志接口是有兩個版本的:一個接受一個CLastErrorFormat參數;另一個省去,在函數內部自己構造。

 

三、str_encode

我不可能在日志文件裡一會記寬字符串,一會記窄字符串,那就沒法看了,又考慮到日志文件的大小,我最終決定,按照窄字符串SystemCurrentCodePage(在簡體中文版的Windows上,就是GB2312)編碼記錄日志,所以對於寬字符串我還要轉換成窄字符串。

Windows提供了兩個API來做編碼轉換:MultiByteToWideChar和WideCharToMultiByte,而這兩個API總是要兩次調用才能安全的轉換。我將其稍稍封裝了一下,做成了兩個函數:

std::wstring multistr2widestr(const unsigned int from_code_page, const std::string& s);
std::string widestr2multistr(const unsigned int to_code_page, const std::wstring& ws, const char *default_char = NULL);

注:SystemCurrentCodePage的代碼頁編碼就是CP_ACP。

這樣在記寬字符串的時候總是會慢一些,所以我代碼中,能用窄字符串的地方我都用窄字符串了。

 

四、any_lexical_cast

代碼中總是免不了要做類型轉換,特別是把數字轉換成字符串,為了簡單一點,我使用了boost的lexical_cast,雖然大家都說這貨效率低,因為使用了C++的流,但是我堅持“先正確,再優化”的原則,還是使用了它。

然而,這個東西使用起來有兩處不便:

1. 轉換失敗的時候會拋異常

2. 把bool轉換成string的時候是0或1,不是true或false

 

為了解決這兩個問題,我又做了一下封裝:

1. 轉換失敗的時候,填充為默認值。調用者必須提供默認值

2. 特化對於bool和string之間的轉換

 

這就是any_lexical_cast:

template<typename Target, typename Source>
Target any_lexical_cast(const Source& src, const Target& fail_value)
{
    Target value = fail_value;
    try
    {
        value = boost::lexical_cast<Target>(src);
    }
    catch (boost::bad_lexical_cast&)
    {
        value = fail_value;
    }
    return value;
}

template<>
bool any_lexical_cast<bool, std::string>(const std::string& src, const bool& fail_value);

template<>
bool any_lexical_cast<bool, std::wstring>(const std::wstring& src, const bool& fail_value);

template<>
std::string any_lexical_cast<std::string, bool>(const bool& src, const std::string&);

template<>
std::wstring any_lexical_cast<std::wstring, bool>(const bool& src, const std::wstring&);

具體實現請參看源碼。

 

五、CSelfPath

日志初始化接口通常需要提供一個路徑參數,以指定日志存放路徑。我為其增加了一個默認路徑:當傳遞空字符串時,將日志文件放在應用程序所在路徑的log目錄下,若log目錄不存在,則先創建。

獲取應用程序所在路徑本可以放在日志模塊內部,但考慮到別的地方可能也會用到,而且應用程序一旦啟動,路徑就不會變,所以就做成了一個單例類CSelfPath。

CSelfPath僅在構造函數中調用GetModuleFileNameA一次獲取路徑並分割成目錄、文件名等等部分。

 

六、CLoggerImpl與Logger

日志的實現類裡邊有好多東西我都不想給調用者看到,典型如private的成員;還有日志實現類的接口並不易用。所以我在日志實現類和調用者之間又引入了一個間接層Logger,它的主要作用就是隱藏日志實現類和使接口更“親民”。當然除了這個我還給了它一些別的功能:控制日志輸出級別。Logger並不是一個類。

 

七、Disable 3rd party library warning

我在使用boost關於string的algorithm的時候,發現編譯器會大段的警告,這來自boost庫中對std::copy的使用,而我明確的知道boost庫的這段代碼是正確的。這些警告又多又煩人,有沒有安全的辦法消除這個警告?

肯定有了:

#pragma warning(push)
#pragma warning(disable:4996)
#include <boost/algorithm/string.hpp>
#pragma warning(pop)

上面的代碼保存為一個頭文件:boost_algorithm_string.h。以後要包含boost/algorithm/string.hpp時,均以boost_algorithm_string.h代替。

 

源碼:https://git.oschina.net/mkdym/DaemonSvc.git (主)&& https://github.com/mkdym/DaemonSvc.git (提升逼格用的)。

 

2015年11月1日星期日

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved