• 写一个Windows上的守护进程(4)日志其余


    写一个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日星期日

  • 相关阅读:
    mac c++编译出现segmentation fault :11错误
    ssh 连接缓慢解决方法
    237. Delete Node in a Linked List
    203. Remove Linked List Elements
    Inversion of Control Containers and the Dependency Injection pattern
    82. Remove Duplicates from Sorted List II
    83. Remove Duplicates from Sorted List
    SxsTrace
    使用CCleaner卸载chrome
    decimal and double ToString problem
  • 原文地址:https://www.cnblogs.com/mkdym/p/4927324.html
Copyright © 2020-2023  润新知