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

写一个Windows上的守护进程(4)日志其余的更多相关文章

  1. 写一个Windows上的守护进程(8)获取进程路径

    写一个Windows上的守护进程(8)获取进程路径 要想守护某个进程,就先得知道这个进程在不在.我们假设要守护的进程只会存在一个实例(这也是绝大部分情形). 我是遍历系统上的所有进程,然后判断他们的路 ...

  2. 写一个Windows上的守护进程(7)捕获异常并生成dump

    写一个Windows上的守护进程(7)捕获异常并生成dump 谁都不能保证自己的代码不出bug.一旦出了bug,最好是崩溃掉,这样很快就能被发现,若是不崩溃,只是业务处理错了,就麻烦了,可能很长时间之 ...

  3. 写一个Windows上的守护进程(6)Windows服务

    写一个Windows上的守护进程(6)Windows服务 守护进程因为要开机启动,还要高权限,所以我就把它做成Windows服务了. 关于Windows服务的官方文档,大家可以看https://msd ...

  4. 写一个Windows上的守护进程(5)文件系统重定向

    写一个Windows上的守护进程(5)文件系统重定向 在Windows上经常操作文件或注册表的同学可能知道,有"文件系统/注册表重定向"这么一回事.大致来说就是32位程序在64位的 ...

  5. 写一个Windows上的守护进程(3)句柄的管理

    写一个Windows上的守护进程(3)句柄的管理 在Windows中编程,跟HANDLE打交道是家常便饭.为了防止忘记CloseHandle,我都是使用do-while-false手法: void f ...

  6. 写一个Windows上的守护进程(2)单例

    写一个Windows上的守护进程(2)单例 上一篇的日志类的实现里有个这: class Singleton<CLoggerImpl> 看名字便知其意--单例.这是一个单例模板类. 一个进程 ...

  7. 写一个Windows上的守护进程(1)开篇

    写一个Windows上的守护进程(1)开篇 最近由于工作需要,要写一个守护进程,主要就是要在被守护进程挂了的时候再把它启起来.说起来这个功能是比较简单的,但是我前一阵子写了好多现在回头看起来比较糟糕的 ...

  8. windows下bat批处理实现守护进程(有日志)

    开发部的一个核心程序总是会自己宕机,然后需要手工去起,而这个服务的安全级别又很高,只有我可以操作,搞得我晚上老没法睡,昨晚实在受不了了,想起以前在hp-ux下写的shell守护进程,这回搞个windo ...

  9. 写一个Windows服务

    做了两个和Windows服务有关的项目了,最开始的时候没做过,不懂,现在明白了许多.需要注意的是,如果不想登录什么的,最后在添加安装程序的那里选择那个字长的右键属性,把启动方式改为local syst ...

随机推荐

  1. 自动生成代码工具【JAVA版】

    发现任何项目无非五类操作:新增.修改.删除.查询详细.查询列表 大多数的服务端基础代码都是相同的,但是每次开发一个新项目都会做很多重复工作,从controller,bean,service,到数据库访 ...

  2. 克隆contos 出现 Error:No suitable device found: no device found for connection &amp;quot;System eth0&amp

    二.问题 这时我复制好的虚拟机,启动登陆进去(用户名和密码跟之前那台是一样的),修改好IPADDR,然后网卡重启出现问题? #service network restart 出现问题:Error:No ...

  3. django-filter 使用Filter来筛选你的数据

    django-filter Django-filter is a generic, reusable application to alleviate writing some of the more ...

  4. NAS4Free 安装配置(一)开箱图

    拆箱记录 东西不错,做工很好 包装箱 背面 正面(未装前面板) 底部 前面板打开后 打开上盖 开机正面图

  5. 根据identifier从StoryBoard中获取对象,UIButton的图片文件位置

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  6. 执行yiic webapp命令时报错:php.exe不是内部或外部命令,也不是可运行的程序

    在执行 yiic webapp ../abc 命令时报错: “php.exe”不是内部或外部命令,也不是可运行的程序 或批处理文件. 这是因为yiic批处理程序找不到php.exe的执行路径引起的. ...

  7. 新唐的icp和isp

    ICP 的意思是 "在电路编程"(in Circuit Programming),PC 电脑可以运行 “NuMicro ICP Programming Tool”软件通过 Nu-L ...

  8. base64计算

    zjzc01:/root# cat aa.pl use URI::Escape; use URI::Escape qw(uri_unescape); use MIME::Base64; use Dig ...

  9. jquery-ui datepicker使用

    这是一款老外设计的日期控件 很多显示方式都是国外的 需要自己调整一下 closeText: "Done", prevText: "上一月", nextText: ...

  10. 《Algorithms 4th Edition》读书笔记——2.4 优先队列(priority queue)-Ⅱ

    2.4.2初级实现 我们知道,基础数据结构是实现优先队列的起点.我们可以是使用有序或无序的数组或链表.在队列较小时,大量使用两种主要操作之一时,或是所操作元素的顺序已知时,它们十分有用.因为这些实现相 ...