@@ -38,6 +38,7 @@ log/ | |||||
*.idb | *.idb | ||||
*.txt | *.txt | ||||
*.pdb | *.pdb | ||||
*.dmp | |||||
# Executables | # Executables | ||||
*.exe | *.exe | ||||
@@ -42,7 +42,7 @@ class mylog | |||||
{ | { | ||||
public: | public: | ||||
//格式 | //格式 | ||||
static bool ToSet(TCHAR* pBuffer, int iLen, DWORD dUserID); | |||||
//static bool ToSet(TCHAR* pBuffer, int iLen, DWORD dUserID); | |||||
static void OutputString(TCHAR* pBuffer, int iTableID); | static void OutputString(TCHAR* pBuffer, int iTableID); | ||||
}; | }; | ||||
@@ -95,7 +95,7 @@ struct tagUserScore | |||||
_snprintf(pBuffer, iLen, TEXT("<br>【子项积分信息】用户分数:%d.") | _snprintf(pBuffer, iLen, TEXT("<br>【子项积分信息】用户分数:%d.") | ||||
, lScore); | , lScore); | ||||
#endif | #endif | ||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
////mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -185,7 +185,7 @@ struct CMD_GR_LogonMobile | |||||
_snprintf(pBuffer, iLen, TEXT("手机登录->游戏标识:%d,进程版本:%d,设备类型:%d,行为标识:%d,分页桌数:%d,用户 I D:%d,登录密码:%s,机器标识:%s.") | _snprintf(pBuffer, iLen, TEXT("手机登录->游戏标识:%d,进程版本:%d,设备类型:%d,行为标识:%d,分页桌数:%d,用户 I D:%d,登录密码:%s,机器标识:%s.") | ||||
, wGameID, dwProcessVersion, cbDeviceType, wBehaviorFlags, wPageTableCount, dwUserID, szPassword, szMachineID); | , wGameID, dwProcessVersion, cbDeviceType, wBehaviorFlags, wPageTableCount, dwUserID, szPassword, szMachineID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -236,7 +236,7 @@ struct CMD_GR_LogonAccounts | |||||
_snprintf(pBuffer, iLen, TEXT("帐号登录->广场版本:%d,框架版本:%d,进程版本:%d,登录密码:%s,登录帐号:%s,机器序列:%s.") | _snprintf(pBuffer, iLen, TEXT("帐号登录->广场版本:%d,框架版本:%d,进程版本:%d,登录密码:%s,登录帐号:%s,机器序列:%s.") | ||||
, dwProcessVersion, dwFrameVersion, dwProcessVersion, szPassword, szAccounts, szMachineID); | , dwProcessVersion, dwFrameVersion, dwProcessVersion, szPassword, szAccounts, szMachineID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -281,7 +281,7 @@ struct CMD_GR_LogonSuccess | |||||
_snprintf(pBuffer, iLen, TEXT("登录成功->用户权限:%d,管理权限:%d.") | _snprintf(pBuffer, iLen, TEXT("登录成功->用户权限:%d,管理权限:%d.") | ||||
, dwUserRight, dwMasterRight); | , dwUserRight, dwMasterRight); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -326,7 +326,7 @@ struct CMD_GR_LogonFailure | |||||
_snprintf(pBuffer, iLen, TEXT("登录失败->错误代码:%d,描述消息:%s.") | _snprintf(pBuffer, iLen, TEXT("登录失败->错误代码:%d,描述消息:%s.") | ||||
, lErrorCode, szDescribeString); | , lErrorCode, szDescribeString); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -369,7 +369,7 @@ struct CMD_GR_LogonFinish | |||||
_snprintf(pBuffer, iLen, TEXT("登录完成->引领任务:%s.") | _snprintf(pBuffer, iLen, TEXT("登录完成->引领任务:%s.") | ||||
, bGuideTask ? _T("是") : _T("否")); | , bGuideTask ? _T("是") : _T("否")); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -420,7 +420,7 @@ struct CMD_GR_UpdateNotify | |||||
_snprintf(pBuffer, iLen, TEXT("升级提示->强行升级:%d,强行升级:%d,建议升级:%d,当前版本:%d,当前版本:%d,当前版本:%d.") | _snprintf(pBuffer, iLen, TEXT("升级提示->强行升级:%d,强行升级:%d,建议升级:%d,当前版本:%d,当前版本:%d,当前版本:%d.") | ||||
, cbMustUpdatePlaza, cbMustUpdateClient, cbAdviceUpdateClient, dwCurrentPlazaVersion, dwCurrentFrameVersion, dwCurrentClientVersion); | , cbMustUpdatePlaza, cbMustUpdateClient, cbAdviceUpdateClient, dwCurrentPlazaVersion, dwCurrentFrameVersion, dwCurrentClientVersion); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -495,7 +495,7 @@ struct CMD_GR_ConfigServer | |||||
_snprintf(pBuffer, iLen, TEXT("房间配置->桌子数目:%d,椅子数目:%d,房间类型:%d,房间规则:%d.") | _snprintf(pBuffer, iLen, TEXT("房间配置->桌子数目:%d,椅子数目:%d,房间类型:%d,房间规则:%d.") | ||||
, wTableCount, wChairCount, wServerType, dwServerRule); | , wTableCount, wChairCount, wServerType, dwServerRule); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -721,7 +721,7 @@ struct JN_GR_RoomListEx | |||||
_snwprintf(pBuffer, iLen, TEXT("玩家已开房房间信息->拥有人:%d,服务器ID:%d,私有房间号:%d,添加更新删除:%s,底分:%d,局数:%d,已有人数:%d,总人数:%d,玩法:%d,bDuoLai=%d,wFengDing=%d") | _snwprintf(pBuffer, iLen, TEXT("玩家已开房房间信息->拥有人:%d,服务器ID:%d,私有房间号:%d,添加更新删除:%s,底分:%d,局数:%d,已有人数:%d,总人数:%d,玩法:%d,bDuoLai=%d,wFengDing=%d") | ||||
, dwUserID, dwServerID, dwRoomNumber, szTemp, dwDiFen, dwJuShu, dwReadyBody, dwBody, bWanFa, bDuoLai, wFengDing); | , dwUserID, dwServerID, dwRoomNumber, szTemp, dwDiFen, dwJuShu, dwReadyBody, dwBody, bWanFa, bDuoLai, wFengDing); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -794,7 +794,7 @@ struct JN_GR_RoomListExGig | |||||
_snprintf(pBuffer, iLen, TEXT("玩家已开房房间信息->拥有人:%d,服务器ID:%d,私有房间号:%d,添加更新删除:%s,底分:%d,局数:%d,已有人数:%d,总人数:%d,玩法:%d.") | _snprintf(pBuffer, iLen, TEXT("玩家已开房房间信息->拥有人:%d,服务器ID:%d,私有房间号:%d,添加更新删除:%s,底分:%d,局数:%d,已有人数:%d,总人数:%d,玩法:%d.") | ||||
, dwUserID, dwServerID, dwRoomNumber, szTemp, dwDiFen, dwJuShu, dwReadyBody, dwBody, bWanFa); | , dwUserID, dwServerID, dwRoomNumber, szTemp, dwDiFen, dwJuShu, dwReadyBody, dwBody, bWanFa); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -849,7 +849,7 @@ struct JN_GR_RoomListGroupEx | |||||
_snprintf(pBuffer, iLen, TEXT("玩家已开房房间列表->总共开房数:%d,具体房间信息:\r\n[%s].") | _snprintf(pBuffer, iLen, TEXT("玩家已开房房间列表->总共开房数:%d,具体房间信息:\r\n[%s].") | ||||
, Num, szBuffer2); | , Num, szBuffer2); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -969,7 +969,7 @@ struct tagMobileUserInfoHead | |||||
, dwGameID, dwUserID, wFaceID, dwCustomID, cbGender, cbMemberOrder, wTableID, wChairID, szTemp, lScore, dwWinCount | , dwGameID, dwUserID, wFaceID, dwCustomID, cbGender, cbMemberOrder, wTableID, wChairID, szTemp, lScore, dwWinCount | ||||
, dwLostCount, dwDrawCount, dwFleeCount, dwExperience, szNickname, szHeadUrl, strIP); | , dwLostCount, dwDrawCount, dwFleeCount, dwExperience, szNickname, szHeadUrl, strIP); | ||||
#endif | #endif | ||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
////mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -1035,7 +1035,7 @@ struct tagMobileUserInfoHead | |||||
, dwGameID, dwUserID, wFaceID, dwCustomID, cbGender, cbMemberOrder, wTableID, wChairID, szTemp, lScore, dwWinCount | , dwGameID, dwUserID, wFaceID, dwCustomID, cbGender, cbMemberOrder, wTableID, wChairID, szTemp, lScore, dwWinCount | ||||
, dwLostCount, dwDrawCount, dwFleeCount, dwExperience, szNickname, szHeadUrl, strIP); | , dwLostCount, dwDrawCount, dwFleeCount, dwExperience, szNickname, szHeadUrl, strIP); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1140,7 +1140,7 @@ struct CMD_GR_UserSitDown | |||||
_snprintf(pBuffer, iLen, TEXT("创建进入房间->限制元宝:%d,椅子位置:%d,是否新建一个桌子:%d,私有桌子号:%s.") | _snprintf(pBuffer, iLen, TEXT("创建进入房间->限制元宝:%d,椅子位置:%d,是否新建一个桌子:%d,私有桌子号:%s.") | ||||
, WXianZhi, Wsocket, cbCreateTable, szPrivateTableID); | , WXianZhi, Wsocket, cbCreateTable, szPrivateTableID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1231,7 +1231,7 @@ struct CMD_GR_UserJoin | |||||
_snprintf(pBuffer, iLen, TEXT("加入房间->桌子位置:%d,椅子位置:%d,是否新建一个桌子:%d,私有桌子号:%s.") | _snprintf(pBuffer, iLen, TEXT("加入房间->桌子位置:%d,椅子位置:%d,是否新建一个桌子:%d,私有桌子号:%s.") | ||||
, wTableID, wChairID, cbCreateTable, szPrivateTableID); | , wTableID, wChairID, cbCreateTable, szPrivateTableID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1277,7 +1277,7 @@ struct CMD_GR_GetDingDanACK | |||||
#else | #else | ||||
_snprintf(pBuffer, iLen, TEXT("获取订单编号应答->结果长度:%d,请求的结果:%s."), bLen, szRet); | _snprintf(pBuffer, iLen, TEXT("获取订单编号应答->结果长度:%d,请求的结果:%s."), bLen, szRet); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1334,7 +1334,7 @@ struct CMD_GR_DaiLiInfo | |||||
#else | #else | ||||
_snprintf(pBuffer, iLen, TEXT("获取代理信息->代理姓名:%s,代理微信:%s,代理QQ:%s"), szName, szWx, szQQ); | _snprintf(pBuffer, iLen, TEXT("获取代理信息->代理姓名:%s,代理微信:%s,代理QQ:%s"), szName, szWx, szQQ); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -1375,7 +1375,7 @@ struct CMD_GR_BangDing | |||||
#else | #else | ||||
_snprintf(pBuffer, iLen, TEXT("绑定代理->代理ID:%d"), dDaiLi); | _snprintf(pBuffer, iLen, TEXT("绑定代理->代理ID:%d"), dDaiLi); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1415,7 +1415,7 @@ struct CMD_GR_BangDingACK | |||||
#else | #else | ||||
_snprintf(pBuffer, iLen, TEXT("绑定代理应答->结果:%s,数据长度%d,XML描述%s"), bRet == 1 ? _T("初步执行成功") : _T("初步执行失败"), bLen, szText); | _snprintf(pBuffer, iLen, TEXT("绑定代理应答->结果:%s,数据长度%d,XML描述%s"), bRet == 1 ? _T("初步执行成功") : _T("初步执行失败"), bLen, szText); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1456,7 +1456,7 @@ struct CMD_GR_GetDingDan | |||||
#else | #else | ||||
_snprintf(pBuffer, iLen, TEXT("获取订单编号->产品ID:%d,产品名称:%s,价格:%d分."), dProductId, ProductName, jiage); | _snprintf(pBuffer, iLen, TEXT("获取订单编号->产品ID:%d,产品名称:%s,价格:%d分."), dProductId, ProductName, jiage); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1511,7 +1511,7 @@ struct CMD_GR_GetDingDanNEW | |||||
#else | #else | ||||
_snprintf(pBuffer, iLen, TEXT("获取订单编号->产品ID:%d,产品名称:%s,价格:%d分."), dProductId, ProductName, jiage); | _snprintf(pBuffer, iLen, TEXT("获取订单编号->产品ID:%d,产品名称:%s,价格:%d分."), dProductId, ProductName, jiage); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1552,7 +1552,7 @@ struct CMD_GR_CheckName | |||||
#else | #else | ||||
_snprintf(pBuffer, iLen, TEXT("实名认证->姓名:%s,身份证:%s,电话:%s."), Name, IDCard, PhoneNum); | _snprintf(pBuffer, iLen, TEXT("实名认证->姓名:%s,身份证:%s,电话:%s."), Name, IDCard, PhoneNum); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1610,7 +1610,7 @@ struct CMD_GR_CheckVerifCode | |||||
#else | #else | ||||
_snwprintf(pBuffer, iLen, TEXT("手机绑定->验证码id:%s,电话:%s"), VerfCode, PhoneNum); | _snwprintf(pBuffer, iLen, TEXT("手机绑定->验证码id:%s,电话:%s"), VerfCode, PhoneNum); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//打印通讯日志 | //打印通讯日志 | ||||
@@ -1640,7 +1640,7 @@ struct CMD_GR_CheckIphone_Query | |||||
#else | #else | ||||
_snwprintf(pBuffer, iLen, TEXT("手机绑定查询->是否绑手机号:%d,钻石数:%d,电话:%s."), IsBingNum, dwCurrency, PhoneNum); | _snwprintf(pBuffer, iLen, TEXT("手机绑定查询->是否绑手机号:%d,钻石数:%d,电话:%s."), IsBingNum, dwCurrency, PhoneNum); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//打印通讯日志 | //打印通讯日志 | ||||
@@ -1674,7 +1674,7 @@ struct CMD_GR_CheckIphone | |||||
#else | #else | ||||
_snwprintf(pBuffer, iLen, TEXT("手机绑定->是否成功:%d,钻石数:%d,电话:%s"), IsBingNum, dwCurrency, PhoneNum); | _snwprintf(pBuffer, iLen, TEXT("手机绑定->是否成功:%d,钻石数:%d,电话:%s"), IsBingNum, dwCurrency, PhoneNum); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//打印通讯日志 | //打印通讯日志 | ||||
@@ -1739,7 +1739,7 @@ struct CMD_GR_UserSeat | |||||
_snprintf(pBuffer, iLen, TEXT("选则座位请求->桌子位置:%d.") | _snprintf(pBuffer, iLen, TEXT("选则座位请求->桌子位置:%d.") | ||||
, seatId); | , seatId); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1784,7 +1784,7 @@ struct CMD_GR_UserSeatEx | |||||
_snprintf(pBuffer, iLen, TEXT("选则座位请求->桌子位置:%d.") | _snprintf(pBuffer, iLen, TEXT("选则座位请求->桌子位置:%d.") | ||||
, seatId); | , seatId); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1827,7 +1827,7 @@ struct CMD_GR_UserSitAck | |||||
_snprintf(pBuffer, iLen, TEXT("选则座位请求应答->成功:%d.") | _snprintf(pBuffer, iLen, TEXT("选则座位请求应答->成功:%d.") | ||||
, ret); | , ret); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1870,7 +1870,7 @@ struct CMD_GR_ZFXC | |||||
_snprintf(pBuffer, iLen, TEXT("支付线程应答->%s."), Data); | _snprintf(pBuffer, iLen, TEXT("支付线程应答->%s."), Data); | ||||
#endif | #endif | ||||
} | } | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1937,7 +1937,7 @@ struct CMD_GR_UserSitDownAck | |||||
_snprintf(pBuffer, iLen, TEXT("创建进入房间应答->创建或进入结果:%d,错误信息:%s.") | _snprintf(pBuffer, iLen, TEXT("创建进入房间应答->创建或进入结果:%d,错误信息:%s.") | ||||
, cbRet, szText); | , cbRet, szText); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -1993,7 +1993,7 @@ struct CMD_GR_UserSitDownAckEx | |||||
_snwprintf(pBuffer, iLen, TEXT("进入房间应答->进入结果:%d,底分:%d,支付方式:%d,桌子类型:%d,玩法类型:%d,是否油上油:%d,是否二人玩法:%d,总局数:%d,是否房主:%d,房间id:%d,私有桌子号:%s") | _snwprintf(pBuffer, iLen, TEXT("进入房间应答->进入结果:%d,底分:%d,支付方式:%d,桌子类型:%d,玩法类型:%d,是否油上油:%d,是否二人玩法:%d,总局数:%d,是否房主:%d,房间id:%d,私有桌子号:%s") | ||||
, cbRet, dijin, paytype, desktype, playtype, bDuoLai, wFengDing, totalplay, isowner, roomId, szPrivateTableID); | , cbRet, dijin, paytype, desktype, playtype, bDuoLai, wFengDing, totalplay, isowner, roomId, szPrivateTableID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2037,7 +2037,7 @@ struct CMD_GR_UserStandUp | |||||
_snprintf(pBuffer, iLen, TEXT("起立请求->桌子位置:%d,椅子位置:%d,强行离开:%d.") | _snprintf(pBuffer, iLen, TEXT("起立请求->桌子位置:%d,椅子位置:%d,强行离开:%d.") | ||||
, wTableID, wChairID, cbForceLeave); | , wTableID, wChairID, cbForceLeave); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2146,7 +2146,7 @@ struct tagGameRecordDrawScore | |||||
_snprintf(pBuffer, iLen, TEXT("dwDrawID:%d,dwUserID:%d,椅子号:%d,积分:%lld,成绩:%lld,奖牌数目:%d,游戏时长:%d,用户名:%s.") | _snprintf(pBuffer, iLen, TEXT("dwDrawID:%d,dwUserID:%d,椅子号:%d,积分:%lld,成绩:%lld,奖牌数目:%d,游戏时长:%d,用户名:%s.") | ||||
, dwDrawID, dwUserID, wChairID, lScore, lGrade, dwUserMedal, dwPlayTimeCount, szUserName); | , dwDrawID, dwUserID, wChairID, lScore, lGrade, dwUserMedal, dwPlayTimeCount, szUserName); | ||||
#endif | #endif | ||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
////mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2196,7 +2196,7 @@ struct tagGameRecordDrawScoreEx | |||||
_snprintf(pBuffer, iLen, TEXT("dwDrawID:%d,dwUserID:%d,椅子号:%d,积分:%lld,成绩:%lld,奖牌数目:%d,游戏时长:%d,用户名:%s.") | _snprintf(pBuffer, iLen, TEXT("dwDrawID:%d,dwUserID:%d,椅子号:%d,积分:%lld,成绩:%lld,奖牌数目:%d,游戏时长:%d,用户名:%s.") | ||||
, dwDrawID, dwUserID, wChairID, lScore, lGrade, dwUserMedal, dwPlayTimeCount, szUserName); | , dwDrawID, dwUserID, wChairID, lScore, lGrade, dwUserMedal, dwPlayTimeCount, szUserName); | ||||
#endif | #endif | ||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
////mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2292,7 +2292,7 @@ struct CMD_GR_GetUserScoreRecordDetail | |||||
_snprintf(pBuffer, iLen, TEXT("获取游戏积分记录详情->wRecordCount:%d,dwUserID:%s.") | _snprintf(pBuffer, iLen, TEXT("获取游戏积分记录详情->wRecordCount:%d,dwUserID:%s.") | ||||
, wRecordCount, szBuffer); | , wRecordCount, szBuffer); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2380,7 +2380,7 @@ struct tagMobileUserScore | |||||
_snprintf(pBuffer, iLen, TEXT("<br>【用户积分】用户分数:%d,胜利盘数:%d,失败盘数:%d,和局盘数:%d,逃跑盘数:%d,用户经验:%d.") | _snprintf(pBuffer, iLen, TEXT("<br>【用户积分】用户分数:%d,胜利盘数:%d,失败盘数:%d,和局盘数:%d,逃跑盘数:%d,用户经验:%d.") | ||||
, lScore, dwWinCount, dwLostCount, dwDrawCount, dwFleeCount, dwExperience); | , lScore, dwWinCount, dwLostCount, dwDrawCount, dwFleeCount, dwExperience); | ||||
#endif | #endif | ||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
////mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -2448,7 +2448,7 @@ struct CMD_GR_UserScore | |||||
_snprintf(pBuffer, iLen, TEXT("用户分数->用户标识:%d,积分信息:%s.") | _snprintf(pBuffer, iLen, TEXT("用户分数->用户标识:%d,积分信息:%s.") | ||||
, dwUserID, szBuffer); | , dwUserID, szBuffer); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2498,7 +2498,7 @@ struct CMD_GR_MobileUserScore | |||||
_snprintf(pBuffer, iLen, TEXT("用户分数->用户标识:%d,积分信息:%s.") | _snprintf(pBuffer, iLen, TEXT("用户分数->用户标识:%d,积分信息:%s.") | ||||
, dwUserID, szBuffer); | , dwUserID, szBuffer); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2571,7 +2571,7 @@ struct tagUserStatus | |||||
_snprintf(pBuffer, iLen, TEXT("<br>【子项用户状态】桌子索引:%d,椅子位置:%d,用户状态:%s") | _snprintf(pBuffer, iLen, TEXT("<br>【子项用户状态】桌子索引:%d,椅子位置:%d,用户状态:%s") | ||||
, wTableID, wChairID, szTemp); | , wTableID, wChairID, szTemp); | ||||
#endif | #endif | ||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
////mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -2620,7 +2620,7 @@ struct CMD_GR_UserStatus | |||||
_snprintf(pBuffer, iLen, TEXT("用户分数->用户标识:%d,用户状态:%s") | _snprintf(pBuffer, iLen, TEXT("用户分数->用户标识:%d,用户状态:%s") | ||||
, dwUserID, szBuffer); | , dwUserID, szBuffer); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2664,7 +2664,7 @@ struct CMD_GR_RequestFailure | |||||
_snprintf(pBuffer, iLen, TEXT("请求失败->错误代码:%d,描述信息:%s.") | _snprintf(pBuffer, iLen, TEXT("请求失败->错误代码:%d,描述信息:%s.") | ||||
, lErrorCode, szDescribeString); | , lErrorCode, szDescribeString); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2737,7 +2737,7 @@ struct CMD_GR_C_UserExpression | |||||
_snprintf(pBuffer, iLen, TEXT("用户表情->表情索引:%d,发送用户:%d,目标用户:%d.") | _snprintf(pBuffer, iLen, TEXT("用户表情->表情索引:%d,发送用户:%d,目标用户:%d.") | ||||
, wItemIndex, dwSendUserID, dwTargetUserID); | , wItemIndex, dwSendUserID, dwTargetUserID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2782,7 +2782,7 @@ struct CMD_GR_S_UserExpression | |||||
_snprintf(pBuffer, iLen, TEXT("用户表情->表情索引:%d,发送用户:%d,目标用户:%d.") | _snprintf(pBuffer, iLen, TEXT("用户表情->表情索引:%d,发送用户:%d,目标用户:%d.") | ||||
, wItemIndex, dwSendUserID, dwTargetUserID); | , wItemIndex, dwSendUserID, dwTargetUserID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2847,7 +2847,7 @@ struct CMD_GR_C_WisperExpression | |||||
_snprintf(pBuffer, iLen, TEXT("用户表情->表情索引:%d,发送用户:%d,目标用户:%d.") | _snprintf(pBuffer, iLen, TEXT("用户表情->表情索引:%d,发送用户:%d,目标用户:%d.") | ||||
, wItemIndex, dwSendUserID, dwTargetUserID); | , wItemIndex, dwSendUserID, dwTargetUserID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -2892,7 +2892,7 @@ struct CMD_GR_S_WisperExpression | |||||
_snprintf(pBuffer, iLen, TEXT("用户表情->表情索引:%d,发送用户:%d,目标用户:%d.") | _snprintf(pBuffer, iLen, TEXT("用户表情->表情索引:%d,发送用户:%d,目标用户:%d.") | ||||
, wItemIndex, dwSendUserID, dwTargetUserID); | , wItemIndex, dwSendUserID, dwTargetUserID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -3053,7 +3053,7 @@ struct CMD_GR_UserInfoReq | |||||
_snprintf(pBuffer, iLen, TEXT("请求用户信息->请求用户:%d,桌子位置:%d.") | _snprintf(pBuffer, iLen, TEXT("请求用户信息->请求用户:%d,桌子位置:%d.") | ||||
, dwUserIDReq, wTablePos); | , dwUserIDReq, wTablePos); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -3102,7 +3102,7 @@ struct CMD_GR_UserBean | |||||
_snprintf(pBuffer, iLen, TEXT("通知当前元宝->元宝:%d.") | _snprintf(pBuffer, iLen, TEXT("通知当前元宝->元宝:%d.") | ||||
, dwCurrBeans); | , dwCurrBeans); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -3156,7 +3156,7 @@ struct tagTableStatus | |||||
_snprintf(pBuffer, iLen, TEXT("<br>【桌子状态】锁定标志:%d,游戏标志:%d,单元积分:%d.") | _snprintf(pBuffer, iLen, TEXT("<br>【桌子状态】锁定标志:%d,游戏标志:%d,单元积分:%d.") | ||||
, cbTableLock, cbPlayStatus, lCellScore); | , cbTableLock, cbPlayStatus, lCellScore); | ||||
#endif | #endif | ||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
////mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -3214,7 +3214,7 @@ struct CMD_GR_TableStatus | |||||
_snprintf(pBuffer, iLen, TEXT("桌子状态->桌子号码:%d,桌子状态:%s.") | _snprintf(pBuffer, iLen, TEXT("桌子状态->桌子号码:%d,桌子状态:%s.") | ||||
, wTableID, szBuffer); | , wTableID, szBuffer); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -3528,7 +3528,7 @@ struct CMD_GR_DELETE_PRIVATE_ROOM_REQ | |||||
_snprintf(pBuffer, iLen, TEXT("申请解散私有房间->桌子:%d,椅子:%d,定时剩余时间:%d.") | _snprintf(pBuffer, iLen, TEXT("申请解散私有房间->桌子:%d,椅子:%d,定时剩余时间:%d.") | ||||
, wTableID, wChairID, leftTime); | , wTableID, wChairID, leftTime); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -3572,7 +3572,7 @@ struct CMD_GR_DELETE_PRIVATE_ROOM_RESP | |||||
_snprintf(pBuffer, iLen, TEXT("申请解散私有房间的应答->桌子:%d,椅子:%d,选择:%s.") | _snprintf(pBuffer, iLen, TEXT("申请解散私有房间的应答->桌子:%d,椅子:%d,选择:%s.") | ||||
, wTableID, wChairID, bAgree == 1 ? _T("同意") : _T("不同意")); | , wTableID, wChairID, bAgree == 1 ? _T("同意") : _T("不同意")); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -3616,7 +3616,7 @@ struct CMD_GR_DELETE_PRIVATE_ROOM_RESULT | |||||
_snprintf(pBuffer, iLen, TEXT("申请解散私有房间的结果->桌子:%d,状态:%s,描述信息:%s.") | _snprintf(pBuffer, iLen, TEXT("申请解散私有房间的结果->桌子:%d,状态:%s,描述信息:%s.") | ||||
, wTableID, bState == 1 ? _T("成功") : _T("失败"), szDescription); | , wTableID, bState == 1 ? _T("成功") : _T("失败"), szDescription); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -3661,7 +3661,7 @@ struct CMD_GR_SendWarning | |||||
_snprintf(pBuffer, iLen, TEXT("发送警告->信息长度:%d,目标用户:%d,警告消息:%s.") | _snprintf(pBuffer, iLen, TEXT("发送警告->信息长度:%d,目标用户:%d,警告消息:%s.") | ||||
, wChatLength, dwTargetUserID, szWarningMessage); | , wChatLength, dwTargetUserID, szWarningMessage); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -3716,7 +3716,7 @@ struct CMD_GR_LookUserIP | |||||
_snprintf(pBuffer, iLen, TEXT("查看地址->目标用户:%d.") | _snprintf(pBuffer, iLen, TEXT("查看地址->目标用户:%d.") | ||||
, dwTargetUserID); | , dwTargetUserID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -3758,7 +3758,7 @@ struct CMD_GR_KickUser | |||||
_snprintf(pBuffer, iLen, TEXT("踢出用户->目标用户:%d.") | _snprintf(pBuffer, iLen, TEXT("踢出用户->目标用户:%d.") | ||||
, dwTargetUserID); | , dwTargetUserID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -3800,7 +3800,7 @@ struct CMD_GR_LimitAccounts | |||||
_snprintf(pBuffer, iLen, TEXT("禁用帐户->目标用户:%d.") | _snprintf(pBuffer, iLen, TEXT("禁用帐户->目标用户:%d.") | ||||
, dwTargetUserID); | , dwTargetUserID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -4119,7 +4119,7 @@ struct CMD_S_ClubAutoAddInfo | |||||
#else | #else | ||||
_snprintf(pBuffer, iLen, TEXT("俱乐部额外玩法自动开房请求->俱乐部:%d,额外玩法id序号:%d,状态:%d."), dwClubID, cbWanfaId, cbStatus); | _snprintf(pBuffer, iLen, TEXT("俱乐部额外玩法自动开房请求->俱乐部:%d,额外玩法id序号:%d,状态:%d."), dwClubID, cbWanfaId, cbStatus); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//打印通讯日志 | //打印通讯日志 | ||||
@@ -4214,7 +4214,7 @@ struct CMD_GR_ClubDissolveRoom | |||||
#else | #else | ||||
_snwprintf(pBuffer, iLen, TEXT("群主解散房间->俱乐部:%d,房间号:%d."), dwClubID, dwRoomID); | _snwprintf(pBuffer, iLen, TEXT("群主解散房间->俱乐部:%d,房间号:%d."), dwClubID, dwRoomID); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
RoomID = dwRoomID; | RoomID = dwRoomID; | ||||
return true; | return true; | ||||
} | } | ||||
@@ -4465,7 +4465,7 @@ struct CMD_GR_Club_Action_Ack1Add | |||||
#else | #else | ||||
_snprintf(pBuffer, iLen, TEXT("俱乐部额外玩法自动开房响应->结果:%d,俱乐部:%d,额外玩法id序号:%d,状态:%d,错误信息:%s."), Result, AutoAddInfo.dwClubID, AutoAddInfo.cbWanfaId, AutoAddInfo.cbStatus,Message); | _snprintf(pBuffer, iLen, TEXT("俱乐部额外玩法自动开房响应->结果:%d,俱乐部:%d,额外玩法id序号:%d,状态:%d,错误信息:%s."), Result, AutoAddInfo.dwClubID, AutoAddInfo.cbWanfaId, AutoAddInfo.cbStatus,Message); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//打印通讯日志 | //打印通讯日志 | ||||
@@ -4648,7 +4648,7 @@ struct CMD_GF_GameOption | |||||
_snprintf(pBuffer, iLen, TEXT("游戏配置->旁观标志:%d,框架版本:%d,游戏版本:%d.") | _snprintf(pBuffer, iLen, TEXT("游戏配置->旁观标志:%d,框架版本:%d,游戏版本:%d.") | ||||
, cbAllowLookon, dwFrameVersion, dwClientVersion); | , cbAllowLookon, dwFrameVersion, dwClientVersion); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -4706,7 +4706,7 @@ struct CMD_GF_GameStatus | |||||
_snprintf(pBuffer, iLen, TEXT("游戏环境->游戏状态:%d,旁观标志:%d.") | _snprintf(pBuffer, iLen, TEXT("游戏环境->游戏状态:%d,旁观标志:%d.") | ||||
, cbGameStatus, cbAllowLookon); | , cbGameStatus, cbAllowLookon); | ||||
#endif | #endif | ||||
mylog::ToSet(pBuffer, iLen, dUserID); | |||||
//mylog::ToSet(pBuffer, iLen, dUserID); | |||||
return true; | return true; | ||||
} | } | ||||
//名字 | //名字 | ||||
@@ -60,7 +60,7 @@ | |||||
<PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
<SDLCheck>false</SDLCheck> | <SDLCheck>false</SDLCheck> | ||||
<AdditionalOptions>/D _CRT_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions> | <AdditionalOptions>/D _CRT_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions> | ||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\ServerControl\CppRedis\includes;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\ServerControl\CppRedis\includes;$(SolutionDir)Source\Spdlog;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
</ClCompile> | </ClCompile> | ||||
<Link> | <Link> | ||||
<SubSystem>Windows</SubSystem> | <SubSystem>Windows</SubSystem> | ||||
@@ -25,6 +25,8 @@ CServiceUnits::~CServiceUnits() | |||||
//Æô¶¯·þÎñ | //Æô¶¯·þÎñ | ||||
bool CServiceUnits::StartService() | bool CServiceUnits::StartService() | ||||
{ | { | ||||
Logger::GetInstance().InitDefaultLog("CorrespondServer", "daily_log.log"); | |||||
LTrace("{}", 45); | |||||
//ЧÑé״̬ | //ЧÑé״̬ | ||||
ASSERT(m_ServiceStatus == ServiceStatus_Stop); | ASSERT(m_ServiceStatus == ServiceStatus_Stop); | ||||
if (m_ServiceStatus != ServiceStatus_Stop) return false; | if (m_ServiceStatus != ServiceStatus_Stop) return false; | ||||
@@ -63,6 +65,7 @@ bool CServiceUnits::ConcludeService() | |||||
if (m_AttemperEngine.GetInterface() != NULL) m_AttemperEngine->ConcludeService(); | if (m_AttemperEngine.GetInterface() != NULL) m_AttemperEngine->ConcludeService(); | ||||
if (m_TCPNetworkEngine.GetInterface() != NULL) m_TCPNetworkEngine->ConcludeService(); | if (m_TCPNetworkEngine.GetInterface() != NULL) m_TCPNetworkEngine->ConcludeService(); | ||||
m_RedisClient->DisConnect(); | m_RedisClient->DisConnect(); | ||||
Logger::ShutDown(); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -1,49 +1,12 @@ | |||||
#pragma once | #pragma once | ||||
#include "StdAfx.h" | |||||
#include <unordered_map> | |||||
#include <process.h> | |||||
#define WRITE_LOG Commend::WriteLog | |||||
#define LOG_LEVEL_DEBUG Commend::ENUM_LOG_LEVEL_DEBUG //调试信息 | |||||
#define LOG_LEVEL_RUNRUN Commend::ENUM_LOG_LEVEL_RUN //代码业务逻辑 | |||||
#define LOG_LEVEL_RUNRUNERROR Commend::ENUM_LOG_LEVEL_RUNERROR //代码业务逻辑错误 | |||||
#define LOG_LEVEL_ERROR Commend::ENUM_LOG_LEVEL_ERROR //代码的错误 | |||||
#define WELCOME_LOG_INFO _T("\r\n################日志记录开始################\r\n") | |||||
class Commend | class Commend | ||||
{ | { | ||||
private: | |||||
// 日志写入器的状态 | |||||
enum enLogStatus | |||||
{ | |||||
ENUM_LOG_INIT, | |||||
ENUM_LOG_RUN, | |||||
ENUM_LOG_EXITING, | |||||
ENUM_LOG_EXITED, | |||||
ENUM_LOG_INVALID, | |||||
}; | |||||
// 日志级别 | |||||
enum enLogInfoLevel | |||||
{ | |||||
ENUM_LOG_LEVEL_DEBUG = 0, | |||||
ENUM_LOG_LEVEL_RUN, | |||||
ENUM_LOG_LEVEL_RUNERROR, | |||||
ENUM_LOG_LEVEL_ERROR, | |||||
}; | |||||
static HANDLE m_hThread; // 线程句柄 | |||||
static unsigned m_uiThreadID; // 线程id | |||||
static enLogStatus m_enStatus; // 当前log写入器的状态 | |||||
static bool m_bRun; // 当前log写入器是否结束 | |||||
static int m_iLogLevel; // 当前写日志的级别 | |||||
//static HANDLE m_hMapLogFile; //映射到共享内存中 | |||||
//static LPTSTR m_psMapAddr; //映射到共享内存到程序内存的地址 | |||||
static CString m_Patch; | |||||
public: | public: | ||||
Commend(); | Commend(); | ||||
~Commend(); | ~Commend(); | ||||
//锁 | //锁 | ||||
static CCriticalSection m_CriticalSection; //线程锁 | |||||
//static CCriticalSection m_CriticalSection; //线程锁 | |||||
//参数转成字符串 | //参数转成字符串 | ||||
static CString ToString(WORD wMainCmdID, WORD wCmdTable, const void* pBuffer); | static CString ToString(WORD wMainCmdID, WORD wCmdTable, const void* pBuffer); | ||||
//名字 | //名字 | ||||
@@ -53,17 +16,5 @@ public: | |||||
//打印通讯日志 | //打印通讯日志 | ||||
static void ToShow(VOID* Frame, WORD wMainCmdID, WORD wCmdTable, const void* pBuffer, int iLen, DWORD dTablePrivate = 0, DWORD dUserID = 0, PVOID pGetGameString = NULL,bool bSend = true); | static void ToShow(VOID* Frame, WORD wMainCmdID, WORD wCmdTable, const void* pBuffer, int iLen, DWORD dTablePrivate = 0, DWORD dUserID = 0, PVOID pGetGameString = NULL,bool bSend = true); | ||||
public: | |||||
//日志信息 | |||||
static std::unordered_map<DWORD, CString> Log; //桌子ID,内容 | |||||
//添加日志 | |||||
static void AddLog(DWORD dPriateTable, CString LogText); | |||||
//获取日志 | |||||
static std::unordered_map<DWORD, CString> GetLog(); | |||||
//运行 | |||||
static unsigned __stdcall LogRuns(void* pParam); | |||||
static CString& GetCurTimeStr(); | |||||
static int WriteUnicodeHeadToFile(CStdioFile* pFile); | |||||
}; | }; | ||||
@@ -62,7 +62,7 @@ | |||||
<SDLCheck>false</SDLCheck> | <SDLCheck>false</SDLCheck> | ||||
<AdditionalOptions>/D _CRT_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions> | <AdditionalOptions>/D _CRT_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions> | ||||
<DisableSpecificWarnings>4018;4996;4244;4200</DisableSpecificWarnings> | <DisableSpecificWarnings>4018;4996;4244;4200</DisableSpecificWarnings> | ||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\Protobuf;$(SolutionDir)Source\ServerControl\CppRedis\includes;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\Protobuf;$(SolutionDir)Source\ServerControl\CppRedis\includes;$(SolutionDir)Source\Spdlog;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
</ClCompile> | </ClCompile> | ||||
<Link> | <Link> | ||||
<SubSystem>Windows</SubSystem> | <SubSystem>Windows</SubSystem> | ||||
@@ -73,6 +73,7 @@ bool CServiceUnits::PostControlRequest(WORD wIdentifier, VOID * pData, WORD wDat | |||||
//启动服务 | //启动服务 | ||||
bool CServiceUnits::StartService() | bool CServiceUnits::StartService() | ||||
{ | { | ||||
Logger::GetInstance().InitDefaultLog("GameServer", "daily_log.log"); | |||||
//效验状态 | //效验状态 | ||||
ASSERT(m_ServiceStatus == ServiceStatus_Stop); | ASSERT(m_ServiceStatus == ServiceStatus_Stop); | ||||
if (m_ServiceStatus != ServiceStatus_Stop) return false; | if (m_ServiceStatus != ServiceStatus_Stop) return false; | ||||
@@ -150,7 +151,7 @@ bool CServiceUnits::ConcludeService() | |||||
//c++中使用google protobuf在关闭应用程序之前需要调用google::protobuf::ShutdownProtobufLibrary();要不然会有内存泄露 | //c++中使用google protobuf在关闭应用程序之前需要调用google::protobuf::ShutdownProtobufLibrary();要不然会有内存泄露 | ||||
google::protobuf::ShutdownProtobufLibrary(); | google::protobuf::ShutdownProtobufLibrary(); | ||||
// _CrtDumpMemoryLeaks();//@yuanjing | // _CrtDumpMemoryLeaks();//@yuanjing | ||||
Logger::ShutDown(); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -3,67 +3,15 @@ | |||||
#include "Commend.h" | #include "Commend.h" | ||||
#include <strsafe.h> | #include <strsafe.h> | ||||
#include "GameServerDlg.h" | #include "GameServerDlg.h" | ||||
#include "../KernelEngine/TraceService.h" | |||||
bool mylog::ToSet(TCHAR* pBuffer, int iLen, DWORD dUserID) | |||||
{ | |||||
TCHAR* szTemp = NULL; | |||||
try | |||||
{ | |||||
if (wcslen(pBuffer) == 0) return true; | |||||
szTemp = (TCHAR*)malloc(iLen); | |||||
ZeroMemory(szTemp, iLen); | |||||
TCHAR szKey[512] = { 0 }; | |||||
CString csTest = pBuffer; | |||||
bool bFind = false; | |||||
if(csTest.Replace(_T("->"),_T("<br><font color=#888888>"))) | |||||
{ | |||||
bFind = true; | |||||
} | |||||
StringCchCopy(pBuffer, iLen / sizeof(TCHAR), csTest.GetBuffer(0)); | |||||
csTest.ReleaseBuffer(); | |||||
CTime m_time; | |||||
m_time=CTime::GetCurrentTime(); //获取当前时间日期 | |||||
CString m_strDateTime = m_time.Format(_T("%Y-%m-%d %H:%M:%S %A")); //格式化日期时间 | |||||
#ifdef _UNICODE | |||||
_snwprintf(szKey, 512, TEXT("【%d】发生时间:%s"), dUserID, m_strDateTime); | |||||
_snwprintf(szTemp, iLen / sizeof(TCHAR), TEXT("<p style='font-size:9pt;' class=\"%d\"><br>-----------------------------------------------------<br><font color=red>"), dUserID); | |||||
#else | |||||
_snprintf(szKey, 512, TEXT("【%d】发生时间:%s"), dUserID, m_strDateTime); | |||||
_snprintf(szTemp, iLen / sizeof(TCHAR), TEXT("<p style='font-size:9pt;' class=\"%d\"><br>-----------------------------------------------------<br><font color=red>"), dUserID); | |||||
#endif | |||||
StringCchCat(szTemp, iLen / sizeof(TCHAR), TEXT("【协议】")); | |||||
StringCchCat(szTemp, iLen / sizeof(TCHAR), szKey); | |||||
StringCchCat(szTemp, iLen / sizeof(TCHAR), TEXT("</font><br><font color=green>")); | |||||
StringCchCat(szTemp, iLen / sizeof(TCHAR), pBuffer); | |||||
if (bFind) StringCchCat(szTemp, iLen / sizeof(TCHAR), TEXT("</font></font></p>")); | |||||
else StringCchCat(szTemp, iLen / sizeof(TCHAR), TEXT("</font></p>")); | |||||
StringCchCopy(pBuffer, iLen / sizeof(TCHAR), szTemp); | |||||
free(szTemp); | |||||
szTemp = NULL; | |||||
} | |||||
catch (CMemoryException* e) | |||||
{ | |||||
if (szTemp) | |||||
{ | |||||
free(szTemp); | |||||
szTemp = NULL; | |||||
} | |||||
} | |||||
pBuffer[iLen - 1] = 0; | |||||
return true; | |||||
} | |||||
//bool ::ToSet(TCHAR* pBuffer, int iLen, DWORD dUserID) | |||||
//{ | |||||
// return true; | |||||
//} | |||||
void mylog::OutputString(TCHAR* pBuffer, int iTableID) | void mylog::OutputString(TCHAR* pBuffer, int iTableID) | ||||
{ | { | ||||
//检查日志是否启用 | |||||
if (!CGameServerDlg::g_bGameLog) return; | |||||
Commend::AddLog(iTableID, pBuffer); | |||||
//OutputDebugString(pBuffer); | |||||
auto tmp = Logger::GetInstance().GetTableLogger(iTableID); | |||||
tmp->info("{}", CT2A(pBuffer)); | |||||
} | } |
@@ -58,7 +58,7 @@ | |||||
<Optimization>Disabled</Optimization> | <Optimization>Disabled</Optimization> | ||||
<PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_AFXEXT;GAME_SERVICE_DLL;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_AFXEXT;GAME_SERVICE_DLL;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
<SDLCheck>false</SDLCheck> | <SDLCheck>false</SDLCheck> | ||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\Protobuf;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\Protobuf;$(SolutionDir)Source\Spdlog;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
</ClCompile> | </ClCompile> | ||||
<Link> | <Link> | ||||
<SubSystem>Windows</SubSystem> | <SubSystem>Windows</SubSystem> | ||||
@@ -59,7 +59,7 @@ | |||||
<PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_AFXEXT;KERNEL_ENGINE_DLL;_UNICODE;_CRT_NON_CONFORMING_SWPRINTFS;%(PreprocessorDefinitions)</PreprocessorDefinitions> | <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_AFXEXT;KERNEL_ENGINE_DLL;_UNICODE;_CRT_NON_CONFORMING_SWPRINTFS;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
<SDLCheck>false</SDLCheck> | <SDLCheck>false</SDLCheck> | ||||
<AdditionalOptions>/D _CRT_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions> | <AdditionalOptions>/D _CRT_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions> | ||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\Protobuf;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\Protobuf;$(SolutionDir)Source\Spdlog;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
</ClCompile> | </ClCompile> | ||||
<Link> | <Link> | ||||
<SubSystem>Windows</SubSystem> | <SubSystem>Windows</SubSystem> | ||||
@@ -1,8 +1,11 @@ | |||||
#include "StdAfx.h" | #include "StdAfx.h" | ||||
#include "TraceService.h" // #include "EventService.h" | #include "TraceService.h" // #include "EventService.h" | ||||
#include "../Spdlog/spdlog/sinks/base_sink.h" | |||||
#include "../Spdlog/spdlog/sinks/msvc_sink.h" | |||||
#include "../Spdlog/spdlog/sinks/stdout_color_sinks.h" | |||||
#include "../Spdlog/spdlog/sinks/daily_file_sink.h" | |||||
#include "../Spdlog/spdlog/sinks/basic_file_sink.h" | |||||
////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////// | ||||
//颜色定义 | //颜色定义 | ||||
#define COLOR_TIME RGB(0,0,0) | #define COLOR_TIME RGB(0,0,0) | ||||
#define COLOR_NORMAL RGB(125,125,125) | #define COLOR_NORMAL RGB(125,125,125) | ||||
@@ -23,7 +26,137 @@ | |||||
//追踪服务 | //追踪服务 | ||||
////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////// | ||||
static ITraceService *g_pITraceService = NULL; | |||||
Logger::~Logger() | |||||
{ | |||||
}; | |||||
//Spdlog | |||||
void Logger::ShutDown() | |||||
{ | |||||
spdlog::shutdown(); | |||||
}; | |||||
Logger& Logger::GetInstance() | |||||
{ | |||||
static Logger _log; | |||||
return _log; | |||||
} | |||||
std::shared_ptr<spdlog::logger> Logger::GetTableLogger(int tableId) | |||||
{ | |||||
if (tablelogger_.count(tableId)) | |||||
{ | |||||
return tablelogger_.at(tableId); | |||||
} | |||||
//创建新的logger | |||||
auto new_loger = spdlog::basic_logger_mt(fmt::format("{}", tableId), fmt::format("{}table_{}.log", logfile_dir_, tableId)); | |||||
tablelogger_[tableId] = new_loger; | |||||
return new_loger; | |||||
} | |||||
void Logger::DeleteTableLogger(int tableId) | |||||
{ | |||||
if (tablelogger_.count(tableId)) | |||||
{ | |||||
spdlog::drop(fmt::format("{}", tableId)); | |||||
tablelogger_.erase(tableId); | |||||
} | |||||
} | |||||
; | |||||
bool Logger::InitDefaultLog(std::string logger_name, std::string file_name, spdlog::level::level_enum log_level /*= spdlog::level::trace*/) | |||||
{ | |||||
[](const char* dir, size_t len = 0) -> bool { | |||||
assert(dir != NULL); | |||||
if (len < 1 || len > 1024) | |||||
return false; | |||||
char* head, * p; | |||||
char tmpDir[1024] = { 0 }; | |||||
// strcpy_s(tmpDir, 1024, dir); //拷贝1024字节发现,len之后的字符全部变为-2 | |||||
strcpy_s(tmpDir, len + 1, dir); | |||||
head = tmpDir; | |||||
if (*head == '\\' || *head == '/') | |||||
++head; | |||||
p = head; | |||||
if (*(tmpDir + len - 1) != '\\' && *(tmpDir + len - 1) != '/') | |||||
*(tmpDir + len) = '\\'; | |||||
while (*p) | |||||
{ | |||||
if (*p == '\\' || *p == '/') | |||||
{ | |||||
*p = '\0'; | |||||
if (_access(head, 0)) // 头文件io.h | |||||
{ | |||||
if (_mkdir(head)) | |||||
{ | |||||
#ifdef _DEBUG | |||||
fprintf(stderr, "Failed to create directory %s\n", head); | |||||
return false; | |||||
#endif | |||||
} | |||||
} | |||||
*p = '\\'; | |||||
} | |||||
++p; | |||||
} | |||||
return true; | |||||
}("logs"); | |||||
spdlog::set_level(spdlog::level::trace); | |||||
//SPDLOG_TRACE("Some trace message.. {} ,{}", 1, 3.23); | |||||
//SPDLOG_DEBUG("Some debug message.. {} ,{}", 1, 3.23); | |||||
//设置为异步日志 | |||||
// spdlog::set_async_mode(32768); // 必须为 2 的幂 | |||||
//spdlog::pattern_formatter formatter("%g", spdlog::pattern_time_type::local, ""); | |||||
//创建一个对应多个sink的_logger_,每一个sink都有独有的格式和日志级别 | |||||
std::vector<std::shared_ptr<spdlog::sinks::sink>> logger_sinks; | |||||
//#ifdef _CONSOLE | |||||
#ifdef _DEBUG | |||||
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); | |||||
console_sink->set_level(log_level); | |||||
console_sink->set_pattern("[%m-%d %H:%M:%S.%e][%^%L%$] [%t:%@] %v"); | |||||
logger_sinks.push_back(console_sink); | |||||
#endif | |||||
// Create a daily logger - a new file is created every day on 2:30am. | |||||
logfile_dir_ = fmt::format("logs/{}/", CT2A(AfxGetAppName())); | |||||
auto dailyfile_sink = std::make_shared<spdlog::sinks::daily_file_sink_mt>(logfile_dir_ + file_name, 23, 59, true); | |||||
dailyfile_sink->set_level(log_level); | |||||
//格式说明参照https://github.com/gabime/spdlog/wiki/3.-Custom-formatting | |||||
dailyfile_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%t:%@] %v"); | |||||
logger_sinks.push_back(dailyfile_sink); | |||||
#ifdef _WIN32 | |||||
//使用OutputDebugStringA窗口输出日志 | |||||
auto vs_sink = std::make_shared<spdlog::sinks::msvc_sink_mt>(); | |||||
vs_sink->set_level(log_level); | |||||
vs_sink->set_pattern("[%m-%d %H:%M:%S.%e] [%^%l%$] [%t:%@] %v"); | |||||
logger_sinks.push_back(vs_sink); | |||||
#endif | |||||
logger_ = std::make_shared<spdlog::logger>(logger_name, begin(logger_sinks), end(logger_sinks)); | |||||
// register it if you need to access it globally | |||||
spdlog::register_logger(logger_); | |||||
// 设置日志记录级别 | |||||
#ifdef _DEBUG | |||||
logger_->set_level(log_level); | |||||
#else | |||||
logger_->set_level(spdlog::level::err); | |||||
#endif | |||||
//设置当出发 err 或更严重的错误时立刻刷新日志到 disk | |||||
logger_->flush_on(spdlog::level::err); | |||||
//对所有已注册的loggers定期1秒调用flush() | |||||
spdlog::flush_every(std::chrono::seconds(1)); | |||||
//替换任何其它logger为默认logger | |||||
spdlog::set_default_logger(logger_); | |||||
logger_->info("spdlog init success\n"); | |||||
//SPDLOG_LOGGER_TRACE(logger_, "test"); | |||||
return true; | |||||
}; | |||||
static ITraceService* g_pITraceService = NULL; | |||||
//函数定义 | //函数定义 | ||||
////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////// | ||||
//构造函数 | //构造函数 | ||||
@@ -35,26 +168,26 @@ CTraceService::CTraceService() | |||||
//服务配置 | //服务配置 | ||||
////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////// | ||||
//设置服务 | //设置服务 | ||||
bool CTraceService::SetTraceService(IUnknownEx * pIUnknownEx) | |||||
bool CTraceService::SetTraceService(IUnknownEx* pIUnknownEx) | |||||
{ | { | ||||
g_pITraceService = QUERY_OBJECT_PTR_INTERFACE(pIUnknownEx, ITraceService); | |||||
return NULL != g_pITraceService; | |||||
g_pITraceService = QUERY_OBJECT_PTR_INTERFACE(pIUnknownEx, ITraceService); | |||||
return NULL != g_pITraceService; | |||||
} | } | ||||
//获取服务 | //获取服务 | ||||
VOID * CTraceService::GetTraceService(const IID & Guid, DWORD dwQueryVer) | |||||
VOID* CTraceService::GetTraceService(const IID& Guid, DWORD dwQueryVer) | |||||
{ | { | ||||
ASSERT(g_pITraceService); | |||||
if (g_pITraceService) return NULL; | |||||
ASSERT(g_pITraceService); | |||||
if (g_pITraceService) return NULL; | |||||
return (VOID*)g_pITraceService->QueryInterface(Guid, dwQueryVer); | |||||
return NULL; | |||||
return (VOID*)g_pITraceService->QueryInterface(Guid, dwQueryVer); | |||||
return NULL; | |||||
} | } | ||||
//获取服务 | //获取服务 | ||||
VOID * CTraceService::GetTraceServiceManager(const IID & Guid, DWORD dwQueryVer) | |||||
VOID* CTraceService::GetTraceServiceManager(const IID& Guid, DWORD dwQueryVer) | |||||
{ | { | ||||
return NULL; | |||||
return NULL; | |||||
} | } | ||||
//状态管理 | //状态管理 | ||||
@@ -62,13 +195,13 @@ VOID * CTraceService::GetTraceServiceManager(const IID & Guid, DWORD dwQueryVer) | |||||
//追踪状态 | //追踪状态 | ||||
bool CTraceService::IsEnableTrace(enTraceLevel TraceLevel) | bool CTraceService::IsEnableTrace(enTraceLevel TraceLevel) | ||||
{ | { | ||||
return false; | |||||
return false; | |||||
} | } | ||||
//追踪控制 | //追踪控制 | ||||
bool CTraceService::EnableTrace(enTraceLevel TraceLevel, bool bEnableTrace) | bool CTraceService::EnableTrace(enTraceLevel TraceLevel, bool bEnableTrace) | ||||
{ | { | ||||
return false; | |||||
return false; | |||||
} | } | ||||
//功能函数 | //功能函数 | ||||
@@ -79,10 +212,10 @@ bool CTraceService::TraceString(LPCTSTR pszString, enTraceLevel TraceLevel) | |||||
if (TraceLevel == TraceLevel_Debug) | if (TraceLevel == TraceLevel_Debug) | ||||
return false; | return false; | ||||
if (g_pITraceService) | |||||
return g_pITraceService->TraceString(pszString, TraceLevel); | |||||
if (g_pITraceService) | |||||
return g_pITraceService->TraceString(pszString, TraceLevel); | |||||
return false; | |||||
return false; | |||||
} | } | ||||
////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////// | ||||
@@ -90,12 +223,12 @@ bool CTraceService::TraceString(LPCTSTR pszString, enTraceLevel TraceLevel) | |||||
////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////// | ||||
BEGIN_MESSAGE_MAP(CTraceServiceControl, CRichEditCtrl) | BEGIN_MESSAGE_MAP(CTraceServiceControl, CRichEditCtrl) | ||||
ON_WM_RBUTTONDOWN() | |||||
ON_COMMAND(IDM_MENU0, OnCopyString) | |||||
ON_COMMAND(IDM_MENU1, OnSelectAll) | |||||
ON_COMMAND(IDM_MENU2, OnDeleteString) | |||||
ON_COMMAND(IDM_MENU3, OnClearAll) | |||||
ON_COMMAND(IDM_MENU4, OnSaveString) | |||||
ON_WM_RBUTTONDOWN() | |||||
ON_COMMAND(IDM_MENU0, OnCopyString) | |||||
ON_COMMAND(IDM_MENU1, OnSelectAll) | |||||
ON_COMMAND(IDM_MENU2, OnDeleteString) | |||||
ON_COMMAND(IDM_MENU3, OnClearAll) | |||||
ON_COMMAND(IDM_MENU4, OnSaveString) | |||||
END_MESSAGE_MAP() | END_MESSAGE_MAP() | ||||
//函数定义 | //函数定义 | ||||
@@ -103,23 +236,23 @@ END_MESSAGE_MAP() | |||||
//构造函数 | //构造函数 | ||||
CTraceServiceControl::CTraceServiceControl() | CTraceServiceControl::CTraceServiceControl() | ||||
{ | { | ||||
CTraceService::SetTraceService((IUnknownEx*)QueryInterface(IID_IUnknownEx, VER_IUnknownEx)); | |||||
CTraceService::SetTraceService((IUnknownEx*)QueryInterface(IID_IUnknownEx, VER_IUnknownEx)); | |||||
} | } | ||||
//析构函数 | //析构函数 | ||||
CTraceServiceControl::~CTraceServiceControl() | CTraceServiceControl::~CTraceServiceControl() | ||||
{ | { | ||||
CTraceService::SetTraceService(NULL); | |||||
CTraceService::SetTraceService(NULL); | |||||
} | } | ||||
//基础接口 | //基础接口 | ||||
////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////// | ||||
//接口查询 | //接口查询 | ||||
VOID * CTraceServiceControl::QueryInterface(const IID & Guid, DWORD dwQueryVer) | |||||
VOID* CTraceServiceControl::QueryInterface(const IID& Guid, DWORD dwQueryVer) | |||||
{ | { | ||||
QUERYINTERFACE(ITraceService, Guid, dwQueryVer); | |||||
QUERYINTERFACE_IUNKNOWNEX(ITraceService, Guid, dwQueryVer); | |||||
return NULL; | |||||
QUERYINTERFACE(ITraceService, Guid, dwQueryVer); | |||||
QUERYINTERFACE_IUNKNOWNEX(ITraceService, Guid, dwQueryVer); | |||||
return NULL; | |||||
} | } | ||||
//信息接口 | //信息接口 | ||||
@@ -127,32 +260,32 @@ VOID * CTraceServiceControl::QueryInterface(const IID & Guid, DWORD dwQueryVer) | |||||
//追踪信息 | //追踪信息 | ||||
bool CTraceServiceControl::TraceString(LPCTSTR pszString, enTraceLevel TraceLevel) | bool CTraceServiceControl::TraceString(LPCTSTR pszString, enTraceLevel TraceLevel) | ||||
{ | { | ||||
//效验参数 | |||||
ITraceService *pITraceService = (ITraceService*)(QueryInterface(IID_ITraceService, VER_ITraceService)); | |||||
ASSERT(pITraceService != NULL); | |||||
if (pITraceService == NULL) return false; | |||||
//变量定义 | |||||
CHARFORMAT2 CharFormat; | |||||
ZeroMemory(&CharFormat, sizeof(CharFormat)); | |||||
//构造数据 | |||||
CharFormat.cbSize = sizeof(CharFormat); | |||||
CharFormat.dwMask = CFM_COLOR | CFM_BACKCOLOR; | |||||
CTraceServiceControl *pRichEditTrace = static_cast<CTraceServiceControl*>(pITraceService); | |||||
if (pRichEditTrace == NULL) return false; | |||||
CharFormat.crTextColor = RGB(0, 0, 0); | |||||
CharFormat.crBackColor = RGB(255, 255, 255); | |||||
lstrcpyn(CharFormat.szFaceName, TEXT("宋体"), sizeof(CharFormat.szFaceName)); | |||||
//获取时间 | |||||
SYSTEMTIME SystemTime; | |||||
TCHAR szTimeBuffer[4098] = { 0 }; | |||||
ZeroMemory(szTimeBuffer, sizeof(TCHAR)*4096); | |||||
GetLocalTime(&SystemTime); | |||||
_snwprintf(szTimeBuffer, sizeof(szTimeBuffer), TEXT("【 %04d-%02d-%02d %02d:%02d:%02d 】"), SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay, SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond); | |||||
// InsertString(szTimeBuffer, CharFormat); | |||||
//效验参数 | |||||
ITraceService* pITraceService = (ITraceService*)(QueryInterface(IID_ITraceService, VER_ITraceService)); | |||||
ASSERT(pITraceService != NULL); | |||||
if (pITraceService == NULL) return false; | |||||
//变量定义 | |||||
CHARFORMAT2 CharFormat; | |||||
ZeroMemory(&CharFormat, sizeof(CharFormat)); | |||||
//构造数据 | |||||
CharFormat.cbSize = sizeof(CharFormat); | |||||
CharFormat.dwMask = CFM_COLOR | CFM_BACKCOLOR; | |||||
CTraceServiceControl* pRichEditTrace = static_cast<CTraceServiceControl*>(pITraceService); | |||||
if (pRichEditTrace == NULL) return false; | |||||
CharFormat.crTextColor = RGB(0, 0, 0); | |||||
CharFormat.crBackColor = RGB(255, 255, 255); | |||||
lstrcpyn(CharFormat.szFaceName, TEXT("宋体"), sizeof(CharFormat.szFaceName)); | |||||
//获取时间 | |||||
SYSTEMTIME SystemTime; | |||||
TCHAR szTimeBuffer[4098] = { 0 }; | |||||
ZeroMemory(szTimeBuffer, sizeof(TCHAR) * 4096); | |||||
GetLocalTime(&SystemTime); | |||||
_snwprintf(szTimeBuffer, sizeof(szTimeBuffer), TEXT("【 %04d-%02d-%02d %02d:%02d:%02d 】"), SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay, SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond); | |||||
// InsertString(szTimeBuffer, CharFormat); | |||||
//长度判断 | //长度判断 | ||||
int iLen = wcslen(pszString); | int iLen = wcslen(pszString); | ||||
@@ -166,8 +299,8 @@ bool CTraceServiceControl::TraceString(LPCTSTR pszString, enTraceLevel TraceLeve | |||||
_sntprintf(szTimeBuffer, sizeof(szTimeBuffer), TEXT("%s%s\n"), szTimeBuffer, pszString); | _sntprintf(szTimeBuffer, sizeof(szTimeBuffer), TEXT("%s%s\n"), szTimeBuffer, pszString); | ||||
} | } | ||||
CharFormat.crTextColor = pRichEditTrace->GetTraceColor(TraceLevel); | |||||
return InsertString(szTimeBuffer, CharFormat); | |||||
CharFormat.crTextColor = pRichEditTrace->GetTraceColor(TraceLevel); | |||||
return InsertString(szTimeBuffer, CharFormat); | |||||
} | } | ||||
//重载函数 | //重载函数 | ||||
@@ -175,8 +308,8 @@ bool CTraceServiceControl::TraceString(LPCTSTR pszString, enTraceLevel TraceLeve | |||||
//绑定函数 | //绑定函数 | ||||
VOID CTraceServiceControl::PreSubclassWindow() | VOID CTraceServiceControl::PreSubclassWindow() | ||||
{ | { | ||||
__super::PreSubclassWindow(); | |||||
CTraceService::SetTraceService((IUnknownEx*)QueryInterface(IID_IUnknownEx, VER_IUnknownEx)); | |||||
__super::PreSubclassWindow(); | |||||
CTraceService::SetTraceService((IUnknownEx*)QueryInterface(IID_IUnknownEx, VER_IUnknownEx)); | |||||
} | } | ||||
//功能函数 | //功能函数 | ||||
@@ -184,24 +317,24 @@ VOID CTraceServiceControl::PreSubclassWindow() | |||||
//加载消息 | //加载消息 | ||||
bool CTraceServiceControl::LoadMessage(LPCTSTR pszFileName) | bool CTraceServiceControl::LoadMessage(LPCTSTR pszFileName) | ||||
{ | { | ||||
if (static_cast<CTraceServiceControl*>(g_pITraceService) == NULL) return false; | |||||
return static_cast<CTraceServiceControl*>(g_pITraceService)->LoadMessage(pszFileName); | |||||
if (static_cast<CTraceServiceControl*>(g_pITraceService) == NULL) return false; | |||||
return static_cast<CTraceServiceControl*>(g_pITraceService)->LoadMessage(pszFileName); | |||||
} | } | ||||
//保存信息 | //保存信息 | ||||
bool CTraceServiceControl::SaveMessage(LPCTSTR pszFileName) | bool CTraceServiceControl::SaveMessage(LPCTSTR pszFileName) | ||||
{ | { | ||||
if (static_cast<CTraceServiceControl*>(g_pITraceService) == NULL) return false; | |||||
return static_cast<CTraceServiceControl*>(g_pITraceService)->SaveMessage(pszFileName); | |||||
if (static_cast<CTraceServiceControl*>(g_pITraceService) == NULL) return false; | |||||
return static_cast<CTraceServiceControl*>(g_pITraceService)->SaveMessage(pszFileName); | |||||
} | } | ||||
//设置参数 | //设置参数 | ||||
bool CTraceServiceControl::SetParameter(LONG lMaxLineCount, LONG lReserveLineCount) | bool CTraceServiceControl::SetParameter(LONG lMaxLineCount, LONG lReserveLineCount) | ||||
{ | { | ||||
m_lMaxLineCount = lMaxLineCount; //最大行数 | |||||
m_lReserveLineCount = lReserveLineCount; //保留行数 | |||||
m_lMaxLineCount = lMaxLineCount; //最大行数 | |||||
m_lReserveLineCount = lReserveLineCount; //保留行数 | |||||
return true; | |||||
return true; | |||||
} | } | ||||
//辅助函数 | //辅助函数 | ||||
@@ -215,196 +348,196 @@ VOID CTraceServiceControl::InitializeService() | |||||
//获取颜色 | //获取颜色 | ||||
COLORREF CTraceServiceControl::GetTraceColor(enTraceLevel TraceLevel) | COLORREF CTraceServiceControl::GetTraceColor(enTraceLevel TraceLevel) | ||||
{ | { | ||||
switch (TraceLevel) | |||||
{ | |||||
case TraceLevel_Info: | |||||
return RGB(133, 124, 129); | |||||
break; | |||||
case TraceLevel_Normal: | |||||
return RGB(133, 124, 129); | |||||
break; | |||||
case TraceLevel_Warning: | |||||
return RGB(255, 0, 0); | |||||
break; | |||||
case TraceLevel_Exception: | |||||
return RGB(255, 0, 0); | |||||
break; | |||||
case TraceLevel_Debug: | |||||
return RGB(19, 127, 140); | |||||
break; | |||||
} | |||||
return RGB(0, 0, 0); | |||||
switch (TraceLevel) | |||||
{ | |||||
case TraceLevel_Info: | |||||
return RGB(133, 124, 129); | |||||
break; | |||||
case TraceLevel_Normal: | |||||
return RGB(133, 124, 129); | |||||
break; | |||||
case TraceLevel_Warning: | |||||
return RGB(255, 0, 0); | |||||
break; | |||||
case TraceLevel_Exception: | |||||
return RGB(255, 0, 0); | |||||
break; | |||||
case TraceLevel_Debug: | |||||
return RGB(19, 127, 140); | |||||
break; | |||||
} | |||||
return RGB(0, 0, 0); | |||||
} | } | ||||
//字符判断 | //字符判断 | ||||
bool EfficacyUrlChar(TCHAR chChar) | bool EfficacyUrlChar(TCHAR chChar) | ||||
{ | { | ||||
//特殊字符 | |||||
if (chChar == TEXT('.')) return true; | |||||
if (chChar == TEXT('=')) return true; | |||||
if (chChar == TEXT('+')) return true; | |||||
if (chChar == TEXT('?')) return true; | |||||
if (chChar == TEXT('#')) return true; | |||||
if (chChar == TEXT('%')) return true; | |||||
if (chChar == TEXT('/')) return true; | |||||
if (chChar == TEXT(':')) return true; | |||||
if (chChar == TEXT('&')) return true; | |||||
//字符范围 | |||||
if ((chChar >= TEXT('a')) && (chChar <= TEXT('z'))) return true; | |||||
if ((chChar >= TEXT('A')) && (chChar <= TEXT('Z'))) return true; | |||||
if ((chChar >= TEXT('0')) && (chChar <= TEXT('9'))) return true; | |||||
return false; | |||||
//特殊字符 | |||||
if (chChar == TEXT('.')) return true; | |||||
if (chChar == TEXT('=')) return true; | |||||
if (chChar == TEXT('+')) return true; | |||||
if (chChar == TEXT('?')) return true; | |||||
if (chChar == TEXT('#')) return true; | |||||
if (chChar == TEXT('%')) return true; | |||||
if (chChar == TEXT('/')) return true; | |||||
if (chChar == TEXT(':')) return true; | |||||
if (chChar == TEXT('&')) return true; | |||||
//字符范围 | |||||
if ((chChar >= TEXT('a')) && (chChar <= TEXT('z'))) return true; | |||||
if ((chChar >= TEXT('A')) && (chChar <= TEXT('Z'))) return true; | |||||
if ((chChar >= TEXT('0')) && (chChar <= TEXT('9'))) return true; | |||||
return false; | |||||
} | } | ||||
//地址判断 | //地址判断 | ||||
bool EfficacyUrlString(LPCTSTR pszUrl) | bool EfficacyUrlString(LPCTSTR pszUrl) | ||||
{ | { | ||||
for (WORD i = 0; i < wcslen(pszUrl); i++) | |||||
{ | |||||
if (!EfficacyUrlChar(pszUrl[i])) return false; | |||||
} | |||||
for (WORD i = 0; i < wcslen(pszUrl); i++) | |||||
{ | |||||
if (!EfficacyUrlChar(pszUrl[i])) return false; | |||||
} | |||||
return true; | |||||
return true; | |||||
} | } | ||||
//插入字串 | //插入字串 | ||||
bool CTraceServiceControl::InsertString(LPCTSTR pszString, CHARFORMAT2 & CharFormat) | |||||
{ | |||||
if (GetTextLength() >= 1024 * 512) | |||||
OnClearAll(); | |||||
//插入消息 | |||||
//变量定义 | |||||
bool bResumeSelect; | |||||
CHARRANGE CharRange; | |||||
//保存状态 | |||||
SetSel(-1L, -1L); | |||||
GetSel(CharRange.cpMin, CharRange.cpMax); | |||||
bResumeSelect = (CharRange.cpMax != CharRange.cpMin); | |||||
//搜索变量 | |||||
LPCTSTR pszHttp = TEXT("http://"); | |||||
const INT nHttpLength = lstrlen(pszHttp); | |||||
const INT nStringLength = lstrlen(pszString); | |||||
//索引定义 | |||||
INT nStringStart = 0; | |||||
INT nStringPause = 0; | |||||
//字符解释 | |||||
for (INT i = 0; i < nStringLength; i++) | |||||
{ | |||||
//变量定义 | |||||
INT nUrlPause = i; | |||||
//地址判断 | |||||
if (((i + nHttpLength) < nStringLength) && (memcmp(&pszString[i], pszHttp, nHttpLength*sizeof(TCHAR)) == 0)) | |||||
{ | |||||
//设置索引 | |||||
nUrlPause = i + nHttpLength; | |||||
//地址搜索 | |||||
while (nUrlPause < nStringLength) | |||||
{ | |||||
//字符判断 | |||||
if (EfficacyUrlChar(pszString[nUrlPause]) == true) | |||||
{ | |||||
nUrlPause++; | |||||
continue; | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
//终止字符 | |||||
if (nUrlPause <= (i + nHttpLength)) nStringPause = (i + 1); | |||||
//插入字符 | |||||
if ((i == (nStringLength - 1)) || (nUrlPause > (i + nHttpLength))) | |||||
{ | |||||
//普通字符 | |||||
if (nStringPause > nStringStart) | |||||
{ | |||||
//获取缓冲 | |||||
CString strNormalString; | |||||
LPTSTR pszNormalString = strNormalString.GetBuffer(nStringPause - nStringStart + 1); | |||||
//拷贝字符 | |||||
pszNormalString[nStringPause - nStringStart] = 0; | |||||
CopyMemory(pszNormalString, &pszString[nStringStart], (nStringPause - nStringStart)*sizeof(TCHAR)); | |||||
//释放缓冲 | |||||
strNormalString.ReleaseBuffer(); | |||||
//插入消息 | |||||
SetSel(-1L, -1L); | |||||
SetWordCharFormat(CharFormat); | |||||
ReplaceSel((LPCTSTR)strNormalString); | |||||
} | |||||
//连接地址 | |||||
if (nUrlPause > (i + nHttpLength)) | |||||
{ | |||||
//获取缓冲 | |||||
CString strUrlString; | |||||
LPTSTR pszUrlString = strUrlString.GetBuffer((nUrlPause - i) + 1); | |||||
//拷贝字符 | |||||
pszUrlString[nUrlPause - i] = 0; | |||||
CopyMemory(pszUrlString, &pszString[i], (nUrlPause - i)*sizeof(TCHAR)); | |||||
//释放缓冲 | |||||
strUrlString.ReleaseBuffer(); | |||||
//构造格式 | |||||
CHARFORMAT2 CharFormatHyper; | |||||
CharFormatHyper = CharFormat; | |||||
//效验地址 | |||||
if (EfficacyUrlString(strUrlString) == true) | |||||
{ | |||||
CharFormatHyper.dwMask |= CFM_LINK; | |||||
CharFormatHyper.dwEffects |= CFE_LINK; | |||||
} | |||||
//插入消息 | |||||
SetSel(-1L, -1L); | |||||
SetWordCharFormat(CharFormatHyper); | |||||
ReplaceSel((LPCTSTR)strUrlString); | |||||
} | |||||
//设置索引 | |||||
nStringStart = __max(i, nUrlPause); | |||||
} | |||||
//设置索引 | |||||
i += (nUrlPause - i); | |||||
} | |||||
//状态设置 | |||||
if (bResumeSelect == true) SetSel(CharRange); | |||||
else PostMessage(WM_VSCROLL, SB_BOTTOM, 0); | |||||
return true; | |||||
bool CTraceServiceControl::InsertString(LPCTSTR pszString, CHARFORMAT2& CharFormat) | |||||
{ | |||||
if (GetTextLength() >= 1024 * 512) | |||||
OnClearAll(); | |||||
//插入消息 | |||||
//变量定义 | |||||
bool bResumeSelect; | |||||
CHARRANGE CharRange; | |||||
//保存状态 | |||||
SetSel(-1L, -1L); | |||||
GetSel(CharRange.cpMin, CharRange.cpMax); | |||||
bResumeSelect = (CharRange.cpMax != CharRange.cpMin); | |||||
//搜索变量 | |||||
LPCTSTR pszHttp = TEXT("http://"); | |||||
const INT nHttpLength = lstrlen(pszHttp); | |||||
const INT nStringLength = lstrlen(pszString); | |||||
//索引定义 | |||||
INT nStringStart = 0; | |||||
INT nStringPause = 0; | |||||
//字符解释 | |||||
for (INT i = 0; i < nStringLength; i++) | |||||
{ | |||||
//变量定义 | |||||
INT nUrlPause = i; | |||||
//地址判断 | |||||
if (((i + nHttpLength) < nStringLength) && (memcmp(&pszString[i], pszHttp, nHttpLength * sizeof(TCHAR)) == 0)) | |||||
{ | |||||
//设置索引 | |||||
nUrlPause = i + nHttpLength; | |||||
//地址搜索 | |||||
while (nUrlPause < nStringLength) | |||||
{ | |||||
//字符判断 | |||||
if (EfficacyUrlChar(pszString[nUrlPause]) == true) | |||||
{ | |||||
nUrlPause++; | |||||
continue; | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
//终止字符 | |||||
if (nUrlPause <= (i + nHttpLength)) nStringPause = (i + 1); | |||||
//插入字符 | |||||
if ((i == (nStringLength - 1)) || (nUrlPause > (i + nHttpLength))) | |||||
{ | |||||
//普通字符 | |||||
if (nStringPause > nStringStart) | |||||
{ | |||||
//获取缓冲 | |||||
CString strNormalString; | |||||
LPTSTR pszNormalString = strNormalString.GetBuffer(nStringPause - nStringStart + 1); | |||||
//拷贝字符 | |||||
pszNormalString[nStringPause - nStringStart] = 0; | |||||
CopyMemory(pszNormalString, &pszString[nStringStart], (nStringPause - nStringStart) * sizeof(TCHAR)); | |||||
//释放缓冲 | |||||
strNormalString.ReleaseBuffer(); | |||||
//插入消息 | |||||
SetSel(-1L, -1L); | |||||
SetWordCharFormat(CharFormat); | |||||
ReplaceSel((LPCTSTR)strNormalString); | |||||
} | |||||
//连接地址 | |||||
if (nUrlPause > (i + nHttpLength)) | |||||
{ | |||||
//获取缓冲 | |||||
CString strUrlString; | |||||
LPTSTR pszUrlString = strUrlString.GetBuffer((nUrlPause - i) + 1); | |||||
//拷贝字符 | |||||
pszUrlString[nUrlPause - i] = 0; | |||||
CopyMemory(pszUrlString, &pszString[i], (nUrlPause - i) * sizeof(TCHAR)); | |||||
//释放缓冲 | |||||
strUrlString.ReleaseBuffer(); | |||||
//构造格式 | |||||
CHARFORMAT2 CharFormatHyper; | |||||
CharFormatHyper = CharFormat; | |||||
//效验地址 | |||||
if (EfficacyUrlString(strUrlString) == true) | |||||
{ | |||||
CharFormatHyper.dwMask |= CFM_LINK; | |||||
CharFormatHyper.dwEffects |= CFE_LINK; | |||||
} | |||||
//插入消息 | |||||
SetSel(-1L, -1L); | |||||
SetWordCharFormat(CharFormatHyper); | |||||
ReplaceSel((LPCTSTR)strUrlString); | |||||
} | |||||
//设置索引 | |||||
nStringStart = __max(i, nUrlPause); | |||||
} | |||||
//设置索引 | |||||
i += (nUrlPause - i); | |||||
} | |||||
//状态设置 | |||||
if (bResumeSelect == true) SetSel(CharRange); | |||||
else PostMessage(WM_VSCROLL, SB_BOTTOM, 0); | |||||
return true; | |||||
} | } | ||||
//回调函数 | //回调函数 | ||||
////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////// | ||||
//加载回调 | //加载回调 | ||||
DWORD CALLBACK CTraceServiceControl::LoadCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG * pcb) | |||||
DWORD CALLBACK CTraceServiceControl::LoadCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb) | |||||
{ | { | ||||
return 0; | |||||
return 0; | |||||
} | } | ||||
//保存回调 | //保存回调 | ||||
DWORD CALLBACK CTraceServiceControl::SaveCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG * pcb) | |||||
DWORD CALLBACK CTraceServiceControl::SaveCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb) | |||||
{ | { | ||||
return 0; | |||||
return 0; | |||||
} | } | ||||
//菜单命令 | //菜单命令 | ||||
@@ -412,41 +545,41 @@ DWORD CALLBACK CTraceServiceControl::SaveCallBack(DWORD dwCookie, LPBYTE pbBuff, | |||||
//删除信息 | //删除信息 | ||||
VOID CTraceServiceControl::OnClearAll() | VOID CTraceServiceControl::OnClearAll() | ||||
{ | { | ||||
SetWindowText(NULL); | |||||
SetWindowText(NULL); | |||||
} | } | ||||
//全部选择 | //全部选择 | ||||
VOID CTraceServiceControl::OnSelectAll() | VOID CTraceServiceControl::OnSelectAll() | ||||
{ | { | ||||
__super::SetSel(0, __super::GetTextLength()); | |||||
__super::SetSel(0, __super::GetTextLength()); | |||||
} | } | ||||
//拷贝字符 | //拷贝字符 | ||||
VOID CTraceServiceControl::OnCopyString() | VOID CTraceServiceControl::OnCopyString() | ||||
{ | { | ||||
__super::Copy(); | |||||
__super::Copy(); | |||||
} | } | ||||
//保存信息 | //保存信息 | ||||
VOID CTraceServiceControl::OnSaveString() | VOID CTraceServiceControl::OnSaveString() | ||||
{ | { | ||||
CFileDialog dlg(FALSE, TEXT("*.txt"), NULL, 4 | 2, TEXT("信息|*.txt||")); | |||||
if (dlg.DoModal() == IDOK) | |||||
{ | |||||
CString m_filename = dlg.GetPathName(); | |||||
CString strInput; | |||||
GetWindowText(strInput); | |||||
CFileDialog dlg(FALSE, TEXT("*.txt"), NULL, 4 | 2, TEXT("信息|*.txt||")); | |||||
if (dlg.DoModal() == IDOK) | |||||
{ | |||||
CString m_filename = dlg.GetPathName(); | |||||
CString strInput; | |||||
GetWindowText(strInput); | |||||
CFile file(m_filename, CFile::modeCreate | CFile::modeWrite); | |||||
file.Write(strInput, strInput.GetLength()); | |||||
file.Close(); | |||||
} | |||||
CFile file(m_filename, CFile::modeCreate | CFile::modeWrite); | |||||
file.Write(strInput, strInput.GetLength()); | |||||
file.Close(); | |||||
} | |||||
} | } | ||||
//删除字符 | //删除字符 | ||||
VOID CTraceServiceControl::OnDeleteString() | VOID CTraceServiceControl::OnDeleteString() | ||||
{ | { | ||||
ReplaceSel(NULL, true); | |||||
ReplaceSel(NULL, true); | |||||
} | } | ||||
//消息映射 | //消息映射 | ||||
@@ -454,31 +587,31 @@ VOID CTraceServiceControl::OnDeleteString() | |||||
//建立消息 | //建立消息 | ||||
INT CTraceServiceControl::OnCreate(LPCREATESTRUCT lpCreateStruct) | INT CTraceServiceControl::OnCreate(LPCREATESTRUCT lpCreateStruct) | ||||
{ | { | ||||
return __super::OnCreate(lpCreateStruct); | |||||
return __super::OnCreate(lpCreateStruct); | |||||
} | } | ||||
//右键消息 | //右键消息 | ||||
VOID CTraceServiceControl::OnRButtonDown(UINT nFlags, CPoint point) | VOID CTraceServiceControl::OnRButtonDown(UINT nFlags, CPoint point) | ||||
{ | { | ||||
CMenu menu; | |||||
ClientToScreen(&point); | |||||
CMenu menu; | |||||
ClientToScreen(&point); | |||||
menu.CreatePopupMenu(); | |||||
menu.CreatePopupMenu(); | |||||
CHARRANGE sl; | |||||
GetSel(sl); | |||||
menu.AppendMenu(MF_STRING | (sl.cpMax != sl.cpMin) ? 0 : MF_DISABLED | MF_GRAYED, IDM_MENU0, TEXT("复制(&C)\tCtrl+C")); | |||||
menu.AppendMenu(MF_STRING | (GetTextLength() > 0 && sl.cpMax - sl.cpMin < GetTextLength()) ? 0 : MF_DISABLED | MF_GRAYED, IDM_MENU1, TEXT("全选(&A)\tCtrl+A")); | |||||
menu.AppendMenu(MF_STRING | (false) ? 0 : MF_DISABLED | MF_GRAYED, IDM_MENU2, TEXT("删除(&D)")); | |||||
menu.AppendMenu(MF_STRING | (GetTextLength() > 0) ? 0 : MF_DISABLED | MF_GRAYED, IDM_MENU3, TEXT("清除信息")); | |||||
menu.AppendMenu(MF_SEPARATOR, 0); | |||||
menu.AppendMenu(MF_STRING | (GetTextLength() > 0) ? 0 : MF_DISABLED | MF_GRAYED, IDM_MENU4, TEXT("保存信息...")); | |||||
CHARRANGE sl; | |||||
GetSel(sl); | |||||
menu.AppendMenu(MF_STRING | (sl.cpMax != sl.cpMin) ? 0 : MF_DISABLED | MF_GRAYED, IDM_MENU0, TEXT("复制(&C)\tCtrl+C")); | |||||
menu.AppendMenu(MF_STRING | (GetTextLength() > 0 && sl.cpMax - sl.cpMin < GetTextLength()) ? 0 : MF_DISABLED | MF_GRAYED, IDM_MENU1, TEXT("全选(&A)\tCtrl+A")); | |||||
menu.AppendMenu(MF_STRING | (false) ? 0 : MF_DISABLED | MF_GRAYED, IDM_MENU2, TEXT("删除(&D)")); | |||||
menu.AppendMenu(MF_STRING | (GetTextLength() > 0) ? 0 : MF_DISABLED | MF_GRAYED, IDM_MENU3, TEXT("清除信息")); | |||||
menu.AppendMenu(MF_SEPARATOR, 0); | |||||
menu.AppendMenu(MF_STRING | (GetTextLength() > 0) ? 0 : MF_DISABLED | MF_GRAYED, IDM_MENU4, TEXT("保存信息...")); | |||||
TrackPopupMenu(menu.m_hMenu, nFlags, point.x, point.y, 0, m_hWnd, NULL); | |||||
TrackPopupMenu(menu.m_hMenu, nFlags, point.x, point.y, 0, m_hWnd, NULL); | |||||
} | } | ||||
//追踪消息 | //追踪消息 | ||||
LRESULT CTraceServiceControl::OnTraceServiceMessage(WPARAM wParam, LPARAM lParam) | LRESULT CTraceServiceControl::OnTraceServiceMessage(WPARAM wParam, LPARAM lParam) | ||||
{ | { | ||||
return 0; | |||||
return 0; | |||||
} | } |
@@ -6,35 +6,82 @@ | |||||
#include "KernelEngineHead.h" | #include "KernelEngineHead.h" | ||||
////////////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////////////// | ||||
//Spdlog | |||||
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE //在包含 *spdlog.h"之前,添加 SPDLOG_ACTIVE_LEVEL 宏定义可以设置期望的日志级别 | |||||
#include "../../Spdlog/spdlog/spdlog.h" | |||||
#include <direct.h> | |||||
#include <chrono> | |||||
#include <unordered_map> | |||||
class KERNEL_ENGINE_CLASS Logger | |||||
{ | |||||
public: | |||||
static void ShutDown(); | |||||
static Logger& GetInstance(); | |||||
std::shared_ptr<spdlog::logger> GetTableLogger(int tableId); | |||||
void DeleteTableLogger(int tableId); | |||||
std::shared_ptr<spdlog::logger>& GetLogger() | |||||
{ | |||||
return logger_; | |||||
}; | |||||
bool InitDefaultLog(std::string logger_name, std::string file_name, spdlog::level::level_enum log_level = spdlog::level::trace); | |||||
private: | |||||
Logger() | |||||
{ | |||||
//InitDefaultLog("klog", "daily_log.log"); | |||||
//logger_ = spdlog::default_logger(); | |||||
}; | |||||
~Logger(); | |||||
Logger(const Logger&) = delete; | |||||
Logger& operator=(const Logger&) = delete; | |||||
private: | |||||
std::unordered_map<int, std::shared_ptr<spdlog::logger>> tablelogger_;//桌子日志 | |||||
std::shared_ptr<spdlog::logger> logger_; //默认全局日志 | |||||
std::string logfile_dir_; | |||||
}; | |||||
#define LTrace(...) SPDLOG_LOGGER_TRACE(Logger::GetInstance().GetLogger(), __VA_ARGS__) | |||||
#define LDebug(...) SPDLOG_LOGGER_DEBUG(Logger::GetInstance().GetLogger(), __VA_ARGS__) | |||||
#define LInfo(...) SPDLOG_LOGGER_INFO(Logger::GetInstance().GetLogger(), __VA_ARGS__) | |||||
#define LWarn(...) SPDLOG_LOGGER_WARN(Logger::GetInstance().GetLogger(), __VA_ARGS__) | |||||
#define LError(...) SPDLOG_LOGGER_ERROR(Logger::GetInstance().GetLogger(), __VA_ARGS__) | |||||
#define LCritical(...) SPDLOG_LOGGER_CRITICAL(Logger::GetInstance().GetLogger(), __VA_ARGS__) | |||||
//追踪服务 | //追踪服务 | ||||
class KERNEL_ENGINE_CLASS CTraceService | class KERNEL_ENGINE_CLASS CTraceService | ||||
{ | { | ||||
//函数定义 | |||||
//函数定义 | |||||
protected: | protected: | ||||
//构造函数 | |||||
CTraceService(); | |||||
//构造函数 | |||||
CTraceService(); | |||||
//服务配置 | |||||
//服务配置 | |||||
public: | public: | ||||
//设置服务 | |||||
static bool SetTraceService(IUnknownEx * pIUnknownEx); | |||||
//获取服务 | |||||
static VOID * GetTraceService(const IID & Guid, DWORD dwQueryVer); | |||||
//获取服务 | |||||
static VOID * GetTraceServiceManager(const IID & Guid, DWORD dwQueryVer); | |||||
//状态管理 | |||||
//设置服务 | |||||
static bool SetTraceService(IUnknownEx* pIUnknownEx); | |||||
//获取服务 | |||||
static VOID* GetTraceService(const IID& Guid, DWORD dwQueryVer); | |||||
//获取服务 | |||||
static VOID* GetTraceServiceManager(const IID& Guid, DWORD dwQueryVer); | |||||
//状态管理 | |||||
public: | public: | ||||
//追踪状态 | |||||
static bool IsEnableTrace(enTraceLevel TraceLevel); | |||||
//追踪控制 | |||||
static bool EnableTrace(enTraceLevel TraceLevel, bool bEnableTrace); | |||||
//追踪状态 | |||||
static bool IsEnableTrace(enTraceLevel TraceLevel); | |||||
//追踪控制 | |||||
static bool EnableTrace(enTraceLevel TraceLevel, bool bEnableTrace); | |||||
//功能函数 | |||||
//功能函数 | |||||
public: | public: | ||||
//追踪信息 | |||||
static bool TraceString(LPCTSTR pszString, enTraceLevel TraceLevel); | |||||
//追踪信息 | |||||
static bool TraceString(LPCTSTR pszString, enTraceLevel TraceLevel); | |||||
}; | }; | ||||
////////////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////////////// | ||||
@@ -42,89 +89,89 @@ public: | |||||
//追踪服务 | //追踪服务 | ||||
class KERNEL_ENGINE_CLASS CTraceServiceControl : public CRichEditCtrl, public ITraceService | class KERNEL_ENGINE_CLASS CTraceServiceControl : public CRichEditCtrl, public ITraceService | ||||
{ | { | ||||
//辅助变量 | |||||
//辅助变量 | |||||
protected: | protected: | ||||
LONG m_lMaxLineCount; //最大行数 | |||||
LONG m_lReserveLineCount; //保留行数 | |||||
BYTE m_cbBuffer[MAX_ASYNCHRONISM_DATA]; //临时对象 | |||||
LONG m_lMaxLineCount; //最大行数 | |||||
LONG m_lReserveLineCount; //保留行数 | |||||
BYTE m_cbBuffer[MAX_ASYNCHRONISM_DATA]; //临时对象 | |||||
//组件变量 | |||||
//组件变量 | |||||
protected: | protected: | ||||
CWHDataQueue m_DataQueue; //数据队列 | |||||
CCriticalSection m_CriticalSection; //同步对象 | |||||
CWHDataQueue m_DataQueue; //数据队列 | |||||
CCriticalSection m_CriticalSection; //同步对象 | |||||
//函数定义 | |||||
//函数定义 | |||||
public: | public: | ||||
//构造函数 | |||||
CTraceServiceControl(); | |||||
//析构函数 | |||||
virtual ~CTraceServiceControl(); | |||||
//构造函数 | |||||
CTraceServiceControl(); | |||||
//析构函数 | |||||
virtual ~CTraceServiceControl(); | |||||
//基础接口 | |||||
//基础接口 | |||||
public: | public: | ||||
//释放对象 | |||||
virtual VOID Release() { return; } | |||||
//接口查询 | |||||
virtual VOID * QueryInterface(const IID & Guid, DWORD dwQueryVer); | |||||
//释放对象 | |||||
virtual VOID Release() { return; } | |||||
//接口查询 | |||||
virtual VOID* QueryInterface(const IID& Guid, DWORD dwQueryVer); | |||||
//信息接口 | |||||
//信息接口 | |||||
public: | public: | ||||
//追踪信息 | |||||
virtual bool TraceString(LPCTSTR pszString, enTraceLevel TraceLevel); | |||||
//追踪信息 | |||||
virtual bool TraceString(LPCTSTR pszString, enTraceLevel TraceLevel); | |||||
//重载函数 | |||||
//重载函数 | |||||
protected: | protected: | ||||
//绑定函数 | |||||
virtual VOID PreSubclassWindow(); | |||||
//绑定函数 | |||||
virtual VOID PreSubclassWindow(); | |||||
//功能函数 | |||||
//功能函数 | |||||
public: | public: | ||||
//加载消息 | |||||
bool LoadMessage(LPCTSTR pszFileName); | |||||
//保存信息 | |||||
bool SaveMessage(LPCTSTR pszFileName); | |||||
//设置参数 | |||||
bool SetParameter(LONG lMaxLineCount, LONG lReserveLineCount); | |||||
//辅助函数 | |||||
//加载消息 | |||||
bool LoadMessage(LPCTSTR pszFileName); | |||||
//保存信息 | |||||
bool SaveMessage(LPCTSTR pszFileName); | |||||
//设置参数 | |||||
bool SetParameter(LONG lMaxLineCount, LONG lReserveLineCount); | |||||
//辅助函数 | |||||
private: | private: | ||||
//配置服务 | |||||
VOID InitializeService(); | |||||
//获取颜色 | |||||
COLORREF GetTraceColor(enTraceLevel TraceLevel); | |||||
//插入字串 | |||||
bool InsertString(LPCTSTR pszString, CHARFORMAT2 & CharFormat); | |||||
//回调函数 | |||||
//配置服务 | |||||
VOID InitializeService(); | |||||
//获取颜色 | |||||
COLORREF GetTraceColor(enTraceLevel TraceLevel); | |||||
//插入字串 | |||||
bool InsertString(LPCTSTR pszString, CHARFORMAT2& CharFormat); | |||||
//回调函数 | |||||
private: | private: | ||||
//加载回调 | |||||
static DWORD CALLBACK LoadCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG * pcb); | |||||
//保存回调 | |||||
static DWORD CALLBACK SaveCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG * pcb); | |||||
//加载回调 | |||||
static DWORD CALLBACK LoadCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb); | |||||
//保存回调 | |||||
static DWORD CALLBACK SaveCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb); | |||||
//菜单命令 | |||||
//菜单命令 | |||||
protected: | protected: | ||||
//删除信息 | |||||
VOID OnClearAll(); | |||||
//全部选择 | |||||
VOID OnSelectAll(); | |||||
//拷贝字符 | |||||
VOID OnCopyString(); | |||||
//保存信息 | |||||
VOID OnSaveString(); | |||||
//删除字符 | |||||
VOID OnDeleteString(); | |||||
//消息映射 | |||||
//删除信息 | |||||
VOID OnClearAll(); | |||||
//全部选择 | |||||
VOID OnSelectAll(); | |||||
//拷贝字符 | |||||
VOID OnCopyString(); | |||||
//保存信息 | |||||
VOID OnSaveString(); | |||||
//删除字符 | |||||
VOID OnDeleteString(); | |||||
//消息映射 | |||||
protected: | protected: | ||||
//建立消息 | |||||
INT OnCreate(LPCREATESTRUCT lpCreateStruct); | |||||
//右键消息 | |||||
VOID OnRButtonDown(UINT nFlags, CPoint point); | |||||
//追踪消息 | |||||
LRESULT OnTraceServiceMessage(WPARAM wParam, LPARAM lParam); | |||||
DECLARE_MESSAGE_MAP() | |||||
//建立消息 | |||||
INT OnCreate(LPCREATESTRUCT lpCreateStruct); | |||||
//右键消息 | |||||
VOID OnRButtonDown(UINT nFlags, CPoint point); | |||||
//追踪消息 | |||||
LRESULT OnTraceServiceMessage(WPARAM wParam, LPARAM lParam); | |||||
DECLARE_MESSAGE_MAP() | |||||
}; | }; | ||||
////////////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////////////// | ||||
@@ -60,7 +60,7 @@ | |||||
<PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
<SDLCheck>false</SDLCheck> | <SDLCheck>false</SDLCheck> | ||||
<AdditionalOptions>/D _CRT_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions> | <AdditionalOptions>/D _CRT_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions> | ||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\Protobuf;$(SolutionDir)Source\ServerControl\CppRedis\includes;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\Protobuf;$(SolutionDir)Source\ServerControl\CppRedis\includes;$(SolutionDir)Source\Spdlog;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
</ClCompile> | </ClCompile> | ||||
<Link> | <Link> | ||||
<SubSystem>Windows</SubSystem> | <SubSystem>Windows</SubSystem> | ||||
@@ -41,9 +41,10 @@ CServiceUnits::~CServiceUnits() | |||||
//启动服务 | //启动服务 | ||||
bool CServiceUnits::StartService() | bool CServiceUnits::StartService() | ||||
{ | { | ||||
//效验状态 | |||||
ASSERT(m_ServiceStatus == ServiceStatus_Stop); | |||||
if (m_ServiceStatus != ServiceStatus_Stop) return false; | |||||
Logger::GetInstance().InitDefaultLog("LoginServer", "daily_log.log"); | |||||
//效验状态 | |||||
ASSERT(m_ServiceStatus == ServiceStatus_Stop); | |||||
if (m_ServiceStatus != ServiceStatus_Stop) return false; | |||||
//设置状态 | //设置状态 | ||||
SetServiceStatus(ServiceStatus_Config); | SetServiceStatus(ServiceStatus_Config); | ||||
@@ -92,7 +93,7 @@ bool CServiceUnits::ConcludeService() | |||||
if (m_TCPSocketService.GetInterface() != NULL) m_TCPSocketService->ConcludeService(); | if (m_TCPSocketService.GetInterface() != NULL) m_TCPSocketService->ConcludeService(); | ||||
//c++中使用google protobuf在关闭应用程序之前需要调用google::protobuf::ShutdownProtobufLibrary();要不然会有内存泄露 | //c++中使用google protobuf在关闭应用程序之前需要调用google::protobuf::ShutdownProtobufLibrary();要不然会有内存泄露 | ||||
google::protobuf::ShutdownProtobufLibrary(); | google::protobuf::ShutdownProtobufLibrary(); | ||||
Logger::ShutDown(); | |||||
m_RedisClient->DisConnect(); | m_RedisClient->DisConnect(); | ||||
return true; | return true; | ||||
} | } | ||||
@@ -59,6 +59,7 @@ | |||||
<PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_AFXEXT;MODULE_MANAGER_DLL;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_AFXEXT;MODULE_MANAGER_DLL;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
<SDLCheck>false</SDLCheck> | <SDLCheck>false</SDLCheck> | ||||
<AdditionalOptions>/D _CRT_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions> | <AdditionalOptions>/D _CRT_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions> | ||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\Spdlog;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
</ClCompile> | </ClCompile> | ||||
<Link> | <Link> | ||||
<SubSystem>Windows</SubSystem> | <SubSystem>Windows</SubSystem> | ||||
@@ -0,0 +1,93 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// | |||||
// Async logging using global thread pool | |||||
// All loggers created here share same global thread pool. | |||||
// Each log message is pushed to a queue along with a shared pointer to the | |||||
// logger. | |||||
// If a logger deleted while having pending messages in the queue, it's actual | |||||
// destruction will defer | |||||
// until all its messages are processed by the thread pool. | |||||
// This is because each message in the queue holds a shared_ptr to the | |||||
// originating logger. | |||||
#include <spdlog/async_logger.h> | |||||
#include <spdlog/details/registry.h> | |||||
#include <spdlog/details/thread_pool.h> | |||||
#include <memory> | |||||
#include <mutex> | |||||
#include <functional> | |||||
namespace spdlog { | |||||
namespace details { | |||||
static const size_t default_async_q_size = 8192; | |||||
} | |||||
// async logger factory - creates async loggers backed with thread pool. | |||||
// if a global thread pool doesn't already exist, create it with default queue | |||||
// size of 8192 items and single thread. | |||||
template<async_overflow_policy OverflowPolicy = async_overflow_policy::block> | |||||
struct async_factory_impl | |||||
{ | |||||
template<typename Sink, typename... SinkArgs> | |||||
static std::shared_ptr<async_logger> create(std::string logger_name, SinkArgs &&... args) | |||||
{ | |||||
auto ®istry_inst = details::registry::instance(); | |||||
// create global thread pool if not already exists.. | |||||
auto &mutex = registry_inst.tp_mutex(); | |||||
std::lock_guard<std::recursive_mutex> tp_lock(mutex); | |||||
auto tp = registry_inst.get_tp(); | |||||
if (tp == nullptr) | |||||
{ | |||||
tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1); | |||||
registry_inst.set_tp(tp); | |||||
} | |||||
auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); | |||||
auto new_logger = std::make_shared<async_logger>(std::move(logger_name), std::move(sink), std::move(tp), OverflowPolicy); | |||||
registry_inst.initialize_logger(new_logger); | |||||
return new_logger; | |||||
} | |||||
}; | |||||
using async_factory = async_factory_impl<async_overflow_policy::block>; | |||||
using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>; | |||||
template<typename Sink, typename... SinkArgs> | |||||
inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name, SinkArgs &&... sink_args) | |||||
{ | |||||
return async_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); | |||||
} | |||||
template<typename Sink, typename... SinkArgs> | |||||
inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name, SinkArgs &&... sink_args) | |||||
{ | |||||
return async_factory_nonblock::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); | |||||
} | |||||
// set global thread pool. | |||||
inline void init_thread_pool(size_t q_size, size_t thread_count, std::function<void()> on_thread_start) | |||||
{ | |||||
auto tp = std::make_shared<details::thread_pool>(q_size, thread_count, on_thread_start); | |||||
details::registry::instance().set_tp(std::move(tp)); | |||||
} | |||||
// set global thread pool. | |||||
inline void init_thread_pool(size_t q_size, size_t thread_count) | |||||
{ | |||||
init_thread_pool(q_size, thread_count, [] {}); | |||||
} | |||||
// get the global thread pool. | |||||
inline std::shared_ptr<spdlog::details::thread_pool> thread_pool() | |||||
{ | |||||
return details::registry::instance().get_tp(); | |||||
} | |||||
} // namespace spdlog |
@@ -0,0 +1,92 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/async_logger.h> | |||||
#endif | |||||
#include <spdlog/sinks/sink.h> | |||||
#include <spdlog/details/thread_pool.h> | |||||
#include <memory> | |||||
#include <string> | |||||
SPDLOG_INLINE spdlog::async_logger::async_logger( | |||||
std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) | |||||
: async_logger(std::move(logger_name), sinks_list.begin(), sinks_list.end(), std::move(tp), overflow_policy) | |||||
{} | |||||
SPDLOG_INLINE spdlog::async_logger::async_logger( | |||||
std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) | |||||
: async_logger(std::move(logger_name), {std::move(single_sink)}, std::move(tp), overflow_policy) | |||||
{} | |||||
// send the log message to the thread pool | |||||
SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg &msg) | |||||
{ | |||||
if (auto pool_ptr = thread_pool_.lock()) | |||||
{ | |||||
pool_ptr->post_log(shared_from_this(), msg, overflow_policy_); | |||||
} | |||||
else | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("async log: thread pool doesn't exist anymore")); | |||||
} | |||||
} | |||||
// send flush request to the thread pool | |||||
SPDLOG_INLINE void spdlog::async_logger::flush_() | |||||
{ | |||||
if (auto pool_ptr = thread_pool_.lock()) | |||||
{ | |||||
pool_ptr->post_flush(shared_from_this(), overflow_policy_); | |||||
} | |||||
else | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("async flush: thread pool doesn't exist anymore")); | |||||
} | |||||
} | |||||
// | |||||
// backend functions - called from the thread pool to do the actual job | |||||
// | |||||
SPDLOG_INLINE void spdlog::async_logger::backend_sink_it_(const details::log_msg &msg) | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
if (sink->should_log(msg.level)) | |||||
{ | |||||
SPDLOG_TRY | |||||
{ | |||||
sink->log(msg); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
} | |||||
if (should_flush_(msg)) | |||||
{ | |||||
backend_flush_(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void spdlog::async_logger::backend_flush_() | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
SPDLOG_TRY | |||||
{ | |||||
sink->flush(); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<spdlog::logger> spdlog::async_logger::clone(std::string new_name) | |||||
{ | |||||
auto cloned = std::make_shared<spdlog::async_logger>(*this); | |||||
cloned->name_ = std::move(new_name); | |||||
return cloned; | |||||
} |
@@ -0,0 +1,68 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// Fast asynchronous logger. | |||||
// Uses pre allocated queue. | |||||
// Creates a single back thread to pop messages from the queue and log them. | |||||
// | |||||
// Upon each log write the logger: | |||||
// 1. Checks if its log level is enough to log the message | |||||
// 2. Push a new copy of the message to a queue (or block the caller until | |||||
// space is available in the queue) | |||||
// Upon destruction, logs all remaining messages in the queue before | |||||
// destructing.. | |||||
#include <spdlog/logger.h> | |||||
namespace spdlog { | |||||
// Async overflow policy - block by default. | |||||
enum class async_overflow_policy | |||||
{ | |||||
block, // Block until message can be enqueued | |||||
overrun_oldest // Discard oldest message in the queue if full when trying to | |||||
// add new item. | |||||
}; | |||||
namespace details { | |||||
class thread_pool; | |||||
} | |||||
class async_logger final : public std::enable_shared_from_this<async_logger>, public logger | |||||
{ | |||||
friend class details::thread_pool; | |||||
public: | |||||
template<typename It> | |||||
async_logger(std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp, | |||||
async_overflow_policy overflow_policy = async_overflow_policy::block) | |||||
: logger(std::move(logger_name), begin, end) | |||||
, thread_pool_(std::move(tp)) | |||||
, overflow_policy_(overflow_policy) | |||||
{} | |||||
async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, | |||||
async_overflow_policy overflow_policy = async_overflow_policy::block); | |||||
async_logger(std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, | |||||
async_overflow_policy overflow_policy = async_overflow_policy::block); | |||||
std::shared_ptr<logger> clone(std::string new_name) override; | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override; | |||||
void flush_() override; | |||||
void backend_sink_it_(const details::log_msg &incoming_log_msg); | |||||
void backend_flush_(); | |||||
private: | |||||
std::weak_ptr<details::thread_pool> thread_pool_; | |||||
async_overflow_policy overflow_policy_; | |||||
}; | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "async_logger-inl.h" | |||||
#endif |
@@ -0,0 +1,57 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/common.h> | |||||
#endif | |||||
namespace spdlog { | |||||
namespace level { | |||||
static string_view_t level_string_views[] SPDLOG_LEVEL_NAMES; | |||||
static const char *short_level_names[] SPDLOG_SHORT_LEVEL_NAMES; | |||||
SPDLOG_INLINE string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT | |||||
{ | |||||
return level_string_views[l]; | |||||
} | |||||
SPDLOG_INLINE const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT | |||||
{ | |||||
return short_level_names[l]; | |||||
} | |||||
SPDLOG_INLINE spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT | |||||
{ | |||||
int level = 0; | |||||
for (const auto &level_str : level_string_views) | |||||
{ | |||||
if (level_str == name) | |||||
{ | |||||
return static_cast<level::level_enum>(level); | |||||
} | |||||
level++; | |||||
} | |||||
return level::off; | |||||
} | |||||
} // namespace level | |||||
SPDLOG_INLINE spdlog_ex::spdlog_ex(std::string msg) | |||||
: msg_(std::move(msg)) | |||||
{} | |||||
SPDLOG_INLINE spdlog_ex::spdlog_ex(const std::string &msg, int last_errno) | |||||
{ | |||||
memory_buf_t outbuf; | |||||
fmt::format_system_error(outbuf, last_errno, msg); | |||||
msg_ = fmt::to_string(outbuf); | |||||
} | |||||
SPDLOG_INLINE const char *spdlog_ex::what() const SPDLOG_NOEXCEPT | |||||
{ | |||||
return msg_.c_str(); | |||||
} | |||||
} // namespace spdlog |
@@ -0,0 +1,245 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/tweakme.h> | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <atomic> | |||||
#include <chrono> | |||||
#include <initializer_list> | |||||
#include <memory> | |||||
#include <exception> | |||||
#include <string> | |||||
#include <type_traits> | |||||
#include <functional> | |||||
#ifdef _WIN32 | |||||
#ifndef NOMINMAX | |||||
#define NOMINMAX // prevent windows redefining min/max | |||||
#endif | |||||
#ifndef WIN32_LEAN_AND_MEAN | |||||
#define WIN32_LEAN_AND_MEAN | |||||
#endif | |||||
#include <windows.h> | |||||
#endif //_WIN32 | |||||
#ifdef SPDLOG_COMPILED_LIB | |||||
#undef SPDLOG_HEADER_ONLY | |||||
#define SPDLOG_INLINE | |||||
#else | |||||
#define SPDLOG_HEADER_ONLY | |||||
#define SPDLOG_INLINE inline | |||||
#endif | |||||
#include <spdlog/fmt/fmt.h> | |||||
// visual studio upto 2013 does not support noexcept nor constexpr | |||||
#if defined(_MSC_VER) && (_MSC_VER < 1900) | |||||
#define SPDLOG_NOEXCEPT _NOEXCEPT | |||||
#define SPDLOG_CONSTEXPR | |||||
#else | |||||
#define SPDLOG_NOEXCEPT noexcept | |||||
#define SPDLOG_CONSTEXPR constexpr | |||||
#endif | |||||
#if defined(__GNUC__) || defined(__clang__) | |||||
#define SPDLOG_DEPRECATED __attribute__((deprecated)) | |||||
#elif defined(_MSC_VER) | |||||
#define SPDLOG_DEPRECATED __declspec(deprecated) | |||||
#else | |||||
#define SPDLOG_DEPRECATED | |||||
#endif | |||||
// disable thread local on msvc 2013 | |||||
#ifndef SPDLOG_NO_TLS | |||||
#if (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt) | |||||
#define SPDLOG_NO_TLS 1 | |||||
#endif | |||||
#endif | |||||
#ifndef SPDLOG_FUNCTION | |||||
#define SPDLOG_FUNCTION static_cast<const char *>(__FUNCTION__) | |||||
#endif | |||||
#ifdef SPDLOG_NO_EXCEPTIONS | |||||
#define SPDLOG_TRY | |||||
#define SPDLOG_THROW(ex) \ | |||||
do \ | |||||
{ \ | |||||
printf("spdlog fatal error: %s\n", ex.what()); \ | |||||
std::abort(); \ | |||||
} while (0) | |||||
#define SPDLOG_CATCH_ALL() | |||||
#else | |||||
#define SPDLOG_TRY try | |||||
#define SPDLOG_THROW(ex) throw(ex) | |||||
#define SPDLOG_CATCH_ALL() catch (...) | |||||
#endif | |||||
namespace spdlog { | |||||
class formatter; | |||||
namespace sinks { | |||||
class sink; | |||||
} | |||||
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
using filename_t = std::wstring; | |||||
#define SPDLOG_FILENAME_T(s) L##s | |||||
#else | |||||
using filename_t = std::string; | |||||
#define SPDLOG_FILENAME_T(s) s | |||||
#endif | |||||
using log_clock = std::chrono::system_clock; | |||||
using sink_ptr = std::shared_ptr<sinks::sink>; | |||||
using sinks_init_list = std::initializer_list<sink_ptr>; | |||||
using err_handler = std::function<void(const std::string &err_msg)>; | |||||
using string_view_t = fmt::basic_string_view<char>; | |||||
using wstring_view_t = fmt::basic_string_view<wchar_t>; | |||||
using memory_buf_t = fmt::basic_memory_buffer<char, 250>; | |||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
#ifndef _WIN32 | |||||
#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows | |||||
#else | |||||
template<typename T> | |||||
struct is_convertible_to_wstring_view : std::is_convertible<T, wstring_view_t> | |||||
{}; | |||||
#endif // _WIN32 | |||||
#else | |||||
template<typename> | |||||
struct is_convertible_to_wstring_view : std::false_type | |||||
{}; | |||||
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
#if defined(SPDLOG_NO_ATOMIC_LEVELS) | |||||
using level_t = details::null_atomic_int; | |||||
#else | |||||
using level_t = std::atomic<int>; | |||||
#endif | |||||
#define SPDLOG_LEVEL_TRACE 0 | |||||
#define SPDLOG_LEVEL_DEBUG 1 | |||||
#define SPDLOG_LEVEL_INFO 2 | |||||
#define SPDLOG_LEVEL_WARN 3 | |||||
#define SPDLOG_LEVEL_ERROR 4 | |||||
#define SPDLOG_LEVEL_CRITICAL 5 | |||||
#define SPDLOG_LEVEL_OFF 6 | |||||
#if !defined(SPDLOG_ACTIVE_LEVEL) | |||||
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO | |||||
#endif | |||||
// Log level enum | |||||
namespace level { | |||||
enum level_enum | |||||
{ | |||||
trace = SPDLOG_LEVEL_TRACE, | |||||
debug = SPDLOG_LEVEL_DEBUG, | |||||
info = SPDLOG_LEVEL_INFO, | |||||
warn = SPDLOG_LEVEL_WARN, | |||||
err = SPDLOG_LEVEL_ERROR, | |||||
critical = SPDLOG_LEVEL_CRITICAL, | |||||
off = SPDLOG_LEVEL_OFF, | |||||
}; | |||||
#if !defined(SPDLOG_LEVEL_NAMES) | |||||
#define SPDLOG_LEVEL_NAMES \ | |||||
{ \ | |||||
"trace", "debug", "info", "warning", "error", "critical", "off" \ | |||||
} | |||||
#endif | |||||
#if !defined(SPDLOG_SHORT_LEVEL_NAMES) | |||||
#define SPDLOG_SHORT_LEVEL_NAMES \ | |||||
{ \ | |||||
"T", "D", "I", "W", "E", "C", "O" \ | |||||
} | |||||
#endif | |||||
string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT; | |||||
const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT; | |||||
spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT; | |||||
using level_hasher = std::hash<int>; | |||||
} // namespace level | |||||
// | |||||
// Color mode used by sinks with color support. | |||||
// | |||||
enum class color_mode | |||||
{ | |||||
always, | |||||
automatic, | |||||
never | |||||
}; | |||||
// | |||||
// Pattern time - specific time getting to use for pattern_formatter. | |||||
// local time by default | |||||
// | |||||
enum class pattern_time_type | |||||
{ | |||||
local, // log localtime | |||||
utc // log utc | |||||
}; | |||||
// | |||||
// Log exception | |||||
// | |||||
class spdlog_ex : public std::exception | |||||
{ | |||||
public: | |||||
explicit spdlog_ex(std::string msg); | |||||
spdlog_ex(const std::string &msg, int last_errno); | |||||
const char *what() const SPDLOG_NOEXCEPT override; | |||||
private: | |||||
std::string msg_; | |||||
}; | |||||
struct source_loc | |||||
{ | |||||
SPDLOG_CONSTEXPR source_loc() = default; | |||||
SPDLOG_CONSTEXPR source_loc(const char *filename_in, int line_in, const char *funcname_in) | |||||
: filename{filename_in} | |||||
, line{line_in} | |||||
, funcname{funcname_in} | |||||
{} | |||||
SPDLOG_CONSTEXPR bool empty() const SPDLOG_NOEXCEPT | |||||
{ | |||||
return line == 0; | |||||
} | |||||
const char *filename{nullptr}; | |||||
int line{0}; | |||||
const char *funcname{nullptr}; | |||||
}; | |||||
namespace details { | |||||
// make_unique support for pre c++14 | |||||
#if __cplusplus >= 201402L // C++14 and beyond | |||||
using std::make_unique; | |||||
#else | |||||
template<typename T, typename... Args> | |||||
std::unique_ptr<T> make_unique(Args &&... args) | |||||
{ | |||||
static_assert(!std::is_array<T>::value, "arrays not supported"); | |||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); | |||||
} | |||||
#endif | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "common-inl.h" | |||||
#endif |
@@ -0,0 +1,69 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/details/backtracer.h> | |||||
#endif | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE backtracer::backtracer(const backtracer &other) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(other.mutex_); | |||||
enabled_ = other.enabled(); | |||||
messages_ = other.messages_; | |||||
} | |||||
SPDLOG_INLINE backtracer::backtracer(backtracer &&other) SPDLOG_NOEXCEPT | |||||
{ | |||||
std::lock_guard<std::mutex> lock(other.mutex_); | |||||
enabled_ = other.enabled(); | |||||
messages_ = std::move(other.messages_); | |||||
} | |||||
SPDLOG_INLINE backtracer &backtracer::operator=(backtracer other) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(mutex_); | |||||
enabled_ = other.enabled(); | |||||
messages_ = std::move(other.messages_); | |||||
return *this; | |||||
} | |||||
SPDLOG_INLINE void backtracer::enable(size_t size) | |||||
{ | |||||
std::lock_guard<std::mutex> lock{mutex_}; | |||||
enabled_.store(true, std::memory_order_relaxed); | |||||
messages_ = circular_q<log_msg_buffer>{size}; | |||||
} | |||||
SPDLOG_INLINE void backtracer::disable() | |||||
{ | |||||
std::lock_guard<std::mutex> lock{mutex_}; | |||||
enabled_.store(false, std::memory_order_relaxed); | |||||
} | |||||
SPDLOG_INLINE bool backtracer::enabled() const | |||||
{ | |||||
return enabled_.load(std::memory_order_relaxed); | |||||
} | |||||
SPDLOG_INLINE void backtracer::push_back(const log_msg &msg) | |||||
{ | |||||
std::lock_guard<std::mutex> lock{mutex_}; | |||||
messages_.push_back(log_msg_buffer{msg}); | |||||
} | |||||
// pop all items in the q and apply the given fun on each of them. | |||||
SPDLOG_INLINE void backtracer::foreach_pop(std::function<void(const details::log_msg &)> fun) | |||||
{ | |||||
std::lock_guard<std::mutex> lock{mutex_}; | |||||
while (!messages_.empty()) | |||||
{ | |||||
auto &front_msg = messages_.front(); | |||||
fun(front_msg); | |||||
messages_.pop_front(); | |||||
} | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,45 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/details/log_msg_buffer.h> | |||||
#include <spdlog/details/circular_q.h> | |||||
#include <atomic> | |||||
#include <mutex> | |||||
#include <functional> | |||||
// Store log messages in circular buffer. | |||||
// Useful for storing debug data in case of error/warning happens. | |||||
namespace spdlog { | |||||
namespace details { | |||||
class backtracer | |||||
{ | |||||
mutable std::mutex mutex_; | |||||
std::atomic<bool> enabled_{false}; | |||||
circular_q<log_msg_buffer> messages_; | |||||
public: | |||||
backtracer() = default; | |||||
backtracer(const backtracer &other); | |||||
backtracer(backtracer &&other) SPDLOG_NOEXCEPT; | |||||
backtracer &operator=(backtracer other); | |||||
void enable(size_t size); | |||||
void disable(); | |||||
bool enabled() const; | |||||
void push_back(const log_msg &msg); | |||||
// pop all items in the q and apply the given fun on each of them. | |||||
void foreach_pop(std::function<void(const details::log_msg &)> fun); | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "backtracer-inl.h" | |||||
#endif |
@@ -0,0 +1,140 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
// circular q view of std::vector. | |||||
#pragma once | |||||
#include <vector> | |||||
namespace spdlog { | |||||
namespace details { | |||||
template<typename T> | |||||
class circular_q | |||||
{ | |||||
size_t max_items_ = 0; | |||||
typename std::vector<T>::size_type head_ = 0; | |||||
typename std::vector<T>::size_type tail_ = 0; | |||||
size_t overrun_counter_ = 0; | |||||
std::vector<T> v_; | |||||
public: | |||||
using value_type = T; | |||||
// empty ctor - create a disabled queue with no elements allocated at all | |||||
circular_q() = default; | |||||
explicit circular_q(size_t max_items) | |||||
: max_items_(max_items + 1) // one item is reserved as marker for full q | |||||
, v_(max_items_) | |||||
{} | |||||
circular_q(const circular_q &) = default; | |||||
circular_q &operator=(const circular_q &) = default; | |||||
// move cannot be default, | |||||
// since we need to reset head_, tail_, etc to zero in the moved object | |||||
circular_q(circular_q &&other) SPDLOG_NOEXCEPT | |||||
{ | |||||
copy_moveable(std::move(other)); | |||||
} | |||||
circular_q &operator=(circular_q &&other) SPDLOG_NOEXCEPT | |||||
{ | |||||
copy_moveable(std::move(other)); | |||||
return *this; | |||||
} | |||||
// push back, overrun (oldest) item if no room left | |||||
void push_back(T &&item) | |||||
{ | |||||
if (max_items_ > 0) | |||||
{ | |||||
v_[tail_] = std::move(item); | |||||
tail_ = (tail_ + 1) % max_items_; | |||||
if (tail_ == head_) // overrun last item if full | |||||
{ | |||||
head_ = (head_ + 1) % max_items_; | |||||
++overrun_counter_; | |||||
} | |||||
} | |||||
} | |||||
// Return reference to the front item. | |||||
// If there are no elements in the container, the behavior is undefined. | |||||
const T &front() const | |||||
{ | |||||
return v_[head_]; | |||||
} | |||||
T &front() | |||||
{ | |||||
return v_[head_]; | |||||
} | |||||
// Return number of elements actually stored | |||||
size_t size() const | |||||
{ | |||||
if (tail_ >= head_) | |||||
{ | |||||
return tail_ - head_; | |||||
} | |||||
else | |||||
{ | |||||
return max_items_ - (head_ - tail_); | |||||
} | |||||
} | |||||
// Return const reference to item by index. | |||||
// If index is out of range 0…size()-1, the behavior is undefined. | |||||
const T &at(size_t i) const | |||||
{ | |||||
assert(i < size()); | |||||
return v_[(head_ + i) % max_items_]; | |||||
} | |||||
// Pop item from front. | |||||
// If there are no elements in the container, the behavior is undefined. | |||||
void pop_front() | |||||
{ | |||||
head_ = (head_ + 1) % max_items_; | |||||
} | |||||
bool empty() const | |||||
{ | |||||
return tail_ == head_; | |||||
} | |||||
bool full() const | |||||
{ | |||||
// head is ahead of the tail by 1 | |||||
if (max_items_ > 0) | |||||
{ | |||||
return ((tail_ + 1) % max_items_) == head_; | |||||
} | |||||
return false; | |||||
} | |||||
size_t overrun_counter() const | |||||
{ | |||||
return overrun_counter_; | |||||
} | |||||
private: | |||||
// copy from other&& and reset it to disabled state | |||||
void copy_moveable(circular_q &&other) SPDLOG_NOEXCEPT | |||||
{ | |||||
max_items_ = other.max_items_; | |||||
head_ = other.head_; | |||||
tail_ = other.tail_; | |||||
overrun_counter_ = other.overrun_counter_; | |||||
v_ = std::move(other.v_); | |||||
// put &&other in disabled, but valid state | |||||
other.max_items_ = 0; | |||||
other.head_ = other.tail_ = 0; | |||||
other.overrun_counter_ = 0; | |||||
} | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,32 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <mutex> | |||||
namespace spdlog { | |||||
namespace details { | |||||
struct console_mutex | |||||
{ | |||||
using mutex_t = std::mutex; | |||||
static mutex_t &mutex() | |||||
{ | |||||
static mutex_t s_mutex; | |||||
return s_mutex; | |||||
} | |||||
}; | |||||
struct console_nullmutex | |||||
{ | |||||
using mutex_t = null_mutex; | |||||
static mutex_t &mutex() | |||||
{ | |||||
static mutex_t s_mutex; | |||||
return s_mutex; | |||||
} | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,132 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/details/file_helper.h> | |||||
#endif | |||||
#include <spdlog/details/os.h> | |||||
#include <spdlog/common.h> | |||||
#include <cerrno> | |||||
#include <chrono> | |||||
#include <cstdio> | |||||
#include <string> | |||||
#include <thread> | |||||
#include <tuple> | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE file_helper::~file_helper() | |||||
{ | |||||
close(); | |||||
} | |||||
SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate) | |||||
{ | |||||
close(); | |||||
filename_ = fname; | |||||
auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab"); | |||||
for (int tries = 0; tries < open_tries_; ++tries) | |||||
{ | |||||
// create containing folder if not exists already. | |||||
os::create_dir(os::dir_name(fname)); | |||||
if (!os::fopen_s(&fd_, fname, mode)) | |||||
{ | |||||
return; | |||||
} | |||||
details::os::sleep_for_millis(open_interval_); | |||||
} | |||||
SPDLOG_THROW(spdlog_ex("Failed opening file " + os::filename_to_str(filename_) + " for writing", errno)); | |||||
} | |||||
SPDLOG_INLINE void file_helper::reopen(bool truncate) | |||||
{ | |||||
if (filename_.empty()) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("Failed re opening file - was not opened before")); | |||||
} | |||||
this->open(filename_, truncate); | |||||
} | |||||
SPDLOG_INLINE void file_helper::flush() | |||||
{ | |||||
std::fflush(fd_); | |||||
} | |||||
SPDLOG_INLINE void file_helper::close() | |||||
{ | |||||
if (fd_ != nullptr) | |||||
{ | |||||
std::fclose(fd_); | |||||
fd_ = nullptr; | |||||
} | |||||
} | |||||
SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) | |||||
{ | |||||
size_t msg_size = buf.size(); | |||||
auto data = buf.data(); | |||||
if (std::fwrite(data, 1, msg_size, fd_) != msg_size) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("Failed writing to file " + os::filename_to_str(filename_), errno)); | |||||
} | |||||
} | |||||
SPDLOG_INLINE size_t file_helper::size() const | |||||
{ | |||||
if (fd_ == nullptr) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(filename_))); | |||||
} | |||||
return os::filesize(fd_); | |||||
} | |||||
SPDLOG_INLINE const filename_t &file_helper::filename() const | |||||
{ | |||||
return filename_; | |||||
} | |||||
// | |||||
// return file path and its extension: | |||||
// | |||||
// "mylog.txt" => ("mylog", ".txt") | |||||
// "mylog" => ("mylog", "") | |||||
// "mylog." => ("mylog.", "") | |||||
// "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") | |||||
// | |||||
// the starting dot in filenames is ignored (hidden files): | |||||
// | |||||
// ".mylog" => (".mylog". "") | |||||
// "my_folder/.mylog" => ("my_folder/.mylog", "") | |||||
// "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") | |||||
SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension(const filename_t &fname) | |||||
{ | |||||
auto ext_index = fname.rfind('.'); | |||||
// no valid extension found - return whole path and empty string as | |||||
// extension | |||||
if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) | |||||
{ | |||||
return std::make_tuple(fname, filename_t()); | |||||
} | |||||
// treat cases like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" | |||||
auto folder_index = fname.rfind(details::os::folder_sep); | |||||
if (folder_index != filename_t::npos && folder_index >= ext_index - 1) | |||||
{ | |||||
return std::make_tuple(fname, filename_t()); | |||||
} | |||||
// finally - return a valid base and extension tuple | |||||
return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index)); | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,59 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/common.h> | |||||
#include <tuple> | |||||
namespace spdlog { | |||||
namespace details { | |||||
// Helper class for file sinks. | |||||
// When failing to open a file, retry several times(5) with a delay interval(10 ms). | |||||
// Throw spdlog_ex exception on errors. | |||||
class file_helper | |||||
{ | |||||
public: | |||||
explicit file_helper() = default; | |||||
file_helper(const file_helper &) = delete; | |||||
file_helper &operator=(const file_helper &) = delete; | |||||
~file_helper(); | |||||
void open(const filename_t &fname, bool truncate = false); | |||||
void reopen(bool truncate); | |||||
void flush(); | |||||
void close(); | |||||
void write(const memory_buf_t &buf); | |||||
size_t size() const; | |||||
const filename_t &filename() const; | |||||
// | |||||
// return file path and its extension: | |||||
// | |||||
// "mylog.txt" => ("mylog", ".txt") | |||||
// "mylog" => ("mylog", "") | |||||
// "mylog." => ("mylog.", "") | |||||
// "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") | |||||
// | |||||
// the starting dot in filenames is ignored (hidden files): | |||||
// | |||||
// ".mylog" => (".mylog". "") | |||||
// "my_folder/.mylog" => ("my_folder/.mylog", "") | |||||
// "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") | |||||
static std::tuple<filename_t, filename_t> split_by_extension(const filename_t &fname); | |||||
private: | |||||
const int open_tries_ = 5; | |||||
const int open_interval_ = 10; | |||||
std::FILE *fd_{nullptr}; | |||||
filename_t filename_; | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "file_helper-inl.h" | |||||
#endif |
@@ -0,0 +1,111 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <chrono> | |||||
#include <type_traits> | |||||
#include <spdlog/fmt/fmt.h> | |||||
#include <spdlog/common.h> | |||||
// Some fmt helpers to efficiently format and pad ints and strings | |||||
namespace spdlog { | |||||
namespace details { | |||||
namespace fmt_helper { | |||||
inline spdlog::string_view_t to_string_view(const memory_buf_t &buf) SPDLOG_NOEXCEPT | |||||
{ | |||||
return spdlog::string_view_t{buf.data(), buf.size()}; | |||||
} | |||||
inline void append_string_view(spdlog::string_view_t view, memory_buf_t &dest) | |||||
{ | |||||
auto *buf_ptr = view.data(); | |||||
if (buf_ptr != nullptr) | |||||
{ | |||||
dest.append(buf_ptr, buf_ptr + view.size()); | |||||
} | |||||
} | |||||
template<typename T> | |||||
inline void append_int(T n, memory_buf_t &dest) | |||||
{ | |||||
fmt::format_int i(n); | |||||
dest.append(i.data(), i.data() + i.size()); | |||||
} | |||||
template<typename T> | |||||
inline unsigned count_digits(T n) | |||||
{ | |||||
using count_type = typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type; | |||||
return static_cast<unsigned>(fmt::internal::count_digits(static_cast<count_type>(n))); | |||||
} | |||||
inline void pad2(int n, memory_buf_t &dest) | |||||
{ | |||||
if (n > 99) | |||||
{ | |||||
append_int(n, dest); | |||||
} | |||||
else if (n > 9) // 10-99 | |||||
{ | |||||
dest.push_back(static_cast<char>('0' + n / 10)); | |||||
dest.push_back(static_cast<char>('0' + n % 10)); | |||||
} | |||||
else if (n >= 0) // 0-9 | |||||
{ | |||||
dest.push_back('0'); | |||||
dest.push_back(static_cast<char>('0' + n)); | |||||
} | |||||
else // negatives (unlikely, but just in case, let fmt deal with it) | |||||
{ | |||||
fmt::format_to(dest, "{:02}", n); | |||||
} | |||||
} | |||||
template<typename T> | |||||
inline void pad_uint(T n, unsigned int width, memory_buf_t &dest) | |||||
{ | |||||
static_assert(std::is_unsigned<T>::value, "pad_uint must get unsigned T"); | |||||
auto digits = count_digits(n); | |||||
if (width > digits) | |||||
{ | |||||
const char *zeroes = "0000000000000000000"; | |||||
dest.append(zeroes, zeroes + width - digits); | |||||
} | |||||
append_int(n, dest); | |||||
} | |||||
template<typename T> | |||||
inline void pad3(T n, memory_buf_t &dest) | |||||
{ | |||||
pad_uint(n, 3, dest); | |||||
} | |||||
template<typename T> | |||||
inline void pad6(T n, memory_buf_t &dest) | |||||
{ | |||||
pad_uint(n, 6, dest); | |||||
} | |||||
template<typename T> | |||||
inline void pad9(T n, memory_buf_t &dest) | |||||
{ | |||||
pad_uint(n, 9, dest); | |||||
} | |||||
// return fraction of a second of the given time_point. | |||||
// e.g. | |||||
// fraction<std::milliseconds>(tp) -> will return the millis part of the second | |||||
template<typename ToDuration> | |||||
inline ToDuration time_fraction(log_clock::time_point tp) | |||||
{ | |||||
using std::chrono::duration_cast; | |||||
using std::chrono::seconds; | |||||
auto duration = tp.time_since_epoch(); | |||||
auto secs = duration_cast<seconds>(duration); | |||||
return duration_cast<ToDuration>(duration) - duration_cast<ToDuration>(secs); | |||||
} | |||||
} // namespace fmt_helper | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,31 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/details/log_msg.h> | |||||
#endif | |||||
#include <spdlog/details/os.h> | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE log_msg::log_msg(spdlog::source_loc loc, string_view_t logger_name, spdlog::level::level_enum lvl, spdlog::string_view_t msg) | |||||
: logger_name(logger_name) | |||||
, level(lvl) | |||||
, time(os::now()) | |||||
#ifndef SPDLOG_NO_THREAD_ID | |||||
, thread_id(os::thread_id()) | |||||
#endif | |||||
, source(loc) | |||||
, payload(msg) | |||||
{} | |||||
SPDLOG_INLINE log_msg::log_msg(string_view_t logger_name, spdlog::level::level_enum lvl, spdlog::string_view_t msg) | |||||
: log_msg(source_loc{}, logger_name, lvl, msg) | |||||
{} | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,35 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/common.h> | |||||
#include <string> | |||||
namespace spdlog { | |||||
namespace details { | |||||
struct log_msg | |||||
{ | |||||
log_msg() = default; | |||||
log_msg(source_loc loc, string_view_t logger_name, level::level_enum lvl, string_view_t msg); | |||||
log_msg(string_view_t logger_name, level::level_enum lvl, string_view_t msg); | |||||
log_msg(const log_msg &other) = default; | |||||
string_view_t logger_name; | |||||
level::level_enum level{level::off}; | |||||
log_clock::time_point time; | |||||
size_t thread_id{0}; | |||||
// wrapping the formatted text with color (updated by pattern_formatter). | |||||
mutable size_t color_range_start{0}; | |||||
mutable size_t color_range_end{0}; | |||||
source_loc source; | |||||
string_view_t payload; | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "log_msg-inl.h" | |||||
#endif |
@@ -0,0 +1,58 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/details/log_msg_buffer.h> | |||||
#endif | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE log_msg_buffer::log_msg_buffer(const log_msg &orig_msg) | |||||
: log_msg{orig_msg} | |||||
{ | |||||
buffer.append(logger_name.begin(), logger_name.end()); | |||||
buffer.append(payload.begin(), payload.end()); | |||||
update_string_views(); | |||||
} | |||||
SPDLOG_INLINE log_msg_buffer::log_msg_buffer(const log_msg_buffer &other) | |||||
: log_msg{other} | |||||
{ | |||||
buffer.append(logger_name.begin(), logger_name.end()); | |||||
buffer.append(payload.begin(), payload.end()); | |||||
update_string_views(); | |||||
} | |||||
SPDLOG_INLINE log_msg_buffer::log_msg_buffer(log_msg_buffer &&other) SPDLOG_NOEXCEPT : log_msg{other}, buffer{std::move(other.buffer)} | |||||
{ | |||||
update_string_views(); | |||||
} | |||||
SPDLOG_INLINE log_msg_buffer &log_msg_buffer::operator=(const log_msg_buffer &other) | |||||
{ | |||||
log_msg::operator=(other); | |||||
buffer.clear(); | |||||
buffer.append(other.buffer.data(), other.buffer.data() + other.buffer.size()); | |||||
update_string_views(); | |||||
return *this; | |||||
} | |||||
SPDLOG_INLINE log_msg_buffer &log_msg_buffer::operator=(log_msg_buffer &&other) SPDLOG_NOEXCEPT | |||||
{ | |||||
log_msg::operator=(other); | |||||
buffer = std::move(other.buffer); | |||||
update_string_views(); | |||||
return *this; | |||||
} | |||||
SPDLOG_INLINE void log_msg_buffer::update_string_views() | |||||
{ | |||||
logger_name = string_view_t{buffer.data(), logger_name.size()}; | |||||
payload = string_view_t{buffer.data() + logger_name.size(), payload.size()}; | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,33 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/details/log_msg.h> | |||||
namespace spdlog { | |||||
namespace details { | |||||
// Extend log_msg with internal buffer to store its payload. | |||||
// THis is needed since log_msg holds string_views that points to stack data. | |||||
class log_msg_buffer : public log_msg | |||||
{ | |||||
memory_buf_t buffer; | |||||
void update_string_views(); | |||||
public: | |||||
log_msg_buffer() = default; | |||||
explicit log_msg_buffer(const log_msg &orig_msg); | |||||
log_msg_buffer(const log_msg_buffer &other); | |||||
log_msg_buffer(log_msg_buffer &&other) SPDLOG_NOEXCEPT; | |||||
log_msg_buffer &operator=(const log_msg_buffer &other); | |||||
log_msg_buffer &operator=(log_msg_buffer &&other) SPDLOG_NOEXCEPT; | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "log_msg_buffer-inl.h" | |||||
#endif |
@@ -0,0 +1,120 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// multi producer-multi consumer blocking queue. | |||||
// enqueue(..) - will block until room found to put the new message. | |||||
// enqueue_nowait(..) - will return immediately with false if no room left in | |||||
// the queue. | |||||
// dequeue_for(..) - will block until the queue is not empty or timeout have | |||||
// passed. | |||||
#include <spdlog/details/circular_q.h> | |||||
#include <condition_variable> | |||||
#include <mutex> | |||||
namespace spdlog { | |||||
namespace details { | |||||
template<typename T> | |||||
class mpmc_blocking_queue | |||||
{ | |||||
public: | |||||
using item_type = T; | |||||
explicit mpmc_blocking_queue(size_t max_items) | |||||
: q_(max_items) | |||||
{} | |||||
#ifndef __MINGW32__ | |||||
// try to enqueue and block if no room left | |||||
void enqueue(T &&item) | |||||
{ | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
pop_cv_.wait(lock, [this] { return !this->q_.full(); }); | |||||
q_.push_back(std::move(item)); | |||||
} | |||||
push_cv_.notify_one(); | |||||
} | |||||
// enqueue immediately. overrun oldest message in the queue if no room left. | |||||
void enqueue_nowait(T &&item) | |||||
{ | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
q_.push_back(std::move(item)); | |||||
} | |||||
push_cv_.notify_one(); | |||||
} | |||||
// try to dequeue item. if no item found. wait upto timeout and try again | |||||
// Return true, if succeeded dequeue item, false otherwise | |||||
bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) | |||||
{ | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) | |||||
{ | |||||
return false; | |||||
} | |||||
popped_item = std::move(q_.front()); | |||||
q_.pop_front(); | |||||
} | |||||
pop_cv_.notify_one(); | |||||
return true; | |||||
} | |||||
#else | |||||
// apparently mingw deadlocks if the mutex is released before cv.notify_one(), | |||||
// so release the mutex at the very end each function. | |||||
// try to enqueue and block if no room left | |||||
void enqueue(T &&item) | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
pop_cv_.wait(lock, [this] { return !this->q_.full(); }); | |||||
q_.push_back(std::move(item)); | |||||
push_cv_.notify_one(); | |||||
} | |||||
// enqueue immediately. overrun oldest message in the queue if no room left. | |||||
void enqueue_nowait(T &&item) | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
q_.push_back(std::move(item)); | |||||
push_cv_.notify_one(); | |||||
} | |||||
// try to dequeue item. if no item found. wait upto timeout and try again | |||||
// Return true, if succeeded dequeue item, false otherwise | |||||
bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) | |||||
{ | |||||
return false; | |||||
} | |||||
popped_item = std::move(q_.front()); | |||||
q_.pop_front(); | |||||
pop_cv_.notify_one(); | |||||
return true; | |||||
} | |||||
#endif | |||||
size_t overrun_counter() | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
return q_.overrun_counter(); | |||||
} | |||||
private: | |||||
std::mutex queue_mutex_; | |||||
std::condition_variable push_cv_; | |||||
std::condition_variable pop_cv_; | |||||
spdlog::details::circular_q<T> q_; | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,49 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <atomic> | |||||
#include <utility> | |||||
// null, no cost dummy "mutex" and dummy "atomic" int | |||||
namespace spdlog { | |||||
namespace details { | |||||
struct null_mutex | |||||
{ | |||||
void lock() const {} | |||||
void unlock() const {} | |||||
bool try_lock() const | |||||
{ | |||||
return true; | |||||
} | |||||
}; | |||||
struct null_atomic_int | |||||
{ | |||||
int value; | |||||
null_atomic_int() = default; | |||||
explicit null_atomic_int(int new_value) | |||||
: value(new_value) | |||||
{} | |||||
int load(std::memory_order = std::memory_order_relaxed) const | |||||
{ | |||||
return value; | |||||
} | |||||
void store(int new_value, std::memory_order = std::memory_order_relaxed) | |||||
{ | |||||
value = new_value; | |||||
} | |||||
int exchange(int new_value, std::memory_order = std::memory_order_relaxed) | |||||
{ | |||||
std::swap(new_value, value); | |||||
return new_value; // return value before the call | |||||
} | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,536 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/details/os.h> | |||||
#endif | |||||
#include <spdlog/common.h> | |||||
#include <algorithm> | |||||
#include <chrono> | |||||
#include <cstdio> | |||||
#include <cstdlib> | |||||
#include <cstring> | |||||
#include <ctime> | |||||
#include <string> | |||||
#include <thread> | |||||
#include <array> | |||||
#include <sys/stat.h> | |||||
#include <sys/types.h> | |||||
#ifdef _WIN32 | |||||
#ifndef NOMINMAX | |||||
#define NOMINMAX // prevent windows redefining min/max | |||||
#endif | |||||
#ifndef WIN32_LEAN_AND_MEAN | |||||
#define WIN32_LEAN_AND_MEAN | |||||
#endif | |||||
#include <io.h> // _get_osfhandle and _isatty support | |||||
#include <process.h> // _get_pid support | |||||
#include <windows.h> | |||||
#ifdef __MINGW32__ | |||||
#include <share.h> | |||||
#endif | |||||
#if defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES) | |||||
#include <limits> | |||||
#endif | |||||
#include <direct.h> // for _mkdir/_wmkdir | |||||
#else // unix | |||||
#include <fcntl.h> | |||||
#include <unistd.h> | |||||
#ifdef __linux__ | |||||
#include <sys/syscall.h> //Use gettid() syscall under linux to get thread id | |||||
#elif defined(_AIX) | |||||
#include <pthread.h> // for pthread_getthreadid_np | |||||
#elif defined(__DragonFly__) || defined(__FreeBSD__) | |||||
#include <pthread_np.h> // for pthread_getthreadid_np | |||||
#elif defined(__NetBSD__) | |||||
#include <lwp.h> // for _lwp_self | |||||
#elif defined(__sun) | |||||
#include <thread.h> // for thr_self | |||||
#endif | |||||
#endif // unix | |||||
#ifndef __has_feature // Clang - feature checking macros. | |||||
#define __has_feature(x) 0 // Compatibility with non-clang compilers. | |||||
#endif | |||||
namespace spdlog { | |||||
namespace details { | |||||
namespace os { | |||||
SPDLOG_INLINE spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT | |||||
{ | |||||
#if defined __linux__ && defined SPDLOG_CLOCK_COARSE | |||||
timespec ts; | |||||
::clock_gettime(CLOCK_REALTIME_COARSE, &ts); | |||||
return std::chrono::time_point<log_clock, typename log_clock::duration>( | |||||
std::chrono::duration_cast<typename log_clock::duration>(std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec))); | |||||
#else | |||||
return log_clock::now(); | |||||
#endif | |||||
} | |||||
SPDLOG_INLINE std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
std::tm tm; | |||||
::localtime_s(&tm, &time_tt); | |||||
#else | |||||
std::tm tm; | |||||
::localtime_r(&time_tt, &tm); | |||||
#endif | |||||
return tm; | |||||
} | |||||
SPDLOG_INLINE std::tm localtime() SPDLOG_NOEXCEPT | |||||
{ | |||||
std::time_t now_t = ::time(nullptr); | |||||
return localtime(now_t); | |||||
} | |||||
SPDLOG_INLINE std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
std::tm tm; | |||||
::gmtime_s(&tm, &time_tt); | |||||
#else | |||||
std::tm tm; | |||||
::gmtime_r(&time_tt, &tm); | |||||
#endif | |||||
return tm; | |||||
} | |||||
SPDLOG_INLINE std::tm gmtime() SPDLOG_NOEXCEPT | |||||
{ | |||||
std::time_t now_t = ::time(nullptr); | |||||
return gmtime(now_t); | |||||
} | |||||
#ifdef SPDLOG_PREVENT_CHILD_FD | |||||
SPDLOG_INLINE void prevent_child_fd(FILE *f) | |||||
{ | |||||
#ifdef _WIN32 | |||||
auto file_handle = reinterpret_cast<HANDLE>(_get_osfhandle(::_fileno(f))); | |||||
if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) | |||||
SPDLOG_THROW(spdlog_ex("SetHandleInformation failed", errno)); | |||||
#else | |||||
auto fd = ::fileno(f); | |||||
if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("fcntl with FD_CLOEXEC failed", errno)); | |||||
} | |||||
#endif | |||||
} | |||||
#endif // SPDLOG_PREVENT_CHILD_FD | |||||
// fopen_s on non windows for writing | |||||
SPDLOG_INLINE bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode) | |||||
{ | |||||
#ifdef _WIN32 | |||||
#ifdef SPDLOG_WCHAR_FILENAMES | |||||
*fp = ::_wfsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); | |||||
#else | |||||
*fp = ::_fsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); | |||||
#endif | |||||
#else // unix | |||||
*fp = ::fopen((filename.c_str()), mode.c_str()); | |||||
#endif | |||||
#ifdef SPDLOG_PREVENT_CHILD_FD | |||||
// prevent child processes from inheriting log file descriptors | |||||
if (*fp != nullptr) | |||||
{ | |||||
prevent_child_fd(*fp); | |||||
} | |||||
#endif | |||||
return *fp == nullptr; | |||||
} | |||||
SPDLOG_INLINE int remove(const filename_t &filename) SPDLOG_NOEXCEPT | |||||
{ | |||||
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
return ::_wremove(filename.c_str()); | |||||
#else | |||||
return std::remove(filename.c_str()); | |||||
#endif | |||||
} | |||||
SPDLOG_INLINE int remove_if_exists(const filename_t &filename) SPDLOG_NOEXCEPT | |||||
{ | |||||
return path_exists(filename) ? remove(filename) : 0; | |||||
} | |||||
SPDLOG_INLINE int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT | |||||
{ | |||||
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
return ::_wrename(filename1.c_str(), filename2.c_str()); | |||||
#else | |||||
return std::rename(filename1.c_str(), filename2.c_str()); | |||||
#endif | |||||
} | |||||
// Return true if path exists (file or directory) | |||||
SPDLOG_INLINE bool path_exists(const filename_t &filename) SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
#ifdef SPDLOG_WCHAR_FILENAMES | |||||
auto attribs = ::GetFileAttributesW(filename.c_str()); | |||||
#else | |||||
auto attribs = ::GetFileAttributesA(filename.c_str()); | |||||
#endif | |||||
return attribs != INVALID_FILE_ATTRIBUTES; | |||||
#else // common linux/unix all have the stat system call | |||||
struct stat buffer; | |||||
return (::stat(filename.c_str(), &buffer) == 0); | |||||
#endif | |||||
} | |||||
// Return file size according to open FILE* object | |||||
SPDLOG_INLINE size_t filesize(FILE *f) | |||||
{ | |||||
if (f == nullptr) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("Failed getting file size. fd is null")); | |||||
} | |||||
#if defined(_WIN32) && !defined(__CYGWIN__) | |||||
int fd = ::_fileno(f); | |||||
#if _WIN64 // 64 bits | |||||
__int64 ret = ::_filelengthi64(fd); | |||||
if (ret >= 0) | |||||
{ | |||||
return static_cast<size_t>(ret); | |||||
} | |||||
#else // windows 32 bits | |||||
long ret = ::_filelength(fd); | |||||
if (ret >= 0) | |||||
{ | |||||
return static_cast<size_t>(ret); | |||||
} | |||||
#endif | |||||
#else // unix | |||||
int fd = ::fileno(f); | |||||
// 64 bits(but not in osx or cygwin, where fstat64 is deprecated) | |||||
#if (defined(__linux__) || defined(__sun) || defined(_AIX)) && (defined(__LP64__) || defined(_LP64)) | |||||
struct stat64 st; | |||||
if (::fstat64(fd, &st) == 0) | |||||
{ | |||||
return static_cast<size_t>(st.st_size); | |||||
} | |||||
#else // other unix or linux 32 bits or cygwin | |||||
struct stat st; | |||||
if (::fstat(fd, &st) == 0) | |||||
{ | |||||
return static_cast<size_t>(st.st_size); | |||||
} | |||||
#endif | |||||
#endif | |||||
SPDLOG_THROW(spdlog_ex("Failed getting file size from fd", errno)); | |||||
} | |||||
// Return utc offset in minutes or throw spdlog_ex on failure | |||||
SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) | |||||
{ | |||||
#ifdef _WIN32 | |||||
#if _WIN32_WINNT < _WIN32_WINNT_WS08 | |||||
TIME_ZONE_INFORMATION tzinfo; | |||||
auto rv = ::GetTimeZoneInformation(&tzinfo); | |||||
#else | |||||
DYNAMIC_TIME_ZONE_INFORMATION tzinfo; | |||||
auto rv = ::GetDynamicTimeZoneInformation(&tzinfo); | |||||
#endif | |||||
if (rv == TIME_ZONE_ID_INVALID) | |||||
SPDLOG_THROW(spdlog::spdlog_ex("Failed getting timezone info. ", errno)); | |||||
int offset = -tzinfo.Bias; | |||||
if (tm.tm_isdst) | |||||
{ | |||||
offset -= tzinfo.DaylightBias; | |||||
} | |||||
else | |||||
{ | |||||
offset -= tzinfo.StandardBias; | |||||
} | |||||
return offset; | |||||
#else | |||||
#if defined(sun) || defined(__sun) || defined(_AIX) || (!defined(_BSD_SOURCE) && !defined(_GNU_SOURCE)) | |||||
// 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris | |||||
struct helper | |||||
{ | |||||
static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), const std::tm &gmtm = details::os::gmtime()) | |||||
{ | |||||
int local_year = localtm.tm_year + (1900 - 1); | |||||
int gmt_year = gmtm.tm_year + (1900 - 1); | |||||
long int days = ( | |||||
// difference in day of year | |||||
localtm.tm_yday - | |||||
gmtm.tm_yday | |||||
// + intervening leap days | |||||
+ ((local_year >> 2) - (gmt_year >> 2)) - (local_year / 100 - gmt_year / 100) + | |||||
((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) | |||||
// + difference in years * 365 */ | |||||
+ (long int)(local_year - gmt_year) * 365); | |||||
long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); | |||||
long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); | |||||
long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec); | |||||
return secs; | |||||
} | |||||
}; | |||||
auto offset_seconds = helper::calculate_gmt_offset(tm); | |||||
#else | |||||
auto offset_seconds = tm.tm_gmtoff; | |||||
#endif | |||||
return static_cast<int>(offset_seconds / 60); | |||||
#endif | |||||
} | |||||
// Return current thread id as size_t | |||||
// It exists because the std::this_thread::get_id() is much slower(especially | |||||
// under VS 2013) | |||||
SPDLOG_INLINE size_t _thread_id() SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
return static_cast<size_t>(::GetCurrentThreadId()); | |||||
#elif defined(__linux__) | |||||
#if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) | |||||
#define SYS_gettid __NR_gettid | |||||
#endif | |||||
return static_cast<size_t>(::syscall(SYS_gettid)); | |||||
#elif defined(_AIX) || defined(__DragonFly__) || defined(__FreeBSD__) | |||||
return static_cast<size_t>(::pthread_getthreadid_np()); | |||||
#elif defined(__NetBSD__) | |||||
return static_cast<size_t>(::_lwp_self()); | |||||
#elif defined(__OpenBSD__) | |||||
return static_cast<size_t>(::getthrid()); | |||||
#elif defined(__sun) | |||||
return static_cast<size_t>(::thr_self()); | |||||
#elif __APPLE__ | |||||
uint64_t tid; | |||||
pthread_threadid_np(nullptr, &tid); | |||||
return static_cast<size_t>(tid); | |||||
#else // Default to standard C++11 (other Unix) | |||||
return static_cast<size_t>(std::hash<std::thread::id>()(std::this_thread::get_id())); | |||||
#endif | |||||
} | |||||
// Return current thread id as size_t (from thread local storage) | |||||
SPDLOG_INLINE size_t thread_id() SPDLOG_NOEXCEPT | |||||
{ | |||||
#if defined(SPDLOG_NO_TLS) | |||||
return _thread_id(); | |||||
#else // cache thread id in tls | |||||
static thread_local const size_t tid = _thread_id(); | |||||
return tid; | |||||
#endif | |||||
} | |||||
// This is avoid msvc issue in sleep_for that happens if the clock changes. | |||||
// See https://github.com/gabime/spdlog/issues/609 | |||||
SPDLOG_INLINE void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT | |||||
{ | |||||
#if defined(_WIN32) | |||||
::Sleep(milliseconds); | |||||
#else | |||||
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); | |||||
#endif | |||||
} | |||||
// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) | |||||
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
SPDLOG_INLINE std::string filename_to_str(const filename_t &filename) | |||||
{ | |||||
memory_buf_t buf; | |||||
wstr_to_utf8buf(filename, buf); | |||||
return fmt::to_string(buf); | |||||
} | |||||
#else | |||||
SPDLOG_INLINE std::string filename_to_str(const filename_t &filename) | |||||
{ | |||||
return filename; | |||||
} | |||||
#endif | |||||
SPDLOG_INLINE int pid() SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
return static_cast<int>(::GetCurrentProcessId()); | |||||
#else | |||||
return static_cast<int>(::getpid()); | |||||
#endif | |||||
} | |||||
// Determine if the terminal supports colors | |||||
// Source: https://github.com/agauniyal/rang/ | |||||
SPDLOG_INLINE bool is_color_terminal() SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
return true; | |||||
#else | |||||
static constexpr std::array<const char *, 14> Terms = { | |||||
{"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"}}; | |||||
const char *env_p = std::getenv("TERM"); | |||||
if (env_p == nullptr) | |||||
{ | |||||
return false; | |||||
} | |||||
static const bool result = | |||||
std::any_of(std::begin(Terms), std::end(Terms), [&](const char *term) { return std::strstr(env_p, term) != nullptr; }); | |||||
return result; | |||||
#endif | |||||
} | |||||
// Detrmine if the terminal attached | |||||
// Source: https://github.com/agauniyal/rang/ | |||||
SPDLOG_INLINE bool in_terminal(FILE *file) SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
return ::_isatty(_fileno(file)) != 0; | |||||
#else | |||||
return ::isatty(fileno(file)) != 0; | |||||
#endif | |||||
} | |||||
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) | |||||
SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target) | |||||
{ | |||||
if (wstr.size() > static_cast<size_t>((std::numeric_limits<int>::max)())) | |||||
{ | |||||
SPDLOG_THROW(spdlog::spdlog_ex("UTF-16 string is too big to be converted to UTF-8")); | |||||
} | |||||
int wstr_size = static_cast<int>(wstr.size()); | |||||
if (wstr_size == 0) | |||||
{ | |||||
target.resize(0); | |||||
return; | |||||
} | |||||
int result_size = static_cast<int>(target.capacity()); | |||||
if ((wstr_size + 1) * 2 > result_size) | |||||
{ | |||||
result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, NULL, 0, NULL, NULL); | |||||
} | |||||
if (result_size > 0) | |||||
{ | |||||
target.resize(result_size); | |||||
result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, target.data(), result_size, NULL, NULL); | |||||
if (result_size > 0) | |||||
{ | |||||
target.resize(result_size); | |||||
return; | |||||
} | |||||
} | |||||
SPDLOG_THROW(spdlog::spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError()))); | |||||
} | |||||
#endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) | |||||
// return true on success | |||||
SPDLOG_INLINE bool mkdir_(const filename_t &path) | |||||
{ | |||||
#ifdef _WIN32 | |||||
#ifdef SPDLOG_WCHAR_FILENAMES | |||||
return ::_wmkdir(path.c_str()) == 0; | |||||
#else | |||||
return ::_mkdir(path.c_str()) == 0; | |||||
#endif | |||||
#else | |||||
return ::mkdir(path.c_str(), mode_t(0755)) == 0; | |||||
#endif | |||||
} | |||||
// create the given directory - and all directories leading to it | |||||
// return true on success or if the directory already exists | |||||
SPDLOG_INLINE bool create_dir(filename_t path) | |||||
{ | |||||
if (path_exists(path)) | |||||
{ | |||||
return true; | |||||
} | |||||
if (path.empty()) | |||||
{ | |||||
return false; | |||||
} | |||||
#ifdef _WIN32 | |||||
// support forward slash in windows | |||||
std::replace(path.begin(), path.end(), '/', folder_sep); | |||||
#endif | |||||
size_t search_offset = 0; | |||||
do | |||||
{ | |||||
auto token_pos = path.find(folder_sep, search_offset); | |||||
// treat the entire path as a folder if no folder separator not found | |||||
if (token_pos == filename_t::npos) | |||||
{ | |||||
token_pos = path.size(); | |||||
} | |||||
auto subdir = path.substr(0, token_pos); | |||||
if (!subdir.empty() && !path_exists(subdir) && !mkdir_(subdir)) | |||||
{ | |||||
return false; // return error if failed creating dir | |||||
} | |||||
search_offset = token_pos + 1; | |||||
} while (search_offset < path.size()); | |||||
return true; | |||||
} | |||||
// Return directory name from given path or empty string | |||||
// "abc/file" => "abc" | |||||
// "abc/" => "abc" | |||||
// "abc" => "" | |||||
// "abc///" => "abc//" | |||||
SPDLOG_INLINE filename_t dir_name(filename_t path) | |||||
{ | |||||
#ifdef _WIN32 | |||||
// support forward slash in windows | |||||
std::replace(path.begin(), path.end(), '/', folder_sep); | |||||
#endif | |||||
auto pos = path.find_last_of(folder_sep); | |||||
return pos != filename_t::npos ? path.substr(0, pos) : filename_t{}; | |||||
} | |||||
} // namespace os | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,111 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/common.h> | |||||
#include <ctime> // std::time_t | |||||
namespace spdlog { | |||||
namespace details { | |||||
namespace os { | |||||
spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT; | |||||
std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT; | |||||
std::tm localtime() SPDLOG_NOEXCEPT; | |||||
std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT; | |||||
std::tm gmtime() SPDLOG_NOEXCEPT; | |||||
// eol definition | |||||
#if !defined(SPDLOG_EOL) | |||||
#ifdef _WIN32 | |||||
#define SPDLOG_EOL "\r\n" | |||||
#else | |||||
#define SPDLOG_EOL "\n" | |||||
#endif | |||||
#endif | |||||
SPDLOG_CONSTEXPR static const char *default_eol = SPDLOG_EOL; | |||||
// folder separator | |||||
#ifdef _WIN32 | |||||
static const char folder_sep = '\\'; | |||||
#else | |||||
SPDLOG_CONSTEXPR static const char folder_sep = '/'; | |||||
#endif | |||||
#ifdef SPDLOG_PREVENT_CHILD_FD | |||||
void prevent_child_fd(FILE *f); | |||||
#endif | |||||
// fopen_s on non windows for writing | |||||
bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode); | |||||
// Remove filename. return 0 on success | |||||
int remove(const filename_t &filename) SPDLOG_NOEXCEPT; | |||||
// Remove file if exists. return 0 on success | |||||
// Note: Non atomic (might return failure to delete if concurrently deleted by other process/thread) | |||||
int remove_if_exists(const filename_t &filename) SPDLOG_NOEXCEPT; | |||||
int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT; | |||||
// Return if file exists. | |||||
bool path_exists(const filename_t &filename) SPDLOG_NOEXCEPT; | |||||
// Return file size according to open FILE* object | |||||
size_t filesize(FILE *f); | |||||
// Return utc offset in minutes or throw spdlog_ex on failure | |||||
int utc_minutes_offset(const std::tm &tm = details::os::localtime()); | |||||
// Return current thread id as size_t | |||||
// It exists because the std::this_thread::get_id() is much slower(especially | |||||
// under VS 2013) | |||||
size_t _thread_id() SPDLOG_NOEXCEPT; | |||||
// Return current thread id as size_t (from thread local storage) | |||||
size_t thread_id() SPDLOG_NOEXCEPT; | |||||
// This is avoid msvc issue in sleep_for that happens if the clock changes. | |||||
// See https://github.com/gabime/spdlog/issues/609 | |||||
void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT; | |||||
std::string filename_to_str(const filename_t &filename); | |||||
int pid() SPDLOG_NOEXCEPT; | |||||
// Determine if the terminal supports colors | |||||
// Source: https://github.com/agauniyal/rang/ | |||||
bool is_color_terminal() SPDLOG_NOEXCEPT; | |||||
// Determine if the terminal attached | |||||
// Source: https://github.com/agauniyal/rang/ | |||||
bool in_terminal(FILE *file) SPDLOG_NOEXCEPT; | |||||
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) | |||||
void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target); | |||||
#endif | |||||
// Return directory name from given path or empty string | |||||
// "abc/file" => "abc" | |||||
// "abc/" => "abc" | |||||
// "abc" => "" | |||||
// "abc///" => "abc//" | |||||
filename_t dir_name(filename_t path); | |||||
// Create a dir from the given path. | |||||
// Return true if succeeded or if this dir already exists. | |||||
bool create_dir(filename_t path); | |||||
} // namespace os | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "os-inl.h" | |||||
#endif |
@@ -0,0 +1,103 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/log_msg.h> | |||||
#include <spdlog/details/os.h> | |||||
#include <spdlog/formatter.h> | |||||
#include <chrono> | |||||
#include <ctime> | |||||
#include <memory> | |||||
#include <string> | |||||
#include <vector> | |||||
namespace spdlog { | |||||
namespace details { | |||||
// padding information. | |||||
struct padding_info | |||||
{ | |||||
enum pad_side | |||||
{ | |||||
left, | |||||
right, | |||||
center | |||||
}; | |||||
padding_info() = default; | |||||
padding_info(size_t width, padding_info::pad_side side, bool truncate) | |||||
: width_(width) | |||||
, side_(side) | |||||
, truncate_(truncate) | |||||
, enabled_(true) | |||||
{} | |||||
bool enabled() const | |||||
{ | |||||
return enabled_; | |||||
} | |||||
const size_t width_ = 0; | |||||
const pad_side side_ = left; | |||||
bool truncate_ = false; | |||||
bool enabled_ = false; | |||||
}; | |||||
class flag_formatter | |||||
{ | |||||
public: | |||||
explicit flag_formatter(padding_info padinfo) | |||||
: padinfo_(padinfo) | |||||
{} | |||||
flag_formatter() = default; | |||||
virtual ~flag_formatter() = default; | |||||
virtual void format(const details::log_msg &msg, const std::tm &tm_time, memory_buf_t &dest) = 0; | |||||
protected: | |||||
padding_info padinfo_; | |||||
}; | |||||
} // namespace details | |||||
class pattern_formatter final : public formatter | |||||
{ | |||||
public: | |||||
explicit pattern_formatter( | |||||
std::string pattern, pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol); | |||||
// use default pattern is not given | |||||
explicit pattern_formatter(pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol); | |||||
pattern_formatter(const pattern_formatter &other) = delete; | |||||
pattern_formatter &operator=(const pattern_formatter &other) = delete; | |||||
std::unique_ptr<formatter> clone() const override; | |||||
void format(const details::log_msg &msg, memory_buf_t &dest) override; | |||||
private: | |||||
std::string pattern_; | |||||
std::string eol_; | |||||
pattern_time_type pattern_time_type_; | |||||
std::tm cached_tm_; | |||||
std::chrono::seconds last_log_secs_; | |||||
std::vector<std::unique_ptr<details::flag_formatter>> formatters_; | |||||
std::tm get_time_(const details::log_msg &msg); | |||||
template<typename Padder> | |||||
void handle_flag_(char flag, details::padding_info padding); | |||||
// Extract given pad spec (e.g. %8X) | |||||
// Advance the given it pass the end of the padding spec found (if any) | |||||
// Return padding. | |||||
details::padding_info handle_padspec_(std::string::const_iterator &it, std::string::const_iterator end); | |||||
void compile_pattern_(const std::string &pattern); | |||||
}; | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "pattern_formatter-inl.h" | |||||
#endif |
@@ -0,0 +1,49 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/details/periodic_worker.h> | |||||
#endif | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE periodic_worker::periodic_worker(const std::function<void()> &callback_fun, std::chrono::seconds interval) | |||||
{ | |||||
active_ = (interval > std::chrono::seconds::zero()); | |||||
if (!active_) | |||||
{ | |||||
return; | |||||
} | |||||
worker_thread_ = std::thread([this, callback_fun, interval]() { | |||||
for (;;) | |||||
{ | |||||
std::unique_lock<std::mutex> lock(this->mutex_); | |||||
if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) | |||||
{ | |||||
return; // active_ == false, so exit this thread | |||||
} | |||||
callback_fun(); | |||||
} | |||||
}); | |||||
} | |||||
// stop the worker thread and join it | |||||
SPDLOG_INLINE periodic_worker::~periodic_worker() | |||||
{ | |||||
if (worker_thread_.joinable()) | |||||
{ | |||||
{ | |||||
std::lock_guard<std::mutex> lock(mutex_); | |||||
active_ = false; | |||||
} | |||||
cv_.notify_one(); | |||||
worker_thread_.join(); | |||||
} | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,40 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// periodic worker thread - periodically executes the given callback function. | |||||
// | |||||
// RAII over the owned thread: | |||||
// creates the thread on construction. | |||||
// stops and joins the thread on destruction (if the thread is executing a callback, wait for it to finish first). | |||||
#include <chrono> | |||||
#include <condition_variable> | |||||
#include <functional> | |||||
#include <mutex> | |||||
#include <thread> | |||||
namespace spdlog { | |||||
namespace details { | |||||
class periodic_worker | |||||
{ | |||||
public: | |||||
periodic_worker(const std::function<void()> &callback_fun, std::chrono::seconds interval); | |||||
periodic_worker(const periodic_worker &) = delete; | |||||
periodic_worker &operator=(const periodic_worker &) = delete; | |||||
// stop the worker thread and join it | |||||
~periodic_worker(); | |||||
private: | |||||
bool active_; | |||||
std::thread worker_thread_; | |||||
std::mutex mutex_; | |||||
std::condition_variable cv_; | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "periodic_worker-inl.h" | |||||
#endif |
@@ -0,0 +1,284 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/details/registry.h> | |||||
#endif | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/periodic_worker.h> | |||||
#include <spdlog/logger.h> | |||||
#include <spdlog/details/pattern_formatter.h> | |||||
#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
// support for the default stdout color logger | |||||
#ifdef _WIN32 | |||||
#include <spdlog/sinks/wincolor_sink.h> | |||||
#else | |||||
#include <spdlog/sinks/ansicolor_sink.h> | |||||
#endif | |||||
#endif // SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
#include <chrono> | |||||
#include <functional> | |||||
#include <memory> | |||||
#include <string> | |||||
#include <unordered_map> | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE registry::registry() | |||||
: formatter_(new pattern_formatter()) | |||||
{ | |||||
#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
// create default logger (ansicolor_stdout_sink_mt or wincolor_stdout_sink_mt in windows). | |||||
#ifdef _WIN32 | |||||
auto color_sink = std::make_shared<sinks::wincolor_stdout_sink_mt>(); | |||||
#else | |||||
auto color_sink = std::make_shared<sinks::ansicolor_stdout_sink_mt>(); | |||||
#endif | |||||
const char *default_logger_name = ""; | |||||
default_logger_ = std::make_shared<spdlog::logger>(default_logger_name, std::move(color_sink)); | |||||
loggers_[default_logger_name] = default_logger_; | |||||
#endif // SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
} | |||||
SPDLOG_INLINE void registry::register_logger(std::shared_ptr<logger> new_logger) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
register_logger_(std::move(new_logger)); | |||||
} | |||||
SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
new_logger->set_formatter(formatter_->clone()); | |||||
if (err_handler_) | |||||
{ | |||||
new_logger->set_error_handler(err_handler_); | |||||
} | |||||
new_logger->set_level(level_); | |||||
new_logger->flush_on(flush_level_); | |||||
if (backtrace_n_messages_ > 0) | |||||
{ | |||||
new_logger->enable_backtrace(backtrace_n_messages_); | |||||
} | |||||
if (automatic_registration_) | |||||
{ | |||||
register_logger_(std::move(new_logger)); | |||||
} | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<logger> registry::get(const std::string &logger_name) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
auto found = loggers_.find(logger_name); | |||||
return found == loggers_.end() ? nullptr : found->second; | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<logger> registry::default_logger() | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
return default_logger_; | |||||
} | |||||
// Return raw ptr to the default logger. | |||||
// To be used directly by the spdlog default api (e.g. spdlog::info) | |||||
// This make the default API faster, but cannot be used concurrently with set_default_logger(). | |||||
// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. | |||||
SPDLOG_INLINE logger *registry::get_default_raw() | |||||
{ | |||||
return default_logger_.get(); | |||||
} | |||||
// set default logger. | |||||
// default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. | |||||
SPDLOG_INLINE void registry::set_default_logger(std::shared_ptr<logger> new_default_logger) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
// remove previous default logger from the map | |||||
if (default_logger_ != nullptr) | |||||
{ | |||||
loggers_.erase(default_logger_->name()); | |||||
} | |||||
if (new_default_logger != nullptr) | |||||
{ | |||||
loggers_[new_default_logger->name()] = new_default_logger; | |||||
} | |||||
default_logger_ = std::move(new_default_logger); | |||||
} | |||||
SPDLOG_INLINE void registry::set_tp(std::shared_ptr<thread_pool> tp) | |||||
{ | |||||
std::lock_guard<std::recursive_mutex> lock(tp_mutex_); | |||||
tp_ = std::move(tp); | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<thread_pool> registry::get_tp() | |||||
{ | |||||
std::lock_guard<std::recursive_mutex> lock(tp_mutex_); | |||||
return tp_; | |||||
} | |||||
// Set global formatter. Each sink in each logger will get a clone of this object | |||||
SPDLOG_INLINE void registry::set_formatter(std::unique_ptr<formatter> formatter) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
formatter_ = std::move(formatter); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->set_formatter(formatter_->clone()); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::enable_backtrace(size_t n_messages) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
backtrace_n_messages_ = n_messages; | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->enable_backtrace(n_messages); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::disable_backtrace() | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
backtrace_n_messages_ = 0; | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->disable_backtrace(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::set_level(level::level_enum log_level) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->set_level(log_level); | |||||
} | |||||
level_ = log_level; | |||||
} | |||||
SPDLOG_INLINE void registry::flush_on(level::level_enum log_level) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->flush_on(log_level); | |||||
} | |||||
flush_level_ = log_level; | |||||
} | |||||
SPDLOG_INLINE void registry::flush_every(std::chrono::seconds interval) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(flusher_mutex_); | |||||
std::function<void()> clbk = std::bind(®istry::flush_all, this); | |||||
periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval); | |||||
} | |||||
SPDLOG_INLINE void registry::set_error_handler(void (*handler)(const std::string &msg)) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->set_error_handler(handler); | |||||
} | |||||
err_handler_ = handler; | |||||
} | |||||
SPDLOG_INLINE void registry::apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
fun(l.second); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::flush_all() | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->flush(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::drop(const std::string &logger_name) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
loggers_.erase(logger_name); | |||||
if (default_logger_ && default_logger_->name() == logger_name) | |||||
{ | |||||
default_logger_.reset(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::drop_all() | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
loggers_.clear(); | |||||
default_logger_.reset(); | |||||
} | |||||
// clean all resources and threads started by the registry | |||||
SPDLOG_INLINE void registry::shutdown() | |||||
{ | |||||
{ | |||||
std::lock_guard<std::mutex> lock(flusher_mutex_); | |||||
periodic_flusher_.reset(); | |||||
} | |||||
drop_all(); | |||||
{ | |||||
std::lock_guard<std::recursive_mutex> lock(tp_mutex_); | |||||
tp_.reset(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE std::recursive_mutex ®istry::tp_mutex() | |||||
{ | |||||
return tp_mutex_; | |||||
} | |||||
SPDLOG_INLINE void registry::set_automatic_registration(bool automatic_registration) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
automatic_registration_ = automatic_registration; | |||||
} | |||||
SPDLOG_INLINE registry ®istry::instance() | |||||
{ | |||||
static registry s_instance; | |||||
return s_instance; | |||||
} | |||||
SPDLOG_INLINE void registry::throw_if_exists_(const std::string &logger_name) | |||||
{ | |||||
if (loggers_.find(logger_name) != loggers_.end()) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("logger with name '" + logger_name + "' already exists")); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::register_logger_(std::shared_ptr<logger> new_logger) | |||||
{ | |||||
auto logger_name = new_logger->name(); | |||||
throw_if_exists_(logger_name); | |||||
loggers_[logger_name] = std::move(new_logger); | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,109 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// Loggers registry of unique name->logger pointer | |||||
// An attempt to create a logger with an already existing name will result with spdlog_ex exception. | |||||
// If user requests a non existing logger, nullptr will be returned | |||||
// This class is thread safe | |||||
#include <spdlog/common.h> | |||||
#include <chrono> | |||||
#include <functional> | |||||
#include <memory> | |||||
#include <string> | |||||
#include <unordered_map> | |||||
#include <mutex> | |||||
namespace spdlog { | |||||
class logger; | |||||
namespace details { | |||||
class thread_pool; | |||||
class periodic_worker; | |||||
class registry | |||||
{ | |||||
public: | |||||
registry(const registry &) = delete; | |||||
registry &operator=(const registry &) = delete; | |||||
void register_logger(std::shared_ptr<logger> new_logger); | |||||
void initialize_logger(std::shared_ptr<logger> new_logger); | |||||
std::shared_ptr<logger> get(const std::string &logger_name); | |||||
std::shared_ptr<logger> default_logger(); | |||||
// Return raw ptr to the default logger. | |||||
// To be used directly by the spdlog default api (e.g. spdlog::info) | |||||
// This make the default API faster, but cannot be used concurrently with set_default_logger(). | |||||
// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. | |||||
logger *get_default_raw(); | |||||
// set default logger. | |||||
// default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. | |||||
void set_default_logger(std::shared_ptr<logger> new_default_logger); | |||||
void set_tp(std::shared_ptr<thread_pool> tp); | |||||
std::shared_ptr<thread_pool> get_tp(); | |||||
// Set global formatter. Each sink in each logger will get a clone of this object | |||||
void set_formatter(std::unique_ptr<formatter> formatter); | |||||
void enable_backtrace(size_t n_messages); | |||||
void disable_backtrace(); | |||||
void set_level(level::level_enum log_level); | |||||
void flush_on(level::level_enum log_level); | |||||
void flush_every(std::chrono::seconds interval); | |||||
void set_error_handler(void (*handler)(const std::string &msg)); | |||||
void apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun); | |||||
void flush_all(); | |||||
void drop(const std::string &logger_name); | |||||
void drop_all(); | |||||
// clean all resources and threads started by the registry | |||||
void shutdown(); | |||||
std::recursive_mutex &tp_mutex(); | |||||
void set_automatic_registration(bool automatic_registration); | |||||
static registry &instance(); | |||||
private: | |||||
registry(); | |||||
~registry() = default; | |||||
void throw_if_exists_(const std::string &logger_name); | |||||
void register_logger_(std::shared_ptr<logger> new_logger); | |||||
std::mutex logger_map_mutex_, flusher_mutex_; | |||||
std::recursive_mutex tp_mutex_; | |||||
std::unordered_map<std::string, std::shared_ptr<logger>> loggers_; | |||||
std::unique_ptr<formatter> formatter_; | |||||
level::level_enum level_ = level::info; | |||||
level::level_enum flush_level_ = level::off; | |||||
void (*err_handler_)(const std::string &msg); | |||||
std::shared_ptr<thread_pool> tp_; | |||||
std::unique_ptr<periodic_worker> periodic_flusher_; | |||||
std::shared_ptr<logger> default_logger_; | |||||
bool automatic_registration_ = true; | |||||
size_t backtrace_n_messages_ = 0; | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "registry-inl.h" | |||||
#endif |
@@ -0,0 +1,24 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "registry.h" | |||||
namespace spdlog { | |||||
// Default logger factory- creates synchronous loggers | |||||
class logger; | |||||
struct synchronous_factory | |||||
{ | |||||
template<typename Sink, typename... SinkArgs> | |||||
static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... args) | |||||
{ | |||||
auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); | |||||
auto new_logger = std::make_shared<spdlog::logger>(std::move(logger_name), std::move(sink)); | |||||
details::registry::instance().initialize_logger(new_logger); | |||||
return new_logger; | |||||
} | |||||
}; | |||||
} // namespace spdlog |
@@ -0,0 +1,123 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/details/thread_pool.h> | |||||
#endif | |||||
#include <spdlog/common.h> | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start) | |||||
: q_(q_max_items) | |||||
{ | |||||
if (threads_n == 0 || threads_n > 1000) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("spdlog::thread_pool(): invalid threads_n param (valid " | |||||
"range is 1-1000)")); | |||||
} | |||||
for (size_t i = 0; i < threads_n; i++) | |||||
{ | |||||
threads_.emplace_back([this, on_thread_start] { | |||||
on_thread_start(); | |||||
this->thread_pool::worker_loop_(); | |||||
}); | |||||
} | |||||
} | |||||
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n) | |||||
: thread_pool(q_max_items, threads_n, [] {}) | |||||
{} | |||||
// message all threads to terminate gracefully join them | |||||
SPDLOG_INLINE thread_pool::~thread_pool() | |||||
{ | |||||
SPDLOG_TRY | |||||
{ | |||||
for (size_t i = 0; i < threads_.size(); i++) | |||||
{ | |||||
post_async_msg_(async_msg(async_msg_type::terminate), async_overflow_policy::block); | |||||
} | |||||
for (auto &t : threads_) | |||||
{ | |||||
t.join(); | |||||
} | |||||
} | |||||
SPDLOG_CATCH_ALL() {} | |||||
} | |||||
void SPDLOG_INLINE thread_pool::post_log(async_logger_ptr &&worker_ptr, const details::log_msg &msg, async_overflow_policy overflow_policy) | |||||
{ | |||||
async_msg async_m(std::move(worker_ptr), async_msg_type::log, msg); | |||||
post_async_msg_(std::move(async_m), overflow_policy); | |||||
} | |||||
void SPDLOG_INLINE thread_pool::post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy) | |||||
{ | |||||
post_async_msg_(async_msg(std::move(worker_ptr), async_msg_type::flush), overflow_policy); | |||||
} | |||||
size_t SPDLOG_INLINE thread_pool::overrun_counter() | |||||
{ | |||||
return q_.overrun_counter(); | |||||
} | |||||
void SPDLOG_INLINE thread_pool::post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy) | |||||
{ | |||||
if (overflow_policy == async_overflow_policy::block) | |||||
{ | |||||
q_.enqueue(std::move(new_msg)); | |||||
} | |||||
else | |||||
{ | |||||
q_.enqueue_nowait(std::move(new_msg)); | |||||
} | |||||
} | |||||
void SPDLOG_INLINE thread_pool::worker_loop_() | |||||
{ | |||||
while (process_next_msg_()) {}; | |||||
} | |||||
// process next message in the queue | |||||
// return true if this thread should still be active (while no terminate msg | |||||
// was received) | |||||
bool SPDLOG_INLINE thread_pool::process_next_msg_() | |||||
{ | |||||
async_msg incoming_async_msg; | |||||
bool dequeued = q_.dequeue_for(incoming_async_msg, std::chrono::seconds(10)); | |||||
if (!dequeued) | |||||
{ | |||||
return true; | |||||
} | |||||
switch (incoming_async_msg.msg_type) | |||||
{ | |||||
case async_msg_type::log: { | |||||
incoming_async_msg.worker_ptr->backend_sink_it_(incoming_async_msg); | |||||
return true; | |||||
} | |||||
case async_msg_type::flush: { | |||||
incoming_async_msg.worker_ptr->backend_flush_(); | |||||
return true; | |||||
} | |||||
case async_msg_type::terminate: { | |||||
return false; | |||||
} | |||||
default: { | |||||
assert(false && "Unexpected async_msg_type"); | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
@@ -0,0 +1,120 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/details/log_msg_buffer.h> | |||||
#include <spdlog/details/mpmc_blocking_q.h> | |||||
#include <spdlog/details/os.h> | |||||
#include <chrono> | |||||
#include <memory> | |||||
#include <thread> | |||||
#include <vector> | |||||
#include <functional> | |||||
namespace spdlog { | |||||
class async_logger; | |||||
namespace details { | |||||
using async_logger_ptr = std::shared_ptr<spdlog::async_logger>; | |||||
enum class async_msg_type | |||||
{ | |||||
log, | |||||
flush, | |||||
terminate | |||||
}; | |||||
#include <spdlog/details/log_msg_buffer.h> | |||||
// Async msg to move to/from the queue | |||||
// Movable only. should never be copied | |||||
struct async_msg : log_msg_buffer | |||||
{ | |||||
async_msg_type msg_type{async_msg_type::log}; | |||||
async_logger_ptr worker_ptr; | |||||
async_msg() = default; | |||||
~async_msg() = default; | |||||
// should only be moved in or out of the queue.. | |||||
async_msg(const async_msg &) = delete; | |||||
// support for vs2013 move | |||||
#if defined(_MSC_VER) && _MSC_VER <= 1800 | |||||
async_msg(async_msg &&other) | |||||
: log_msg_buffer(std::move(other)) | |||||
, msg_type(other.msg_type) | |||||
, worker_ptr(std::move(other.worker_ptr)) | |||||
{} | |||||
async_msg &operator=(async_msg &&other) | |||||
{ | |||||
*static_cast<log_msg_buffer *>(this) = std::move(other); | |||||
msg_type = other.msg_type; | |||||
worker_ptr = std::move(other.worker_ptr); | |||||
return *this; | |||||
} | |||||
#else // (_MSC_VER) && _MSC_VER <= 1800 | |||||
async_msg(async_msg &&) = default; | |||||
async_msg &operator=(async_msg &&) = default; | |||||
#endif | |||||
// construct from log_msg with given type | |||||
async_msg(async_logger_ptr &&worker, async_msg_type the_type, const details::log_msg &m) | |||||
: log_msg_buffer{m} | |||||
, msg_type{the_type} | |||||
, worker_ptr{std::move(worker)} | |||||
{} | |||||
async_msg(async_logger_ptr &&worker, async_msg_type the_type) | |||||
: log_msg_buffer{} | |||||
, msg_type{the_type} | |||||
, worker_ptr{std::move(worker)} | |||||
{} | |||||
explicit async_msg(async_msg_type the_type) | |||||
: async_msg{nullptr, the_type} | |||||
{} | |||||
}; | |||||
class thread_pool | |||||
{ | |||||
public: | |||||
using item_type = async_msg; | |||||
using q_type = details::mpmc_blocking_queue<item_type>; | |||||
thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start); | |||||
thread_pool(size_t q_max_items, size_t threads_n); | |||||
// message all threads to terminate gracefully join them | |||||
~thread_pool(); | |||||
thread_pool(const thread_pool &) = delete; | |||||
thread_pool &operator=(thread_pool &&) = delete; | |||||
void post_log(async_logger_ptr &&worker_ptr, const details::log_msg &msg, async_overflow_policy overflow_policy); | |||||
void post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy); | |||||
size_t overrun_counter(); | |||||
private: | |||||
q_type q_; | |||||
std::vector<std::thread> threads_; | |||||
void post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy); | |||||
void worker_loop_(); | |||||
// process next message in the queue | |||||
// return true if this thread should still be active (while no terminate msg | |||||
// was received) | |||||
bool process_next_msg_(); | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "thread_pool-inl.h" | |||||
#endif |
@@ -0,0 +1,175 @@ | |||||
// | |||||
// Copyright(c) 2015 Gabi Melman. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
// | |||||
#pragma once | |||||
// | |||||
// Support for logging binary data as hex | |||||
// format flags: | |||||
// {:X} - print in uppercase. | |||||
// {:s} - don't separate each byte with space. | |||||
// {:p} - don't print the position on each line start. | |||||
// {:n} - don't split the output to lines. | |||||
// | |||||
// Examples: | |||||
// | |||||
// std::vector<char> v(200, 0x0b); | |||||
// logger->info("Some buffer {}", spdlog::to_hex(v)); | |||||
// char buf[128]; | |||||
// logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf))); | |||||
namespace spdlog { | |||||
namespace details { | |||||
template<typename It> | |||||
class bytes_range | |||||
{ | |||||
public: | |||||
bytes_range(It range_begin, It range_end) | |||||
: begin_(range_begin) | |||||
, end_(range_end) | |||||
{} | |||||
It begin() const | |||||
{ | |||||
return begin_; | |||||
} | |||||
It end() const | |||||
{ | |||||
return end_; | |||||
} | |||||
private: | |||||
It begin_, end_; | |||||
}; | |||||
} // namespace details | |||||
// create a bytes_range that wraps the given container | |||||
template<typename Container> | |||||
inline details::bytes_range<typename Container::const_iterator> to_hex(const Container &container) | |||||
{ | |||||
static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1"); | |||||
using Iter = typename Container::const_iterator; | |||||
return details::bytes_range<Iter>(std::begin(container), std::end(container)); | |||||
} | |||||
// create bytes_range from ranges | |||||
template<typename It> | |||||
inline details::bytes_range<It> to_hex(const It range_begin, const It range_end) | |||||
{ | |||||
return details::bytes_range<It>(range_begin, range_end); | |||||
} | |||||
} // namespace spdlog | |||||
namespace fmt { | |||||
template<typename T> | |||||
struct formatter<spdlog::details::bytes_range<T>> | |||||
{ | |||||
const std::size_t line_size = 100; | |||||
const char delimiter = ' '; | |||||
bool put_newlines = true; | |||||
bool put_delimiters = true; | |||||
bool use_uppercase = false; | |||||
bool put_positions = true; // position on start of each line | |||||
// parse the format string flags | |||||
template<typename ParseContext> | |||||
auto parse(ParseContext &ctx) -> decltype(ctx.begin()) | |||||
{ | |||||
auto it = ctx.begin(); | |||||
while (*it && *it != '}') | |||||
{ | |||||
switch (*it) | |||||
{ | |||||
case 'X': | |||||
use_uppercase = true; | |||||
break; | |||||
case 's': | |||||
put_delimiters = false; | |||||
break; | |||||
case 'p': | |||||
put_positions = false; | |||||
break; | |||||
case 'n': | |||||
put_newlines = false; | |||||
break; | |||||
} | |||||
++it; | |||||
} | |||||
return it; | |||||
} | |||||
// format the given bytes range as hex | |||||
template<typename FormatContext, typename Container> | |||||
auto format(const spdlog::details::bytes_range<Container> &the_range, FormatContext &ctx) -> decltype(ctx.out()) | |||||
{ | |||||
SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF"; | |||||
SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef"; | |||||
const char *hex_chars = use_uppercase ? hex_upper : hex_lower; | |||||
std::size_t pos = 0; | |||||
std::size_t column = line_size; | |||||
#if FMT_VERSION < 60000 | |||||
auto inserter = ctx.begin(); | |||||
#else | |||||
auto inserter = ctx.out(); | |||||
#endif | |||||
for (auto &item : the_range) | |||||
{ | |||||
auto ch = static_cast<unsigned char>(item); | |||||
pos++; | |||||
if (put_newlines && column >= line_size) | |||||
{ | |||||
column = put_newline(inserter, pos); | |||||
// put first byte without delimiter in front of it | |||||
*inserter++ = hex_chars[(ch >> 4) & 0x0f]; | |||||
*inserter++ = hex_chars[ch & 0x0f]; | |||||
column += 2; | |||||
continue; | |||||
} | |||||
if (put_delimiters) | |||||
{ | |||||
*inserter++ = delimiter; | |||||
++column; | |||||
} | |||||
*inserter++ = hex_chars[(ch >> 4) & 0x0f]; | |||||
*inserter++ = hex_chars[ch & 0x0f]; | |||||
column += 2; | |||||
} | |||||
return inserter; | |||||
} | |||||
// put newline(and position header) | |||||
// return the next column | |||||
template<typename It> | |||||
std::size_t put_newline(It inserter, std::size_t pos) | |||||
{ | |||||
#ifdef _WIN32 | |||||
*inserter++ = '\r'; | |||||
#endif | |||||
*inserter++ = '\n'; | |||||
if (put_positions) | |||||
{ | |||||
fmt::format_to(inserter, "{:<04X}: ", pos - 1); | |||||
return 7; | |||||
} | |||||
else | |||||
{ | |||||
return 1; | |||||
} | |||||
} | |||||
}; | |||||
} // namespace fmt |
@@ -0,0 +1,27 @@ | |||||
Copyright (c) 2012 - present, Victor Zverovich | |||||
Permission is hereby granted, free of charge, to any person obtaining | |||||
a copy of this software and associated documentation files (the | |||||
"Software"), to deal in the Software without restriction, including | |||||
without limitation the rights to use, copy, modify, merge, publish, | |||||
distribute, sublicense, and/or sell copies of the Software, and to | |||||
permit persons to whom the Software is furnished to do so, subject to | |||||
the following conditions: | |||||
The above copyright notice and this permission notice shall be | |||||
included in all copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||||
--- Optional exception to the license --- | |||||
As an exception, if, as a result of your compiling your source code, portions | |||||
of this Software are embedded into a machine-executable object form of such | |||||
source code, you may redistribute such embedded portions in such object form | |||||
without including the above copyright and permission notices. |
@@ -0,0 +1,829 @@ | |||||
// Formatting library for C++ - chrono support | |||||
// | |||||
// Copyright (c) 2012 - present, Victor Zverovich | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
#ifndef FMT_CHRONO_H_ | |||||
#define FMT_CHRONO_H_ | |||||
#include "format.h" | |||||
#include "locale.h" | |||||
#include <chrono> | |||||
#include <ctime> | |||||
#include <locale> | |||||
#include <sstream> | |||||
// enable safe chrono durations, unless explicitly disabled | |||||
#ifndef FMT_SAFE_DURATION_CAST | |||||
# define FMT_SAFE_DURATION_CAST 1 | |||||
#endif | |||||
#if FMT_SAFE_DURATION_CAST | |||||
# include "safe-duration-cast.h" | |||||
#endif | |||||
FMT_BEGIN_NAMESPACE | |||||
// Prevents expansion of a preceding token as a function-style macro. | |||||
// Usage: f FMT_NOMACRO() | |||||
#define FMT_NOMACRO | |||||
namespace internal { | |||||
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } | |||||
inline null<> localtime_s(...) { return null<>(); } | |||||
inline null<> gmtime_r(...) { return null<>(); } | |||||
inline null<> gmtime_s(...) { return null<>(); } | |||||
} // namespace internal | |||||
// Thread-safe replacement for std::localtime | |||||
inline std::tm localtime(std::time_t time) { | |||||
struct dispatcher { | |||||
std::time_t time_; | |||||
std::tm tm_; | |||||
dispatcher(std::time_t t) : time_(t) {} | |||||
bool run() { | |||||
using namespace fmt::internal; | |||||
return handle(localtime_r(&time_, &tm_)); | |||||
} | |||||
bool handle(std::tm* tm) { return tm != nullptr; } | |||||
bool handle(internal::null<>) { | |||||
using namespace fmt::internal; | |||||
return fallback(localtime_s(&tm_, &time_)); | |||||
} | |||||
bool fallback(int res) { return res == 0; } | |||||
#if !FMT_MSC_VER | |||||
bool fallback(internal::null<>) { | |||||
using namespace fmt::internal; | |||||
std::tm* tm = std::localtime(&time_); | |||||
if (tm) tm_ = *tm; | |||||
return tm != nullptr; | |||||
} | |||||
#endif | |||||
}; | |||||
dispatcher lt(time); | |||||
// Too big time values may be unsupported. | |||||
if (!lt.run()) FMT_THROW(format_error("time_t value out of range")); | |||||
return lt.tm_; | |||||
} | |||||
// Thread-safe replacement for std::gmtime | |||||
inline std::tm gmtime(std::time_t time) { | |||||
struct dispatcher { | |||||
std::time_t time_; | |||||
std::tm tm_; | |||||
dispatcher(std::time_t t) : time_(t) {} | |||||
bool run() { | |||||
using namespace fmt::internal; | |||||
return handle(gmtime_r(&time_, &tm_)); | |||||
} | |||||
bool handle(std::tm* tm) { return tm != nullptr; } | |||||
bool handle(internal::null<>) { | |||||
using namespace fmt::internal; | |||||
return fallback(gmtime_s(&tm_, &time_)); | |||||
} | |||||
bool fallback(int res) { return res == 0; } | |||||
#if !FMT_MSC_VER | |||||
bool fallback(internal::null<>) { | |||||
std::tm* tm = std::gmtime(&time_); | |||||
if (tm) tm_ = *tm; | |||||
return tm != nullptr; | |||||
} | |||||
#endif | |||||
}; | |||||
dispatcher gt(time); | |||||
// Too big time values may be unsupported. | |||||
if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); | |||||
return gt.tm_; | |||||
} | |||||
namespace internal { | |||||
inline std::size_t strftime(char* str, std::size_t count, const char* format, | |||||
const std::tm* time) { | |||||
return std::strftime(str, count, format, time); | |||||
} | |||||
inline std::size_t strftime(wchar_t* str, std::size_t count, | |||||
const wchar_t* format, const std::tm* time) { | |||||
return std::wcsftime(str, count, format, time); | |||||
} | |||||
} // namespace internal | |||||
template <typename Char> struct formatter<std::tm, Char> { | |||||
template <typename ParseContext> | |||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||||
auto it = ctx.begin(); | |||||
if (it != ctx.end() && *it == ':') ++it; | |||||
auto end = it; | |||||
while (end != ctx.end() && *end != '}') ++end; | |||||
tm_format.reserve(internal::to_unsigned(end - it + 1)); | |||||
tm_format.append(it, end); | |||||
tm_format.push_back('\0'); | |||||
return end; | |||||
} | |||||
template <typename FormatContext> | |||||
auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) { | |||||
basic_memory_buffer<Char> buf; | |||||
std::size_t start = buf.size(); | |||||
for (;;) { | |||||
std::size_t size = buf.capacity() - start; | |||||
std::size_t count = | |||||
internal::strftime(&buf[start], size, &tm_format[0], &tm); | |||||
if (count != 0) { | |||||
buf.resize(start + count); | |||||
break; | |||||
} | |||||
if (size >= tm_format.size() * 256) { | |||||
// If the buffer is 256 times larger than the format string, assume | |||||
// that `strftime` gives an empty result. There doesn't seem to be a | |||||
// better way to distinguish the two cases: | |||||
// https://github.com/fmtlib/fmt/issues/367 | |||||
break; | |||||
} | |||||
const std::size_t MIN_GROWTH = 10; | |||||
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); | |||||
} | |||||
return std::copy(buf.begin(), buf.end(), ctx.out()); | |||||
} | |||||
basic_memory_buffer<Char> tm_format; | |||||
}; | |||||
namespace internal { | |||||
template <typename Period> FMT_CONSTEXPR const char* get_units() { | |||||
return nullptr; | |||||
} | |||||
template <> FMT_CONSTEXPR const char* get_units<std::atto>() { return "as"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::femto>() { return "fs"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::pico>() { return "ps"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::nano>() { return "ns"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::micro>() { return "µs"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::milli>() { return "ms"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::centi>() { return "cs"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::deci>() { return "ds"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<1>>() { return "s"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::deca>() { return "das"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::hecto>() { return "hs"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::kilo>() { return "ks"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::mega>() { return "Ms"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::giga>() { return "Gs"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::tera>() { return "Ts"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::peta>() { return "Ps"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::exa>() { return "Es"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<60>>() { | |||||
return "m"; | |||||
} | |||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<3600>>() { | |||||
return "h"; | |||||
} | |||||
enum class numeric_system { | |||||
standard, | |||||
// Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. | |||||
alternative | |||||
}; | |||||
// Parses a put_time-like format string and invokes handler actions. | |||||
template <typename Char, typename Handler> | |||||
FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, | |||||
const Char* end, | |||||
Handler&& handler) { | |||||
auto ptr = begin; | |||||
while (ptr != end) { | |||||
auto c = *ptr; | |||||
if (c == '}') break; | |||||
if (c != '%') { | |||||
++ptr; | |||||
continue; | |||||
} | |||||
if (begin != ptr) handler.on_text(begin, ptr); | |||||
++ptr; // consume '%' | |||||
if (ptr == end) FMT_THROW(format_error("invalid format")); | |||||
c = *ptr++; | |||||
switch (c) { | |||||
case '%': | |||||
handler.on_text(ptr - 1, ptr); | |||||
break; | |||||
case 'n': { | |||||
const char newline[] = "\n"; | |||||
handler.on_text(newline, newline + 1); | |||||
break; | |||||
} | |||||
case 't': { | |||||
const char tab[] = "\t"; | |||||
handler.on_text(tab, tab + 1); | |||||
break; | |||||
} | |||||
// Day of the week: | |||||
case 'a': | |||||
handler.on_abbr_weekday(); | |||||
break; | |||||
case 'A': | |||||
handler.on_full_weekday(); | |||||
break; | |||||
case 'w': | |||||
handler.on_dec0_weekday(numeric_system::standard); | |||||
break; | |||||
case 'u': | |||||
handler.on_dec1_weekday(numeric_system::standard); | |||||
break; | |||||
// Month: | |||||
case 'b': | |||||
handler.on_abbr_month(); | |||||
break; | |||||
case 'B': | |||||
handler.on_full_month(); | |||||
break; | |||||
// Hour, minute, second: | |||||
case 'H': | |||||
handler.on_24_hour(numeric_system::standard); | |||||
break; | |||||
case 'I': | |||||
handler.on_12_hour(numeric_system::standard); | |||||
break; | |||||
case 'M': | |||||
handler.on_minute(numeric_system::standard); | |||||
break; | |||||
case 'S': | |||||
handler.on_second(numeric_system::standard); | |||||
break; | |||||
// Other: | |||||
case 'c': | |||||
handler.on_datetime(numeric_system::standard); | |||||
break; | |||||
case 'x': | |||||
handler.on_loc_date(numeric_system::standard); | |||||
break; | |||||
case 'X': | |||||
handler.on_loc_time(numeric_system::standard); | |||||
break; | |||||
case 'D': | |||||
handler.on_us_date(); | |||||
break; | |||||
case 'F': | |||||
handler.on_iso_date(); | |||||
break; | |||||
case 'r': | |||||
handler.on_12_hour_time(); | |||||
break; | |||||
case 'R': | |||||
handler.on_24_hour_time(); | |||||
break; | |||||
case 'T': | |||||
handler.on_iso_time(); | |||||
break; | |||||
case 'p': | |||||
handler.on_am_pm(); | |||||
break; | |||||
case 'Q': | |||||
handler.on_duration_value(); | |||||
break; | |||||
case 'q': | |||||
handler.on_duration_unit(); | |||||
break; | |||||
case 'z': | |||||
handler.on_utc_offset(); | |||||
break; | |||||
case 'Z': | |||||
handler.on_tz_name(); | |||||
break; | |||||
// Alternative representation: | |||||
case 'E': { | |||||
if (ptr == end) FMT_THROW(format_error("invalid format")); | |||||
c = *ptr++; | |||||
switch (c) { | |||||
case 'c': | |||||
handler.on_datetime(numeric_system::alternative); | |||||
break; | |||||
case 'x': | |||||
handler.on_loc_date(numeric_system::alternative); | |||||
break; | |||||
case 'X': | |||||
handler.on_loc_time(numeric_system::alternative); | |||||
break; | |||||
default: | |||||
FMT_THROW(format_error("invalid format")); | |||||
} | |||||
break; | |||||
} | |||||
case 'O': | |||||
if (ptr == end) FMT_THROW(format_error("invalid format")); | |||||
c = *ptr++; | |||||
switch (c) { | |||||
case 'w': | |||||
handler.on_dec0_weekday(numeric_system::alternative); | |||||
break; | |||||
case 'u': | |||||
handler.on_dec1_weekday(numeric_system::alternative); | |||||
break; | |||||
case 'H': | |||||
handler.on_24_hour(numeric_system::alternative); | |||||
break; | |||||
case 'I': | |||||
handler.on_12_hour(numeric_system::alternative); | |||||
break; | |||||
case 'M': | |||||
handler.on_minute(numeric_system::alternative); | |||||
break; | |||||
case 'S': | |||||
handler.on_second(numeric_system::alternative); | |||||
break; | |||||
default: | |||||
FMT_THROW(format_error("invalid format")); | |||||
} | |||||
break; | |||||
default: | |||||
FMT_THROW(format_error("invalid format")); | |||||
} | |||||
begin = ptr; | |||||
} | |||||
if (begin != ptr) handler.on_text(begin, ptr); | |||||
return ptr; | |||||
} | |||||
struct chrono_format_checker { | |||||
FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); } | |||||
template <typename Char> void on_text(const Char*, const Char*) {} | |||||
FMT_NORETURN void on_abbr_weekday() { report_no_date(); } | |||||
FMT_NORETURN void on_full_weekday() { report_no_date(); } | |||||
FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); } | |||||
FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); } | |||||
FMT_NORETURN void on_abbr_month() { report_no_date(); } | |||||
FMT_NORETURN void on_full_month() { report_no_date(); } | |||||
void on_24_hour(numeric_system) {} | |||||
void on_12_hour(numeric_system) {} | |||||
void on_minute(numeric_system) {} | |||||
void on_second(numeric_system) {} | |||||
FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); } | |||||
FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); } | |||||
FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); } | |||||
FMT_NORETURN void on_us_date() { report_no_date(); } | |||||
FMT_NORETURN void on_iso_date() { report_no_date(); } | |||||
void on_12_hour_time() {} | |||||
void on_24_hour_time() {} | |||||
void on_iso_time() {} | |||||
void on_am_pm() {} | |||||
void on_duration_value() {} | |||||
void on_duration_unit() {} | |||||
FMT_NORETURN void on_utc_offset() { report_no_date(); } | |||||
FMT_NORETURN void on_tz_name() { report_no_date(); } | |||||
}; | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
inline bool isnan(T) { | |||||
return false; | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||||
inline bool isnan(T value) { | |||||
return std::isnan(value); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
inline bool isfinite(T) { | |||||
return true; | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||||
inline bool isfinite(T value) { | |||||
return std::isfinite(value); | |||||
} | |||||
// Convers value to int and checks that it's in the range [0, upper). | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
inline int to_nonnegative_int(T value, int upper) { | |||||
FMT_ASSERT(value >= 0 && value <= upper, "invalid value"); | |||||
(void)upper; | |||||
return static_cast<int>(value); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
inline int to_nonnegative_int(T value, int upper) { | |||||
FMT_ASSERT( | |||||
std::isnan(value) || (value >= 0 && value <= static_cast<T>(upper)), | |||||
"invalid value"); | |||||
(void)upper; | |||||
return static_cast<int>(value); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
inline T mod(T x, int y) { | |||||
return x % y; | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||||
inline T mod(T x, int y) { | |||||
return std::fmod(x, static_cast<T>(y)); | |||||
} | |||||
// If T is an integral type, maps T to its unsigned counterpart, otherwise | |||||
// leaves it unchanged (unlike std::make_unsigned). | |||||
template <typename T, bool INTEGRAL = std::is_integral<T>::value> | |||||
struct make_unsigned_or_unchanged { | |||||
using type = T; | |||||
}; | |||||
template <typename T> struct make_unsigned_or_unchanged<T, true> { | |||||
using type = typename std::make_unsigned<T>::type; | |||||
}; | |||||
#if FMT_SAFE_DURATION_CAST | |||||
// throwing version of safe_duration_cast | |||||
template <typename To, typename FromRep, typename FromPeriod> | |||||
To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) { | |||||
int ec; | |||||
To to = safe_duration_cast::safe_duration_cast<To>(from, ec); | |||||
if (ec) FMT_THROW(format_error("cannot format duration")); | |||||
return to; | |||||
} | |||||
#endif | |||||
template <typename Rep, typename Period, | |||||
FMT_ENABLE_IF(std::is_integral<Rep>::value)> | |||||
inline std::chrono::duration<Rep, std::milli> get_milliseconds( | |||||
std::chrono::duration<Rep, Period> d) { | |||||
// this may overflow and/or the result may not fit in the | |||||
// target type. | |||||
#if FMT_SAFE_DURATION_CAST | |||||
using CommonSecondsType = | |||||
typename std::common_type<decltype(d), std::chrono::seconds>::type; | |||||
const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d); | |||||
const auto d_as_whole_seconds = | |||||
fmt_safe_duration_cast<std::chrono::seconds>(d_as_common); | |||||
// this conversion should be nonproblematic | |||||
const auto diff = d_as_common - d_as_whole_seconds; | |||||
const auto ms = | |||||
fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff); | |||||
return ms; | |||||
#else | |||||
auto s = std::chrono::duration_cast<std::chrono::seconds>(d); | |||||
return std::chrono::duration_cast<std::chrono::milliseconds>(d - s); | |||||
#endif | |||||
} | |||||
template <typename Rep, typename Period, | |||||
FMT_ENABLE_IF(std::is_floating_point<Rep>::value)> | |||||
inline std::chrono::duration<Rep, std::milli> get_milliseconds( | |||||
std::chrono::duration<Rep, Period> d) { | |||||
using common_type = typename std::common_type<Rep, std::intmax_t>::type; | |||||
auto ms = mod(d.count() * static_cast<common_type>(Period::num) / | |||||
static_cast<common_type>(Period::den) * 1000, | |||||
1000); | |||||
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms)); | |||||
} | |||||
template <typename Rep, typename OutputIt> | |||||
OutputIt format_chrono_duration_value(OutputIt out, Rep val, int precision) { | |||||
if (precision >= 0) return format_to(out, "{:.{}f}", val, precision); | |||||
return format_to(out, std::is_floating_point<Rep>::value ? "{:g}" : "{}", | |||||
val); | |||||
} | |||||
template <typename Period, typename OutputIt> | |||||
static OutputIt format_chrono_duration_unit(OutputIt out) { | |||||
if (const char* unit = get_units<Period>()) return format_to(out, "{}", unit); | |||||
if (Period::den == 1) return format_to(out, "[{}]s", Period::num); | |||||
return format_to(out, "[{}/{}]s", Period::num, Period::den); | |||||
} | |||||
template <typename FormatContext, typename OutputIt, typename Rep, | |||||
typename Period> | |||||
struct chrono_formatter { | |||||
FormatContext& context; | |||||
OutputIt out; | |||||
int precision; | |||||
// rep is unsigned to avoid overflow. | |||||
using rep = | |||||
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int), | |||||
unsigned, typename make_unsigned_or_unchanged<Rep>::type>; | |||||
rep val; | |||||
using seconds = std::chrono::duration<rep>; | |||||
seconds s; | |||||
using milliseconds = std::chrono::duration<rep, std::milli>; | |||||
bool negative; | |||||
using char_type = typename FormatContext::char_type; | |||||
explicit chrono_formatter(FormatContext& ctx, OutputIt o, | |||||
std::chrono::duration<Rep, Period> d) | |||||
: context(ctx), out(o), val(d.count()), negative(false) { | |||||
if (d.count() < 0) { | |||||
val = 0 - val; | |||||
negative = true; | |||||
} | |||||
// this may overflow and/or the result may not fit in the | |||||
// target type. | |||||
#if FMT_SAFE_DURATION_CAST | |||||
// might need checked conversion (rep!=Rep) | |||||
auto tmpval = std::chrono::duration<rep, Period>(val); | |||||
s = fmt_safe_duration_cast<seconds>(tmpval); | |||||
#else | |||||
s = std::chrono::duration_cast<seconds>( | |||||
std::chrono::duration<rep, Period>(val)); | |||||
#endif | |||||
} | |||||
// returns true if nan or inf, writes to out. | |||||
bool handle_nan_inf() { | |||||
if (isfinite(val)) { | |||||
return false; | |||||
} | |||||
if (isnan(val)) { | |||||
write_nan(); | |||||
return true; | |||||
} | |||||
// must be +-inf | |||||
if (val > 0) { | |||||
write_pinf(); | |||||
} else { | |||||
write_ninf(); | |||||
} | |||||
return true; | |||||
} | |||||
Rep hour() const { return static_cast<Rep>(mod((s.count() / 3600), 24)); } | |||||
Rep hour12() const { | |||||
Rep hour = static_cast<Rep>(mod((s.count() / 3600), 12)); | |||||
return hour <= 0 ? 12 : hour; | |||||
} | |||||
Rep minute() const { return static_cast<Rep>(mod((s.count() / 60), 60)); } | |||||
Rep second() const { return static_cast<Rep>(mod(s.count(), 60)); } | |||||
std::tm time() const { | |||||
auto time = std::tm(); | |||||
time.tm_hour = to_nonnegative_int(hour(), 24); | |||||
time.tm_min = to_nonnegative_int(minute(), 60); | |||||
time.tm_sec = to_nonnegative_int(second(), 60); | |||||
return time; | |||||
} | |||||
void write_sign() { | |||||
if (negative) { | |||||
*out++ = '-'; | |||||
negative = false; | |||||
} | |||||
} | |||||
void write(Rep value, int width) { | |||||
write_sign(); | |||||
if (isnan(value)) return write_nan(); | |||||
uint32_or_64_t<int> n = to_unsigned( | |||||
to_nonnegative_int(value, (std::numeric_limits<int>::max)())); | |||||
int num_digits = internal::count_digits(n); | |||||
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); | |||||
out = format_decimal<char_type>(out, n, num_digits); | |||||
} | |||||
void write_nan() { std::copy_n("nan", 3, out); } | |||||
void write_pinf() { std::copy_n("inf", 3, out); } | |||||
void write_ninf() { std::copy_n("-inf", 4, out); } | |||||
void format_localized(const tm& time, const char* format) { | |||||
if (isnan(val)) return write_nan(); | |||||
auto locale = context.locale().template get<std::locale>(); | |||||
auto& facet = std::use_facet<std::time_put<char_type>>(locale); | |||||
std::basic_ostringstream<char_type> os; | |||||
os.imbue(locale); | |||||
facet.put(os, os, ' ', &time, format, format + std::strlen(format)); | |||||
auto str = os.str(); | |||||
std::copy(str.begin(), str.end(), out); | |||||
} | |||||
void on_text(const char_type* begin, const char_type* end) { | |||||
std::copy(begin, end, out); | |||||
} | |||||
// These are not implemented because durations don't have date information. | |||||
void on_abbr_weekday() {} | |||||
void on_full_weekday() {} | |||||
void on_dec0_weekday(numeric_system) {} | |||||
void on_dec1_weekday(numeric_system) {} | |||||
void on_abbr_month() {} | |||||
void on_full_month() {} | |||||
void on_datetime(numeric_system) {} | |||||
void on_loc_date(numeric_system) {} | |||||
void on_loc_time(numeric_system) {} | |||||
void on_us_date() {} | |||||
void on_iso_date() {} | |||||
void on_utc_offset() {} | |||||
void on_tz_name() {} | |||||
void on_24_hour(numeric_system ns) { | |||||
if (handle_nan_inf()) return; | |||||
if (ns == numeric_system::standard) return write(hour(), 2); | |||||
auto time = tm(); | |||||
time.tm_hour = to_nonnegative_int(hour(), 24); | |||||
format_localized(time, "%OH"); | |||||
} | |||||
void on_12_hour(numeric_system ns) { | |||||
if (handle_nan_inf()) return; | |||||
if (ns == numeric_system::standard) return write(hour12(), 2); | |||||
auto time = tm(); | |||||
time.tm_hour = to_nonnegative_int(hour12(), 12); | |||||
format_localized(time, "%OI"); | |||||
} | |||||
void on_minute(numeric_system ns) { | |||||
if (handle_nan_inf()) return; | |||||
if (ns == numeric_system::standard) return write(minute(), 2); | |||||
auto time = tm(); | |||||
time.tm_min = to_nonnegative_int(minute(), 60); | |||||
format_localized(time, "%OM"); | |||||
} | |||||
void on_second(numeric_system ns) { | |||||
if (handle_nan_inf()) return; | |||||
if (ns == numeric_system::standard) { | |||||
write(second(), 2); | |||||
#if FMT_SAFE_DURATION_CAST | |||||
// convert rep->Rep | |||||
using duration_rep = std::chrono::duration<rep, Period>; | |||||
using duration_Rep = std::chrono::duration<Rep, Period>; | |||||
auto tmpval = fmt_safe_duration_cast<duration_Rep>(duration_rep{val}); | |||||
#else | |||||
auto tmpval = std::chrono::duration<Rep, Period>(val); | |||||
#endif | |||||
auto ms = get_milliseconds(tmpval); | |||||
if (ms != std::chrono::milliseconds(0)) { | |||||
*out++ = '.'; | |||||
write(ms.count(), 3); | |||||
} | |||||
return; | |||||
} | |||||
auto time = tm(); | |||||
time.tm_sec = to_nonnegative_int(second(), 60); | |||||
format_localized(time, "%OS"); | |||||
} | |||||
void on_12_hour_time() { | |||||
if (handle_nan_inf()) return; | |||||
format_localized(time(), "%r"); | |||||
} | |||||
void on_24_hour_time() { | |||||
if (handle_nan_inf()) { | |||||
*out++ = ':'; | |||||
handle_nan_inf(); | |||||
return; | |||||
} | |||||
write(hour(), 2); | |||||
*out++ = ':'; | |||||
write(minute(), 2); | |||||
} | |||||
void on_iso_time() { | |||||
on_24_hour_time(); | |||||
*out++ = ':'; | |||||
if (handle_nan_inf()) return; | |||||
write(second(), 2); | |||||
} | |||||
void on_am_pm() { | |||||
if (handle_nan_inf()) return; | |||||
format_localized(time(), "%p"); | |||||
} | |||||
void on_duration_value() { | |||||
if (handle_nan_inf()) return; | |||||
write_sign(); | |||||
out = format_chrono_duration_value(out, val, precision); | |||||
} | |||||
void on_duration_unit() { out = format_chrono_duration_unit<Period>(out); } | |||||
}; | |||||
} // namespace internal | |||||
template <typename Rep, typename Period, typename Char> | |||||
struct formatter<std::chrono::duration<Rep, Period>, Char> { | |||||
private: | |||||
basic_format_specs<Char> specs; | |||||
int precision; | |||||
using arg_ref_type = internal::arg_ref<Char>; | |||||
arg_ref_type width_ref; | |||||
arg_ref_type precision_ref; | |||||
mutable basic_string_view<Char> format_str; | |||||
using duration = std::chrono::duration<Rep, Period>; | |||||
struct spec_handler { | |||||
formatter& f; | |||||
basic_parse_context<Char>& context; | |||||
basic_string_view<Char> format_str; | |||||
template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { | |||||
context.check_arg_id(arg_id); | |||||
return arg_ref_type(arg_id); | |||||
} | |||||
FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) { | |||||
context.check_arg_id(arg_id); | |||||
const auto str_val = internal::string_view_metadata(format_str, arg_id); | |||||
return arg_ref_type(str_val); | |||||
} | |||||
FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) { | |||||
return arg_ref_type(context.next_arg_id()); | |||||
} | |||||
void on_error(const char* msg) { FMT_THROW(format_error(msg)); } | |||||
void on_fill(Char fill) { f.specs.fill[0] = fill; } | |||||
void on_align(align_t align) { f.specs.align = align; } | |||||
void on_width(unsigned width) { f.specs.width = width; } | |||||
void on_precision(unsigned precision) { f.precision = precision; } | |||||
void end_precision() {} | |||||
template <typename Id> void on_dynamic_width(Id arg_id) { | |||||
f.width_ref = make_arg_ref(arg_id); | |||||
} | |||||
template <typename Id> void on_dynamic_precision(Id arg_id) { | |||||
f.precision_ref = make_arg_ref(arg_id); | |||||
} | |||||
}; | |||||
using iterator = typename basic_parse_context<Char>::iterator; | |||||
struct parse_range { | |||||
iterator begin; | |||||
iterator end; | |||||
}; | |||||
FMT_CONSTEXPR parse_range do_parse(basic_parse_context<Char>& ctx) { | |||||
auto begin = ctx.begin(), end = ctx.end(); | |||||
if (begin == end || *begin == '}') return {begin, begin}; | |||||
spec_handler handler{*this, ctx, format_str}; | |||||
begin = internal::parse_align(begin, end, handler); | |||||
if (begin == end) return {begin, begin}; | |||||
begin = internal::parse_width(begin, end, handler); | |||||
if (begin == end) return {begin, begin}; | |||||
if (*begin == '.') { | |||||
if (std::is_floating_point<Rep>::value) | |||||
begin = internal::parse_precision(begin, end, handler); | |||||
else | |||||
handler.on_error("precision not allowed for this argument type"); | |||||
} | |||||
end = parse_chrono_format(begin, end, internal::chrono_format_checker()); | |||||
return {begin, end}; | |||||
} | |||||
public: | |||||
formatter() : precision(-1) {} | |||||
FMT_CONSTEXPR auto parse(basic_parse_context<Char>& ctx) | |||||
-> decltype(ctx.begin()) { | |||||
auto range = do_parse(ctx); | |||||
format_str = basic_string_view<Char>( | |||||
&*range.begin, internal::to_unsigned(range.end - range.begin)); | |||||
return range.end; | |||||
} | |||||
template <typename FormatContext> | |||||
auto format(const duration& d, FormatContext& ctx) -> decltype(ctx.out()) { | |||||
auto begin = format_str.begin(), end = format_str.end(); | |||||
// As a possible future optimization, we could avoid extra copying if width | |||||
// is not specified. | |||||
basic_memory_buffer<Char> buf; | |||||
auto out = std::back_inserter(buf); | |||||
using range = internal::output_range<decltype(ctx.out()), Char>; | |||||
internal::basic_writer<range> w(range(ctx.out())); | |||||
internal::handle_dynamic_spec<internal::width_checker>( | |||||
specs.width, width_ref, ctx, format_str.begin()); | |||||
internal::handle_dynamic_spec<internal::precision_checker>( | |||||
precision, precision_ref, ctx, format_str.begin()); | |||||
if (begin == end || *begin == '}') { | |||||
out = internal::format_chrono_duration_value(out, d.count(), precision); | |||||
internal::format_chrono_duration_unit<Period>(out); | |||||
} else { | |||||
internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f( | |||||
ctx, out, d); | |||||
f.precision = precision; | |||||
parse_chrono_format(begin, end, f); | |||||
} | |||||
w.write(buf.data(), buf.size(), specs); | |||||
return w.out(); | |||||
} | |||||
}; | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_CHRONO_H_ |
@@ -0,0 +1,585 @@ | |||||
// Formatting library for C++ - color support | |||||
// | |||||
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
#ifndef FMT_COLOR_H_ | |||||
#define FMT_COLOR_H_ | |||||
#include "format.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
enum class color : uint32_t { | |||||
alice_blue = 0xF0F8FF, // rgb(240,248,255) | |||||
antique_white = 0xFAEBD7, // rgb(250,235,215) | |||||
aqua = 0x00FFFF, // rgb(0,255,255) | |||||
aquamarine = 0x7FFFD4, // rgb(127,255,212) | |||||
azure = 0xF0FFFF, // rgb(240,255,255) | |||||
beige = 0xF5F5DC, // rgb(245,245,220) | |||||
bisque = 0xFFE4C4, // rgb(255,228,196) | |||||
black = 0x000000, // rgb(0,0,0) | |||||
blanched_almond = 0xFFEBCD, // rgb(255,235,205) | |||||
blue = 0x0000FF, // rgb(0,0,255) | |||||
blue_violet = 0x8A2BE2, // rgb(138,43,226) | |||||
brown = 0xA52A2A, // rgb(165,42,42) | |||||
burly_wood = 0xDEB887, // rgb(222,184,135) | |||||
cadet_blue = 0x5F9EA0, // rgb(95,158,160) | |||||
chartreuse = 0x7FFF00, // rgb(127,255,0) | |||||
chocolate = 0xD2691E, // rgb(210,105,30) | |||||
coral = 0xFF7F50, // rgb(255,127,80) | |||||
cornflower_blue = 0x6495ED, // rgb(100,149,237) | |||||
cornsilk = 0xFFF8DC, // rgb(255,248,220) | |||||
crimson = 0xDC143C, // rgb(220,20,60) | |||||
cyan = 0x00FFFF, // rgb(0,255,255) | |||||
dark_blue = 0x00008B, // rgb(0,0,139) | |||||
dark_cyan = 0x008B8B, // rgb(0,139,139) | |||||
dark_golden_rod = 0xB8860B, // rgb(184,134,11) | |||||
dark_gray = 0xA9A9A9, // rgb(169,169,169) | |||||
dark_green = 0x006400, // rgb(0,100,0) | |||||
dark_khaki = 0xBDB76B, // rgb(189,183,107) | |||||
dark_magenta = 0x8B008B, // rgb(139,0,139) | |||||
dark_olive_green = 0x556B2F, // rgb(85,107,47) | |||||
dark_orange = 0xFF8C00, // rgb(255,140,0) | |||||
dark_orchid = 0x9932CC, // rgb(153,50,204) | |||||
dark_red = 0x8B0000, // rgb(139,0,0) | |||||
dark_salmon = 0xE9967A, // rgb(233,150,122) | |||||
dark_sea_green = 0x8FBC8F, // rgb(143,188,143) | |||||
dark_slate_blue = 0x483D8B, // rgb(72,61,139) | |||||
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) | |||||
dark_turquoise = 0x00CED1, // rgb(0,206,209) | |||||
dark_violet = 0x9400D3, // rgb(148,0,211) | |||||
deep_pink = 0xFF1493, // rgb(255,20,147) | |||||
deep_sky_blue = 0x00BFFF, // rgb(0,191,255) | |||||
dim_gray = 0x696969, // rgb(105,105,105) | |||||
dodger_blue = 0x1E90FF, // rgb(30,144,255) | |||||
fire_brick = 0xB22222, // rgb(178,34,34) | |||||
floral_white = 0xFFFAF0, // rgb(255,250,240) | |||||
forest_green = 0x228B22, // rgb(34,139,34) | |||||
fuchsia = 0xFF00FF, // rgb(255,0,255) | |||||
gainsboro = 0xDCDCDC, // rgb(220,220,220) | |||||
ghost_white = 0xF8F8FF, // rgb(248,248,255) | |||||
gold = 0xFFD700, // rgb(255,215,0) | |||||
golden_rod = 0xDAA520, // rgb(218,165,32) | |||||
gray = 0x808080, // rgb(128,128,128) | |||||
green = 0x008000, // rgb(0,128,0) | |||||
green_yellow = 0xADFF2F, // rgb(173,255,47) | |||||
honey_dew = 0xF0FFF0, // rgb(240,255,240) | |||||
hot_pink = 0xFF69B4, // rgb(255,105,180) | |||||
indian_red = 0xCD5C5C, // rgb(205,92,92) | |||||
indigo = 0x4B0082, // rgb(75,0,130) | |||||
ivory = 0xFFFFF0, // rgb(255,255,240) | |||||
khaki = 0xF0E68C, // rgb(240,230,140) | |||||
lavender = 0xE6E6FA, // rgb(230,230,250) | |||||
lavender_blush = 0xFFF0F5, // rgb(255,240,245) | |||||
lawn_green = 0x7CFC00, // rgb(124,252,0) | |||||
lemon_chiffon = 0xFFFACD, // rgb(255,250,205) | |||||
light_blue = 0xADD8E6, // rgb(173,216,230) | |||||
light_coral = 0xF08080, // rgb(240,128,128) | |||||
light_cyan = 0xE0FFFF, // rgb(224,255,255) | |||||
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) | |||||
light_gray = 0xD3D3D3, // rgb(211,211,211) | |||||
light_green = 0x90EE90, // rgb(144,238,144) | |||||
light_pink = 0xFFB6C1, // rgb(255,182,193) | |||||
light_salmon = 0xFFA07A, // rgb(255,160,122) | |||||
light_sea_green = 0x20B2AA, // rgb(32,178,170) | |||||
light_sky_blue = 0x87CEFA, // rgb(135,206,250) | |||||
light_slate_gray = 0x778899, // rgb(119,136,153) | |||||
light_steel_blue = 0xB0C4DE, // rgb(176,196,222) | |||||
light_yellow = 0xFFFFE0, // rgb(255,255,224) | |||||
lime = 0x00FF00, // rgb(0,255,0) | |||||
lime_green = 0x32CD32, // rgb(50,205,50) | |||||
linen = 0xFAF0E6, // rgb(250,240,230) | |||||
magenta = 0xFF00FF, // rgb(255,0,255) | |||||
maroon = 0x800000, // rgb(128,0,0) | |||||
medium_aquamarine = 0x66CDAA, // rgb(102,205,170) | |||||
medium_blue = 0x0000CD, // rgb(0,0,205) | |||||
medium_orchid = 0xBA55D3, // rgb(186,85,211) | |||||
medium_purple = 0x9370DB, // rgb(147,112,219) | |||||
medium_sea_green = 0x3CB371, // rgb(60,179,113) | |||||
medium_slate_blue = 0x7B68EE, // rgb(123,104,238) | |||||
medium_spring_green = 0x00FA9A, // rgb(0,250,154) | |||||
medium_turquoise = 0x48D1CC, // rgb(72,209,204) | |||||
medium_violet_red = 0xC71585, // rgb(199,21,133) | |||||
midnight_blue = 0x191970, // rgb(25,25,112) | |||||
mint_cream = 0xF5FFFA, // rgb(245,255,250) | |||||
misty_rose = 0xFFE4E1, // rgb(255,228,225) | |||||
moccasin = 0xFFE4B5, // rgb(255,228,181) | |||||
navajo_white = 0xFFDEAD, // rgb(255,222,173) | |||||
navy = 0x000080, // rgb(0,0,128) | |||||
old_lace = 0xFDF5E6, // rgb(253,245,230) | |||||
olive = 0x808000, // rgb(128,128,0) | |||||
olive_drab = 0x6B8E23, // rgb(107,142,35) | |||||
orange = 0xFFA500, // rgb(255,165,0) | |||||
orange_red = 0xFF4500, // rgb(255,69,0) | |||||
orchid = 0xDA70D6, // rgb(218,112,214) | |||||
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) | |||||
pale_green = 0x98FB98, // rgb(152,251,152) | |||||
pale_turquoise = 0xAFEEEE, // rgb(175,238,238) | |||||
pale_violet_red = 0xDB7093, // rgb(219,112,147) | |||||
papaya_whip = 0xFFEFD5, // rgb(255,239,213) | |||||
peach_puff = 0xFFDAB9, // rgb(255,218,185) | |||||
peru = 0xCD853F, // rgb(205,133,63) | |||||
pink = 0xFFC0CB, // rgb(255,192,203) | |||||
plum = 0xDDA0DD, // rgb(221,160,221) | |||||
powder_blue = 0xB0E0E6, // rgb(176,224,230) | |||||
purple = 0x800080, // rgb(128,0,128) | |||||
rebecca_purple = 0x663399, // rgb(102,51,153) | |||||
red = 0xFF0000, // rgb(255,0,0) | |||||
rosy_brown = 0xBC8F8F, // rgb(188,143,143) | |||||
royal_blue = 0x4169E1, // rgb(65,105,225) | |||||
saddle_brown = 0x8B4513, // rgb(139,69,19) | |||||
salmon = 0xFA8072, // rgb(250,128,114) | |||||
sandy_brown = 0xF4A460, // rgb(244,164,96) | |||||
sea_green = 0x2E8B57, // rgb(46,139,87) | |||||
sea_shell = 0xFFF5EE, // rgb(255,245,238) | |||||
sienna = 0xA0522D, // rgb(160,82,45) | |||||
silver = 0xC0C0C0, // rgb(192,192,192) | |||||
sky_blue = 0x87CEEB, // rgb(135,206,235) | |||||
slate_blue = 0x6A5ACD, // rgb(106,90,205) | |||||
slate_gray = 0x708090, // rgb(112,128,144) | |||||
snow = 0xFFFAFA, // rgb(255,250,250) | |||||
spring_green = 0x00FF7F, // rgb(0,255,127) | |||||
steel_blue = 0x4682B4, // rgb(70,130,180) | |||||
tan = 0xD2B48C, // rgb(210,180,140) | |||||
teal = 0x008080, // rgb(0,128,128) | |||||
thistle = 0xD8BFD8, // rgb(216,191,216) | |||||
tomato = 0xFF6347, // rgb(255,99,71) | |||||
turquoise = 0x40E0D0, // rgb(64,224,208) | |||||
violet = 0xEE82EE, // rgb(238,130,238) | |||||
wheat = 0xF5DEB3, // rgb(245,222,179) | |||||
white = 0xFFFFFF, // rgb(255,255,255) | |||||
white_smoke = 0xF5F5F5, // rgb(245,245,245) | |||||
yellow = 0xFFFF00, // rgb(255,255,0) | |||||
yellow_green = 0x9ACD32 // rgb(154,205,50) | |||||
}; // enum class color | |||||
enum class terminal_color : uint8_t { | |||||
black = 30, | |||||
red, | |||||
green, | |||||
yellow, | |||||
blue, | |||||
magenta, | |||||
cyan, | |||||
white, | |||||
bright_black = 90, | |||||
bright_red, | |||||
bright_green, | |||||
bright_yellow, | |||||
bright_blue, | |||||
bright_magenta, | |||||
bright_cyan, | |||||
bright_white | |||||
}; | |||||
enum class emphasis : uint8_t { | |||||
bold = 1, | |||||
italic = 1 << 1, | |||||
underline = 1 << 2, | |||||
strikethrough = 1 << 3 | |||||
}; | |||||
// rgb is a struct for red, green and blue colors. | |||||
// Using the name "rgb" makes some editors show the color in a tooltip. | |||||
struct rgb { | |||||
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} | |||||
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} | |||||
FMT_CONSTEXPR rgb(uint32_t hex) | |||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} | |||||
FMT_CONSTEXPR rgb(color hex) | |||||
: r((uint32_t(hex) >> 16) & 0xFF), | |||||
g((uint32_t(hex) >> 8) & 0xFF), | |||||
b(uint32_t(hex) & 0xFF) {} | |||||
uint8_t r; | |||||
uint8_t g; | |||||
uint8_t b; | |||||
}; | |||||
namespace internal { | |||||
// color is a struct of either a rgb color or a terminal color. | |||||
struct color_type { | |||||
FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {} | |||||
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true), | |||||
value{} { | |||||
value.rgb_color = static_cast<uint32_t>(rgb_color); | |||||
} | |||||
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} { | |||||
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) | | |||||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b; | |||||
} | |||||
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(), | |||||
value{} { | |||||
value.term_color = static_cast<uint8_t>(term_color); | |||||
} | |||||
bool is_rgb; | |||||
union color_union { | |||||
uint8_t term_color; | |||||
uint32_t rgb_color; | |||||
} value; | |||||
}; | |||||
} // namespace internal | |||||
// Experimental text formatting support. | |||||
class text_style { | |||||
public: | |||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT | |||||
: set_foreground_color(), | |||||
set_background_color(), | |||||
ems(em) {} | |||||
FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) { | |||||
if (!set_foreground_color) { | |||||
set_foreground_color = rhs.set_foreground_color; | |||||
foreground_color = rhs.foreground_color; | |||||
} else if (rhs.set_foreground_color) { | |||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) | |||||
FMT_THROW(format_error("can't OR a terminal color")); | |||||
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; | |||||
} | |||||
if (!set_background_color) { | |||||
set_background_color = rhs.set_background_color; | |||||
background_color = rhs.background_color; | |||||
} else if (rhs.set_background_color) { | |||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb) | |||||
FMT_THROW(format_error("can't OR a terminal color")); | |||||
background_color.value.rgb_color |= rhs.background_color.value.rgb_color; | |||||
} | |||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) | | |||||
static_cast<uint8_t>(rhs.ems)); | |||||
return *this; | |||||
} | |||||
friend FMT_CONSTEXPR text_style operator|(text_style lhs, | |||||
const text_style& rhs) { | |||||
return lhs |= rhs; | |||||
} | |||||
FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) { | |||||
if (!set_foreground_color) { | |||||
set_foreground_color = rhs.set_foreground_color; | |||||
foreground_color = rhs.foreground_color; | |||||
} else if (rhs.set_foreground_color) { | |||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) | |||||
FMT_THROW(format_error("can't AND a terminal color")); | |||||
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color; | |||||
} | |||||
if (!set_background_color) { | |||||
set_background_color = rhs.set_background_color; | |||||
background_color = rhs.background_color; | |||||
} else if (rhs.set_background_color) { | |||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb) | |||||
FMT_THROW(format_error("can't AND a terminal color")); | |||||
background_color.value.rgb_color &= rhs.background_color.value.rgb_color; | |||||
} | |||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) & | |||||
static_cast<uint8_t>(rhs.ems)); | |||||
return *this; | |||||
} | |||||
friend FMT_CONSTEXPR text_style operator&(text_style lhs, | |||||
const text_style& rhs) { | |||||
return lhs &= rhs; | |||||
} | |||||
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT { | |||||
return set_foreground_color; | |||||
} | |||||
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT { | |||||
return set_background_color; | |||||
} | |||||
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT { | |||||
return static_cast<uint8_t>(ems) != 0; | |||||
} | |||||
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT { | |||||
assert(has_foreground() && "no foreground specified for this style"); | |||||
return foreground_color; | |||||
} | |||||
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT { | |||||
assert(has_background() && "no background specified for this style"); | |||||
return background_color; | |||||
} | |||||
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT { | |||||
assert(has_emphasis() && "no emphasis specified for this style"); | |||||
return ems; | |||||
} | |||||
private: | |||||
FMT_CONSTEXPR text_style(bool is_foreground, | |||||
internal::color_type text_color) FMT_NOEXCEPT | |||||
: set_foreground_color(), | |||||
set_background_color(), | |||||
ems() { | |||||
if (is_foreground) { | |||||
foreground_color = text_color; | |||||
set_foreground_color = true; | |||||
} else { | |||||
background_color = text_color; | |||||
set_background_color = true; | |||||
} | |||||
} | |||||
friend FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground) | |||||
FMT_NOEXCEPT; | |||||
friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background) | |||||
FMT_NOEXCEPT; | |||||
internal::color_type foreground_color; | |||||
internal::color_type background_color; | |||||
bool set_foreground_color; | |||||
bool set_background_color; | |||||
emphasis ems; | |||||
}; | |||||
FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT { | |||||
return text_style(/*is_foreground=*/true, foreground); | |||||
} | |||||
FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT { | |||||
return text_style(/*is_foreground=*/false, background); | |||||
} | |||||
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT { | |||||
return text_style(lhs) | rhs; | |||||
} | |||||
namespace internal { | |||||
template <typename Char> struct ansi_color_escape { | |||||
FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color, | |||||
const char* esc) FMT_NOEXCEPT { | |||||
// If we have a terminal color, we need to output another escape code | |||||
// sequence. | |||||
if (!text_color.is_rgb) { | |||||
bool is_background = esc == internal::data::background_color; | |||||
uint32_t value = text_color.value.term_color; | |||||
// Background ASCII codes are the same as the foreground ones but with | |||||
// 10 more. | |||||
if (is_background) value += 10u; | |||||
std::size_t index = 0; | |||||
buffer[index++] = static_cast<Char>('\x1b'); | |||||
buffer[index++] = static_cast<Char>('['); | |||||
if (value >= 100u) { | |||||
buffer[index++] = static_cast<Char>('1'); | |||||
value %= 100u; | |||||
} | |||||
buffer[index++] = static_cast<Char>('0' + value / 10u); | |||||
buffer[index++] = static_cast<Char>('0' + value % 10u); | |||||
buffer[index++] = static_cast<Char>('m'); | |||||
buffer[index++] = static_cast<Char>('\0'); | |||||
return; | |||||
} | |||||
for (int i = 0; i < 7; i++) { | |||||
buffer[i] = static_cast<Char>(esc[i]); | |||||
} | |||||
rgb color(text_color.value.rgb_color); | |||||
to_esc(color.r, buffer + 7, ';'); | |||||
to_esc(color.g, buffer + 11, ';'); | |||||
to_esc(color.b, buffer + 15, 'm'); | |||||
buffer[19] = static_cast<Char>(0); | |||||
} | |||||
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT { | |||||
uint8_t em_codes[4] = {}; | |||||
uint8_t em_bits = static_cast<uint8_t>(em); | |||||
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1; | |||||
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3; | |||||
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4; | |||||
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough)) | |||||
em_codes[3] = 9; | |||||
std::size_t index = 0; | |||||
for (int i = 0; i < 4; ++i) { | |||||
if (!em_codes[i]) continue; | |||||
buffer[index++] = static_cast<Char>('\x1b'); | |||||
buffer[index++] = static_cast<Char>('['); | |||||
buffer[index++] = static_cast<Char>('0' + em_codes[i]); | |||||
buffer[index++] = static_cast<Char>('m'); | |||||
} | |||||
buffer[index++] = static_cast<Char>(0); | |||||
} | |||||
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; } | |||||
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } | |||||
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT { | |||||
return buffer + std::strlen(buffer); | |||||
} | |||||
private: | |||||
Char buffer[7u + 3u * 4u + 1u]; | |||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, | |||||
char delimiter) FMT_NOEXCEPT { | |||||
out[0] = static_cast<Char>('0' + c / 100); | |||||
out[1] = static_cast<Char>('0' + c / 10 % 10); | |||||
out[2] = static_cast<Char>('0' + c % 10); | |||||
out[3] = static_cast<Char>(delimiter); | |||||
} | |||||
}; | |||||
template <typename Char> | |||||
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color( | |||||
internal::color_type foreground) FMT_NOEXCEPT { | |||||
return ansi_color_escape<Char>(foreground, internal::data::foreground_color); | |||||
} | |||||
template <typename Char> | |||||
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color( | |||||
internal::color_type background) FMT_NOEXCEPT { | |||||
return ansi_color_escape<Char>(background, internal::data::background_color); | |||||
} | |||||
template <typename Char> | |||||
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT { | |||||
return ansi_color_escape<Char>(em); | |||||
} | |||||
template <typename Char> | |||||
inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT { | |||||
std::fputs(chars, stream); | |||||
} | |||||
template <> | |||||
inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT { | |||||
std::fputws(chars, stream); | |||||
} | |||||
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT { | |||||
fputs(internal::data::reset_color, stream); | |||||
} | |||||
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT { | |||||
fputs(internal::data::wreset_color, stream); | |||||
} | |||||
template <typename Char> | |||||
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT { | |||||
const char* begin = data::reset_color; | |||||
const char* end = begin + sizeof(data::reset_color) - 1; | |||||
buffer.append(begin, end); | |||||
} | |||||
template <typename Char> | |||||
std::basic_string<Char> vformat(const text_style& ts, | |||||
basic_string_view<Char> format_str, | |||||
basic_format_args<buffer_context<Char> > args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
bool has_style = false; | |||||
if (ts.has_emphasis()) { | |||||
has_style = true; | |||||
ansi_color_escape<Char> escape = make_emphasis<Char>(ts.get_emphasis()); | |||||
buffer.append(escape.begin(), escape.end()); | |||||
} | |||||
if (ts.has_foreground()) { | |||||
has_style = true; | |||||
ansi_color_escape<Char> escape = | |||||
make_foreground_color<Char>(ts.get_foreground()); | |||||
buffer.append(escape.begin(), escape.end()); | |||||
} | |||||
if (ts.has_background()) { | |||||
has_style = true; | |||||
ansi_color_escape<Char> escape = | |||||
make_background_color<Char>(ts.get_background()); | |||||
buffer.append(escape.begin(), escape.end()); | |||||
} | |||||
internal::vformat_to(buffer, format_str, args); | |||||
if (has_style) { | |||||
reset_color<Char>(buffer); | |||||
} | |||||
return fmt::to_string(buffer); | |||||
} | |||||
} // namespace internal | |||||
template <typename S, typename Char = char_t<S> > | |||||
void vprint(std::FILE* f, const text_style& ts, const S& format, | |||||
basic_format_args<buffer_context<Char> > args) { | |||||
bool has_style = false; | |||||
if (ts.has_emphasis()) { | |||||
has_style = true; | |||||
internal::fputs<Char>(internal::make_emphasis<Char>(ts.get_emphasis()), f); | |||||
} | |||||
if (ts.has_foreground()) { | |||||
has_style = true; | |||||
internal::fputs<Char>( | |||||
internal::make_foreground_color<Char>(ts.get_foreground()), f); | |||||
} | |||||
if (ts.has_background()) { | |||||
has_style = true; | |||||
internal::fputs<Char>( | |||||
internal::make_background_color<Char>(ts.get_background()), f); | |||||
} | |||||
vprint(f, format, args); | |||||
if (has_style) { | |||||
internal::reset_color<Char>(f); | |||||
} | |||||
} | |||||
/** | |||||
Formats a string and prints it to the specified file stream using ANSI | |||||
escape sequences to specify text formatting. | |||||
Example: | |||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red), | |||||
"Elapsed time: {0:.2f} seconds", 1.23); | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
FMT_ENABLE_IF(internal::is_string<S>::value)> | |||||
void print(std::FILE* f, const text_style& ts, const S& format_str, | |||||
const Args&... args) { | |||||
internal::check_format_string<Args...>(format_str); | |||||
using context = buffer_context<char_t<S> >; | |||||
format_arg_store<context, Args...> as{args...}; | |||||
vprint(f, ts, format_str, basic_format_args<context>(as)); | |||||
} | |||||
/** | |||||
Formats a string and prints it to stdout using ANSI escape sequences to | |||||
specify text formatting. | |||||
Example: | |||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red), | |||||
"Elapsed time: {0:.2f} seconds", 1.23); | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
FMT_ENABLE_IF(internal::is_string<S>::value)> | |||||
void print(const text_style& ts, const S& format_str, const Args&... args) { | |||||
return print(stdout, ts, format_str, args...); | |||||
} | |||||
template <typename S, typename Char = char_t<S> > | |||||
inline std::basic_string<Char> vformat( | |||||
const text_style& ts, const S& format_str, | |||||
basic_format_args<buffer_context<Char> > args) { | |||||
return internal::vformat(ts, to_string_view(format_str), args); | |||||
} | |||||
/** | |||||
\rst | |||||
Formats arguments and returns the result as a string using ANSI | |||||
escape sequences to specify text formatting. | |||||
**Example**:: | |||||
#include <fmt/color.h> | |||||
std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), | |||||
"The answer is {}", 42); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, typename Char = char_t<S> > | |||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str, | |||||
const Args&... args) { | |||||
return internal::vformat(ts, to_string_view(format_str), | |||||
{internal::make_args_checked(format_str, args...)}); | |||||
} | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_COLOR_H_ |
@@ -0,0 +1,466 @@ | |||||
// Formatting library for C++ - experimental format string compilation | |||||
// | |||||
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
#ifndef FMT_COMPILE_H_ | |||||
#define FMT_COMPILE_H_ | |||||
#include <vector> | |||||
#include "format.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
namespace internal { | |||||
template <typename Char> struct format_part { | |||||
public: | |||||
struct named_argument_id { | |||||
FMT_CONSTEXPR named_argument_id(internal::string_view_metadata id) | |||||
: id(id) {} | |||||
internal::string_view_metadata id; | |||||
}; | |||||
struct argument_id { | |||||
FMT_CONSTEXPR argument_id() : argument_id(0u) {} | |||||
FMT_CONSTEXPR argument_id(unsigned id) | |||||
: which(which_arg_id::index), val(id) {} | |||||
FMT_CONSTEXPR argument_id(internal::string_view_metadata id) | |||||
: which(which_arg_id::named_index), val(id) {} | |||||
enum class which_arg_id { index, named_index }; | |||||
which_arg_id which; | |||||
union value { | |||||
FMT_CONSTEXPR value() : index(0u) {} | |||||
FMT_CONSTEXPR value(unsigned id) : index(id) {} | |||||
FMT_CONSTEXPR value(internal::string_view_metadata id) | |||||
: named_index(id) {} | |||||
unsigned index; | |||||
internal::string_view_metadata named_index; | |||||
} val; | |||||
}; | |||||
struct specification { | |||||
FMT_CONSTEXPR specification() : arg_id(0u) {} | |||||
FMT_CONSTEXPR specification(unsigned id) : arg_id(id) {} | |||||
FMT_CONSTEXPR specification(internal::string_view_metadata id) | |||||
: arg_id(id) {} | |||||
argument_id arg_id; | |||||
internal::dynamic_format_specs<Char> parsed_specs; | |||||
}; | |||||
FMT_CONSTEXPR format_part() | |||||
: which(kind::argument_id), end_of_argument_id(0u), val(0u) {} | |||||
FMT_CONSTEXPR format_part(internal::string_view_metadata text) | |||||
: which(kind::text), end_of_argument_id(0u), val(text) {} | |||||
FMT_CONSTEXPR format_part(unsigned id) | |||||
: which(kind::argument_id), end_of_argument_id(0u), val(id) {} | |||||
FMT_CONSTEXPR format_part(named_argument_id arg_id) | |||||
: which(kind::named_argument_id), end_of_argument_id(0u), val(arg_id) {} | |||||
FMT_CONSTEXPR format_part(specification spec) | |||||
: which(kind::specification), end_of_argument_id(0u), val(spec) {} | |||||
enum class kind { argument_id, named_argument_id, text, specification }; | |||||
kind which; | |||||
std::size_t end_of_argument_id; | |||||
union value { | |||||
FMT_CONSTEXPR value() : arg_id(0u) {} | |||||
FMT_CONSTEXPR value(unsigned id) : arg_id(id) {} | |||||
FMT_CONSTEXPR value(named_argument_id named_id) | |||||
: named_arg_id(named_id.id) {} | |||||
FMT_CONSTEXPR value(internal::string_view_metadata t) : text(t) {} | |||||
FMT_CONSTEXPR value(specification s) : spec(s) {} | |||||
unsigned arg_id; | |||||
internal::string_view_metadata named_arg_id; | |||||
internal::string_view_metadata text; | |||||
specification spec; | |||||
} val; | |||||
}; | |||||
template <typename Char, typename PartsContainer> | |||||
class format_preparation_handler : public internal::error_handler { | |||||
private: | |||||
using part = format_part<Char>; | |||||
public: | |||||
using iterator = typename basic_string_view<Char>::iterator; | |||||
FMT_CONSTEXPR format_preparation_handler(basic_string_view<Char> format, | |||||
PartsContainer& parts) | |||||
: parts_(parts), format_(format), parse_context_(format) {} | |||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { | |||||
if (begin == end) return; | |||||
const auto offset = begin - format_.data(); | |||||
const auto size = end - begin; | |||||
parts_.push_back(part(string_view_metadata(offset, size))); | |||||
} | |||||
FMT_CONSTEXPR void on_arg_id() { | |||||
parts_.push_back(part(parse_context_.next_arg_id())); | |||||
} | |||||
FMT_CONSTEXPR void on_arg_id(unsigned id) { | |||||
parse_context_.check_arg_id(id); | |||||
parts_.push_back(part(id)); | |||||
} | |||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) { | |||||
const auto view = string_view_metadata(format_, id); | |||||
const auto arg_id = typename part::named_argument_id(view); | |||||
parts_.push_back(part(arg_id)); | |||||
} | |||||
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) { | |||||
parts_.back().end_of_argument_id = ptr - format_.begin(); | |||||
} | |||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, | |||||
const Char* end) { | |||||
const auto specs_offset = to_unsigned(begin - format_.begin()); | |||||
using parse_context = basic_parse_context<Char>; | |||||
internal::dynamic_format_specs<Char> parsed_specs; | |||||
dynamic_specs_handler<parse_context> handler(parsed_specs, parse_context_); | |||||
begin = parse_format_specs(begin, end, handler); | |||||
if (*begin != '}') on_error("missing '}' in format string"); | |||||
auto& last_part = parts_.back(); | |||||
auto specs = last_part.which == part::kind::argument_id | |||||
? typename part::specification(last_part.val.arg_id) | |||||
: typename part::specification(last_part.val.named_arg_id); | |||||
specs.parsed_specs = parsed_specs; | |||||
last_part = part(specs); | |||||
last_part.end_of_argument_id = specs_offset; | |||||
return begin; | |||||
} | |||||
private: | |||||
PartsContainer& parts_; | |||||
basic_string_view<Char> format_; | |||||
basic_parse_context<Char> parse_context_; | |||||
}; | |||||
template <typename Format, typename PreparedPartsProvider, typename... Args> | |||||
class prepared_format { | |||||
public: | |||||
using char_type = char_t<Format>; | |||||
using format_part_t = format_part<char_type>; | |||||
constexpr prepared_format(Format f) | |||||
: format_(std::move(f)), parts_provider_(to_string_view(format_)) {} | |||||
prepared_format() = delete; | |||||
using context = buffer_context<char_type>; | |||||
template <typename Range, typename Context> | |||||
auto vformat_to(Range out, basic_format_args<Context> args) const -> | |||||
typename Context::iterator { | |||||
const auto format_view = internal::to_string_view(format_); | |||||
basic_parse_context<char_type> parse_ctx(format_view); | |||||
Context ctx(out.begin(), args); | |||||
const auto& parts = parts_provider_.parts(); | |||||
for (auto part_it = parts.begin(); part_it != parts.end(); ++part_it) { | |||||
const auto& part = *part_it; | |||||
const auto& value = part.val; | |||||
switch (part.which) { | |||||
case format_part_t::kind::text: { | |||||
const auto text = value.text.to_view(format_view.data()); | |||||
auto output = ctx.out(); | |||||
auto&& it = internal::reserve(output, text.size()); | |||||
it = std::copy_n(text.begin(), text.size(), it); | |||||
ctx.advance_to(output); | |||||
} break; | |||||
case format_part_t::kind::argument_id: { | |||||
advance_parse_context_to_specification(parse_ctx, part); | |||||
format_arg<Range>(parse_ctx, ctx, value.arg_id); | |||||
} break; | |||||
case format_part_t::kind::named_argument_id: { | |||||
advance_parse_context_to_specification(parse_ctx, part); | |||||
const auto named_arg_id = | |||||
value.named_arg_id.to_view(format_view.data()); | |||||
format_arg<Range>(parse_ctx, ctx, named_arg_id); | |||||
} break; | |||||
case format_part_t::kind::specification: { | |||||
const auto& arg_id_value = value.spec.arg_id.val; | |||||
const auto arg = value.spec.arg_id.which == | |||||
format_part_t::argument_id::which_arg_id::index | |||||
? ctx.arg(arg_id_value.index) | |||||
: ctx.arg(arg_id_value.named_index.to_view( | |||||
to_string_view(format_).data())); | |||||
auto specs = value.spec.parsed_specs; | |||||
handle_dynamic_spec<internal::width_checker>( | |||||
specs.width, specs.width_ref, ctx, format_view.begin()); | |||||
handle_dynamic_spec<internal::precision_checker>( | |||||
specs.precision, specs.precision_ref, ctx, format_view.begin()); | |||||
check_prepared_specs(specs, arg.type()); | |||||
advance_parse_context_to_specification(parse_ctx, part); | |||||
ctx.advance_to( | |||||
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg)); | |||||
} break; | |||||
} | |||||
} | |||||
return ctx.out(); | |||||
} | |||||
private: | |||||
void advance_parse_context_to_specification( | |||||
basic_parse_context<char_type>& parse_ctx, | |||||
const format_part_t& part) const { | |||||
const auto view = to_string_view(format_); | |||||
const auto specification_begin = view.data() + part.end_of_argument_id; | |||||
advance_to(parse_ctx, specification_begin); | |||||
} | |||||
template <typename Range, typename Context, typename Id> | |||||
void format_arg(basic_parse_context<char_type>& parse_ctx, Context& ctx, | |||||
Id arg_id) const { | |||||
parse_ctx.check_arg_id(arg_id); | |||||
const auto stopped_at = | |||||
visit_format_arg(arg_formatter<Range>(ctx), ctx.arg(arg_id)); | |||||
ctx.advance_to(stopped_at); | |||||
} | |||||
template <typename Char> | |||||
void check_prepared_specs(const basic_format_specs<Char>& specs, | |||||
internal::type arg_type) const { | |||||
internal::error_handler h; | |||||
numeric_specs_checker<internal::error_handler> checker(h, arg_type); | |||||
if (specs.align == align::numeric) checker.require_numeric_argument(); | |||||
if (specs.sign != sign::none) checker.check_sign(); | |||||
if (specs.alt) checker.require_numeric_argument(); | |||||
if (specs.precision >= 0) checker.check_precision(); | |||||
} | |||||
private: | |||||
Format format_; | |||||
PreparedPartsProvider parts_provider_; | |||||
}; | |||||
template <typename Char> struct part_counter { | |||||
unsigned num_parts = 0; | |||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { | |||||
if (begin != end) ++num_parts; | |||||
} | |||||
FMT_CONSTEXPR void on_arg_id() { ++num_parts; } | |||||
FMT_CONSTEXPR void on_arg_id(unsigned) { ++num_parts; } | |||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; } | |||||
FMT_CONSTEXPR void on_replacement_field(const Char*) {} | |||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, | |||||
const Char* end) { | |||||
// Find the matching brace. | |||||
unsigned braces_counter = 0; | |||||
for (; begin != end; ++begin) { | |||||
if (*begin == '{') { | |||||
++braces_counter; | |||||
} else if (*begin == '}') { | |||||
if (braces_counter == 0u) break; | |||||
--braces_counter; | |||||
} | |||||
} | |||||
return begin; | |||||
} | |||||
FMT_CONSTEXPR void on_error(const char*) {} | |||||
}; | |||||
template <typename Format> class compiletime_prepared_parts_type_provider { | |||||
private: | |||||
using char_type = char_t<Format>; | |||||
static FMT_CONSTEXPR unsigned count_parts() { | |||||
FMT_CONSTEXPR_DECL const auto text = to_string_view(Format{}); | |||||
part_counter<char_type> counter; | |||||
internal::parse_format_string</*IS_CONSTEXPR=*/true>(text, counter); | |||||
return counter.num_parts; | |||||
} | |||||
// Workaround for old compilers. Compiletime parts preparation will not be | |||||
// performed with them anyway. | |||||
#if FMT_USE_CONSTEXPR | |||||
static FMT_CONSTEXPR_DECL const unsigned number_of_format_parts = | |||||
compiletime_prepared_parts_type_provider::count_parts(); | |||||
#else | |||||
static const unsigned number_of_format_parts = 0u; | |||||
#endif | |||||
public: | |||||
template <unsigned N> struct format_parts_array { | |||||
using value_type = format_part<char_type>; | |||||
FMT_CONSTEXPR format_parts_array() : arr{} {} | |||||
FMT_CONSTEXPR value_type& operator[](unsigned ind) { return arr[ind]; } | |||||
FMT_CONSTEXPR const value_type* begin() const { return arr; } | |||||
FMT_CONSTEXPR const value_type* end() const { return begin() + N; } | |||||
private: | |||||
value_type arr[N]; | |||||
}; | |||||
struct empty { | |||||
// Parts preparator will search for it | |||||
using value_type = format_part<char_type>; | |||||
}; | |||||
using type = conditional_t<number_of_format_parts != 0, | |||||
format_parts_array<number_of_format_parts>, empty>; | |||||
}; | |||||
template <typename Parts> class compiletime_prepared_parts_collector { | |||||
private: | |||||
using format_part = typename Parts::value_type; | |||||
public: | |||||
FMT_CONSTEXPR explicit compiletime_prepared_parts_collector(Parts& parts) | |||||
: parts_{parts}, counter_{0u} {} | |||||
FMT_CONSTEXPR void push_back(format_part part) { parts_[counter_++] = part; } | |||||
FMT_CONSTEXPR format_part& back() { return parts_[counter_ - 1]; } | |||||
private: | |||||
Parts& parts_; | |||||
unsigned counter_; | |||||
}; | |||||
template <typename PartsContainer, typename Char> | |||||
FMT_CONSTEXPR PartsContainer prepare_parts(basic_string_view<Char> format) { | |||||
PartsContainer parts; | |||||
internal::parse_format_string</*IS_CONSTEXPR=*/false>( | |||||
format, format_preparation_handler<Char, PartsContainer>(format, parts)); | |||||
return parts; | |||||
} | |||||
template <typename PartsContainer, typename Char> | |||||
FMT_CONSTEXPR PartsContainer | |||||
prepare_compiletime_parts(basic_string_view<Char> format) { | |||||
using collector = compiletime_prepared_parts_collector<PartsContainer>; | |||||
PartsContainer parts; | |||||
collector c(parts); | |||||
internal::parse_format_string</*IS_CONSTEXPR=*/true>( | |||||
format, format_preparation_handler<Char, collector>(format, c)); | |||||
return parts; | |||||
} | |||||
template <typename PartsContainer> class runtime_parts_provider { | |||||
public: | |||||
runtime_parts_provider() = delete; | |||||
template <typename Char> | |||||
runtime_parts_provider(basic_string_view<Char> format) | |||||
: parts_(prepare_parts<PartsContainer>(format)) {} | |||||
const PartsContainer& parts() const { return parts_; } | |||||
private: | |||||
PartsContainer parts_; | |||||
}; | |||||
template <typename Format, typename PartsContainer> | |||||
struct compiletime_parts_provider { | |||||
compiletime_parts_provider() = delete; | |||||
template <typename Char> | |||||
FMT_CONSTEXPR compiletime_parts_provider(basic_string_view<Char>) {} | |||||
const PartsContainer& parts() const { | |||||
static FMT_CONSTEXPR_DECL const PartsContainer prepared_parts = | |||||
prepare_compiletime_parts<PartsContainer>( | |||||
internal::to_string_view(Format{})); | |||||
return prepared_parts; | |||||
} | |||||
}; | |||||
} // namespace internal | |||||
#if FMT_USE_CONSTEXPR | |||||
template <typename... Args, typename S, | |||||
FMT_ENABLE_IF(is_compile_string<S>::value)> | |||||
FMT_CONSTEXPR auto compile(S format_str) -> internal::prepared_format< | |||||
S, | |||||
internal::compiletime_parts_provider< | |||||
S, | |||||
typename internal::compiletime_prepared_parts_type_provider<S>::type>, | |||||
Args...> { | |||||
return format_str; | |||||
} | |||||
#endif | |||||
template <typename... Args, typename Char, size_t N> | |||||
auto compile(const Char (&format_str)[N]) -> internal::prepared_format< | |||||
std::basic_string<Char>, | |||||
internal::runtime_parts_provider<std::vector<internal::format_part<Char>>>, | |||||
Args...> { | |||||
return std::basic_string<Char>(format_str, N - 1); | |||||
} | |||||
template <typename CompiledFormat, typename... Args, | |||||
typename Char = typename CompiledFormat::char_type> | |||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
using range = internal::buffer_range<Char>; | |||||
using context = buffer_context<Char>; | |||||
cf.template vformat_to<range, context>(range(buffer), | |||||
{make_format_args<context>(args...)}); | |||||
return to_string(buffer); | |||||
} | |||||
template <typename OutputIt, typename CompiledFormat, typename... Args> | |||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf, | |||||
const Args&... args) { | |||||
using char_type = typename CompiledFormat::char_type; | |||||
using range = internal::output_range<OutputIt, char_type>; | |||||
using context = format_context_t<OutputIt, char_type>; | |||||
return cf.template vformat_to<range, context>( | |||||
range(out), {make_format_args<context>(args...)}); | |||||
} | |||||
template <typename OutputIt, typename CompiledFormat, typename... Args, | |||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)> | |||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, | |||||
const CompiledFormat& cf, | |||||
const Args&... args) { | |||||
auto it = | |||||
format_to(internal::truncating_iterator<OutputIt>(out, n), cf, args...); | |||||
return {it.base(), it.count()}; | |||||
} | |||||
template <typename CompiledFormat, typename... Args> | |||||
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) { | |||||
return fmt::format_to( | |||||
internal::counting_iterator<typename CompiledFormat::char_type>(), | |||||
cf, args...) | |||||
.count(); | |||||
} | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_COMPILE_H_ |
@@ -0,0 +1,77 @@ | |||||
// Formatting library for C++ - std::locale support | |||||
// | |||||
// Copyright (c) 2012 - present, Victor Zverovich | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
#ifndef FMT_LOCALE_H_ | |||||
#define FMT_LOCALE_H_ | |||||
#include <locale> | |||||
#include "format.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
namespace internal { | |||||
template <typename Char> | |||||
typename buffer_context<Char>::iterator vformat_to( | |||||
const std::locale& loc, buffer<Char>& buf, | |||||
basic_string_view<Char> format_str, | |||||
basic_format_args<buffer_context<Char>> args) { | |||||
using range = buffer_range<Char>; | |||||
return vformat_to<arg_formatter<range>>(buf, to_string_view(format_str), args, | |||||
internal::locale_ref(loc)); | |||||
} | |||||
template <typename Char> | |||||
std::basic_string<Char> vformat(const std::locale& loc, | |||||
basic_string_view<Char> format_str, | |||||
basic_format_args<buffer_context<Char>> args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
internal::vformat_to(loc, buffer, format_str, args); | |||||
return fmt::to_string(buffer); | |||||
} | |||||
} // namespace internal | |||||
template <typename S, typename Char = char_t<S>> | |||||
inline std::basic_string<Char> vformat( | |||||
const std::locale& loc, const S& format_str, | |||||
basic_format_args<buffer_context<Char>> args) { | |||||
return internal::vformat(loc, to_string_view(format_str), args); | |||||
} | |||||
template <typename S, typename... Args, typename Char = char_t<S>> | |||||
inline std::basic_string<Char> format(const std::locale& loc, | |||||
const S& format_str, Args&&... args) { | |||||
return internal::vformat( | |||||
loc, to_string_view(format_str), | |||||
{internal::make_args_checked<Args...>(format_str, args...)}); | |||||
} | |||||
template <typename S, typename OutputIt, typename... Args, | |||||
typename Char = enable_if_t< | |||||
internal::is_output_iterator<OutputIt>::value, char_t<S>>> | |||||
inline OutputIt vformat_to(OutputIt out, const std::locale& loc, | |||||
const S& format_str, | |||||
format_args_t<OutputIt, Char> args) { | |||||
using range = internal::output_range<OutputIt, Char>; | |||||
return vformat_to<arg_formatter<range>>( | |||||
range(out), to_string_view(format_str), args, internal::locale_ref(loc)); | |||||
} | |||||
template <typename OutputIt, typename S, typename... Args, | |||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value&& | |||||
internal::is_string<S>::value)> | |||||
inline OutputIt format_to(OutputIt out, const std::locale& loc, | |||||
const S& format_str, Args&&... args) { | |||||
internal::check_format_string<Args...>(format_str); | |||||
using context = format_context_t<OutputIt, char_t<S>>; | |||||
format_arg_store<context, Args...> as{args...}; | |||||
return vformat_to(out, loc, to_string_view(format_str), | |||||
basic_format_args<context>(as)); | |||||
} | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_LOCALE_H_ |
@@ -0,0 +1,136 @@ | |||||
// Formatting library for C++ - std::ostream support | |||||
// | |||||
// Copyright (c) 2012 - present, Victor Zverovich | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
#ifndef FMT_OSTREAM_H_ | |||||
#define FMT_OSTREAM_H_ | |||||
#include <ostream> | |||||
#include "format.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
namespace internal { | |||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> { | |||||
private: | |||||
using int_type = typename std::basic_streambuf<Char>::int_type; | |||||
using traits_type = typename std::basic_streambuf<Char>::traits_type; | |||||
buffer<Char>& buffer_; | |||||
public: | |||||
formatbuf(buffer<Char>& buf) : buffer_(buf) {} | |||||
protected: | |||||
// The put-area is actually always empty. This makes the implementation | |||||
// simpler and has the advantage that the streambuf and the buffer are always | |||||
// in sync and sputc never writes into uninitialized memory. The obvious | |||||
// disadvantage is that each call to sputc always results in a (virtual) call | |||||
// to overflow. There is no disadvantage here for sputn since this always | |||||
// results in a call to xsputn. | |||||
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE { | |||||
if (!traits_type::eq_int_type(ch, traits_type::eof())) | |||||
buffer_.push_back(static_cast<Char>(ch)); | |||||
return ch; | |||||
} | |||||
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE { | |||||
buffer_.append(s, s + count); | |||||
return count; | |||||
} | |||||
}; | |||||
template <typename Char> struct test_stream : std::basic_ostream<Char> { | |||||
private: | |||||
struct null; | |||||
// Hide all operator<< from std::basic_ostream<Char>. | |||||
void operator<<(null); | |||||
}; | |||||
// Checks if T has a user-defined operator<< (e.g. not a member of | |||||
// std::ostream). | |||||
template <typename T, typename Char> class is_streamable { | |||||
private: | |||||
template <typename U> | |||||
static decltype((void)(std::declval<test_stream<Char>&>() | |||||
<< std::declval<U>()), | |||||
std::true_type()) | |||||
test(int); | |||||
template <typename> static std::false_type test(...); | |||||
using result = decltype(test<T>(0)); | |||||
public: | |||||
static const bool value = result::value; | |||||
}; | |||||
// Write the content of buf to os. | |||||
template <typename Char> | |||||
void write(std::basic_ostream<Char>& os, buffer<Char>& buf) { | |||||
const Char* buf_data = buf.data(); | |||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type; | |||||
unsigned_streamsize size = buf.size(); | |||||
unsigned_streamsize max_size = | |||||
to_unsigned((std::numeric_limits<std::streamsize>::max)()); | |||||
do { | |||||
unsigned_streamsize n = size <= max_size ? size : max_size; | |||||
os.write(buf_data, static_cast<std::streamsize>(n)); | |||||
buf_data += n; | |||||
size -= n; | |||||
} while (size != 0); | |||||
} | |||||
template <typename Char, typename T> | |||||
void format_value(buffer<Char>& buf, const T& value) { | |||||
formatbuf<Char> format_buf(buf); | |||||
std::basic_ostream<Char> output(&format_buf); | |||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit); | |||||
output << value; | |||||
buf.resize(buf.size()); | |||||
} | |||||
// Formats an object of type T that has an overloaded ostream operator<<. | |||||
template <typename T, typename Char> | |||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>> | |||||
: formatter<basic_string_view<Char>, Char> { | |||||
template <typename Context> | |||||
auto format(const T& value, Context& ctx) -> decltype(ctx.out()) { | |||||
basic_memory_buffer<Char> buffer; | |||||
format_value(buffer, value); | |||||
basic_string_view<Char> str(buffer.data(), buffer.size()); | |||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx); | |||||
} | |||||
}; | |||||
} // namespace internal | |||||
template <typename Char> | |||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str, | |||||
basic_format_args<buffer_context<Char>> args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
internal::vformat_to(buffer, format_str, args); | |||||
internal::write(os, buffer); | |||||
} | |||||
/** | |||||
\rst | |||||
Prints formatted data to the stream *os*. | |||||
**Example**:: | |||||
fmt::print(cerr, "Don't {}!", "panic"); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>> | |||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) { | |||||
vprint(os, to_string_view(format_str), | |||||
{internal::make_args_checked<Args...>(format_str, args...)}); | |||||
} | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_OSTREAM_H_ |
@@ -0,0 +1,311 @@ | |||||
// A C++ interface to POSIX functions. | |||||
// | |||||
// Copyright (c) 2012 - 2016, Victor Zverovich | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
#ifndef FMT_POSIX_H_ | |||||
#define FMT_POSIX_H_ | |||||
#if defined(__MINGW32__) || defined(__CYGWIN__) | |||||
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/. | |||||
# undef __STRICT_ANSI__ | |||||
#endif | |||||
#include <errno.h> | |||||
#include <fcntl.h> // for O_RDONLY | |||||
#include <locale.h> // for locale_t | |||||
#include <stdio.h> | |||||
#include <stdlib.h> // for strtod_l | |||||
#include <cstddef> | |||||
#if defined __APPLE__ || defined(__FreeBSD__) | |||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X | |||||
#endif | |||||
#include "format.h" | |||||
#ifndef FMT_POSIX | |||||
# if defined(_WIN32) && !defined(__MINGW32__) | |||||
// Fix warnings about deprecated symbols. | |||||
# define FMT_POSIX(call) _##call | |||||
# else | |||||
# define FMT_POSIX(call) call | |||||
# endif | |||||
#endif | |||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability. | |||||
#ifdef FMT_SYSTEM | |||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call) | |||||
#else | |||||
# define FMT_SYSTEM(call) call | |||||
# ifdef _WIN32 | |||||
// Fix warnings about deprecated symbols. | |||||
# define FMT_POSIX_CALL(call) ::_##call | |||||
# else | |||||
# define FMT_POSIX_CALL(call) ::call | |||||
# endif | |||||
#endif | |||||
// Retries the expression while it evaluates to error_result and errno | |||||
// equals to EINTR. | |||||
#ifndef _WIN32 | |||||
# define FMT_RETRY_VAL(result, expression, error_result) \ | |||||
do { \ | |||||
result = (expression); \ | |||||
} while (result == error_result && errno == EINTR) | |||||
#else | |||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression) | |||||
#endif | |||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) | |||||
FMT_BEGIN_NAMESPACE | |||||
/** | |||||
\rst | |||||
A reference to a null-terminated string. It can be constructed from a C | |||||
string or ``std::string``. | |||||
You can use one of the following type aliases for common character types: | |||||
+---------------+-----------------------------+ | |||||
| Type | Definition | | |||||
+===============+=============================+ | |||||
| cstring_view | basic_cstring_view<char> | | |||||
+---------------+-----------------------------+ | |||||
| wcstring_view | basic_cstring_view<wchar_t> | | |||||
+---------------+-----------------------------+ | |||||
This class is most useful as a parameter type to allow passing | |||||
different types of strings to a function, for example:: | |||||
template <typename... Args> | |||||
std::string format(cstring_view format_str, const Args & ... args); | |||||
format("{}", 42); | |||||
format(std::string("{}"), 42); | |||||
\endrst | |||||
*/ | |||||
template <typename Char> class basic_cstring_view { | |||||
private: | |||||
const Char* data_; | |||||
public: | |||||
/** Constructs a string reference object from a C string. */ | |||||
basic_cstring_view(const Char* s) : data_(s) {} | |||||
/** | |||||
\rst | |||||
Constructs a string reference from an ``std::string`` object. | |||||
\endrst | |||||
*/ | |||||
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {} | |||||
/** Returns the pointer to a C string. */ | |||||
const Char* c_str() const { return data_; } | |||||
}; | |||||
using cstring_view = basic_cstring_view<char>; | |||||
using wcstring_view = basic_cstring_view<wchar_t>; | |||||
// An error code. | |||||
class error_code { | |||||
private: | |||||
int value_; | |||||
public: | |||||
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {} | |||||
int get() const FMT_NOEXCEPT { return value_; } | |||||
}; | |||||
// A buffered file. | |||||
class buffered_file { | |||||
private: | |||||
FILE* file_; | |||||
friend class file; | |||||
explicit buffered_file(FILE* f) : file_(f) {} | |||||
public: | |||||
// Constructs a buffered_file object which doesn't represent any file. | |||||
buffered_file() FMT_NOEXCEPT : file_(nullptr) {} | |||||
// Destroys the object closing the file it represents if any. | |||||
FMT_API ~buffered_file() FMT_NOEXCEPT; | |||||
private: | |||||
buffered_file(const buffered_file&) = delete; | |||||
void operator=(const buffered_file&) = delete; | |||||
public: | |||||
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) { | |||||
other.file_ = nullptr; | |||||
} | |||||
buffered_file& operator=(buffered_file&& other) { | |||||
close(); | |||||
file_ = other.file_; | |||||
other.file_ = nullptr; | |||||
return *this; | |||||
} | |||||
// Opens a file. | |||||
FMT_API buffered_file(cstring_view filename, cstring_view mode); | |||||
// Closes the file. | |||||
FMT_API void close(); | |||||
// Returns the pointer to a FILE object representing this file. | |||||
FILE* get() const FMT_NOEXCEPT { return file_; } | |||||
// We place parentheses around fileno to workaround a bug in some versions | |||||
// of MinGW that define fileno as a macro. | |||||
FMT_API int(fileno)() const; | |||||
void vprint(string_view format_str, format_args args) { | |||||
fmt::vprint(file_, format_str, args); | |||||
} | |||||
template <typename... Args> | |||||
inline void print(string_view format_str, const Args&... args) { | |||||
vprint(format_str, make_format_args(args...)); | |||||
} | |||||
}; | |||||
// A file. Closed file is represented by a file object with descriptor -1. | |||||
// Methods that are not declared with FMT_NOEXCEPT may throw | |||||
// fmt::system_error in case of failure. Note that some errors such as | |||||
// closing the file multiple times will cause a crash on Windows rather | |||||
// than an exception. You can get standard behavior by overriding the | |||||
// invalid parameter handler with _set_invalid_parameter_handler. | |||||
class file { | |||||
private: | |||||
int fd_; // File descriptor. | |||||
// Constructs a file object with a given descriptor. | |||||
explicit file(int fd) : fd_(fd) {} | |||||
public: | |||||
// Possible values for the oflag argument to the constructor. | |||||
enum { | |||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. | |||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. | |||||
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. | |||||
}; | |||||
// Constructs a file object which doesn't represent any file. | |||||
file() FMT_NOEXCEPT : fd_(-1) {} | |||||
// Opens a file and constructs a file object representing this file. | |||||
FMT_API file(cstring_view path, int oflag); | |||||
private: | |||||
file(const file&) = delete; | |||||
void operator=(const file&) = delete; | |||||
public: | |||||
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; } | |||||
file& operator=(file&& other) { | |||||
close(); | |||||
fd_ = other.fd_; | |||||
other.fd_ = -1; | |||||
return *this; | |||||
} | |||||
// Destroys the object closing the file it represents if any. | |||||
FMT_API ~file() FMT_NOEXCEPT; | |||||
// Returns the file descriptor. | |||||
int descriptor() const FMT_NOEXCEPT { return fd_; } | |||||
// Closes the file. | |||||
FMT_API void close(); | |||||
// Returns the file size. The size has signed type for consistency with | |||||
// stat::st_size. | |||||
FMT_API long long size() const; | |||||
// Attempts to read count bytes from the file into the specified buffer. | |||||
FMT_API std::size_t read(void* buffer, std::size_t count); | |||||
// Attempts to write count bytes from the specified buffer to the file. | |||||
FMT_API std::size_t write(const void* buffer, std::size_t count); | |||||
// Duplicates a file descriptor with the dup function and returns | |||||
// the duplicate as a file object. | |||||
FMT_API static file dup(int fd); | |||||
// Makes fd be the copy of this file descriptor, closing fd first if | |||||
// necessary. | |||||
FMT_API void dup2(int fd); | |||||
// Makes fd be the copy of this file descriptor, closing fd first if | |||||
// necessary. | |||||
FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT; | |||||
// Creates a pipe setting up read_end and write_end file objects for reading | |||||
// and writing respectively. | |||||
FMT_API static void pipe(file& read_end, file& write_end); | |||||
// Creates a buffered_file object associated with this file and detaches | |||||
// this file object from the file. | |||||
FMT_API buffered_file fdopen(const char* mode); | |||||
}; | |||||
// Returns the memory page size. | |||||
long getpagesize(); | |||||
#ifdef FMT_LOCALE | |||||
// A "C" numeric locale. | |||||
class Locale { | |||||
private: | |||||
# ifdef _WIN32 | |||||
using locale_t = _locale_t; | |||||
enum { LC_NUMERIC_MASK = LC_NUMERIC }; | |||||
static locale_t newlocale(int category_mask, const char* locale, locale_t) { | |||||
return _create_locale(category_mask, locale); | |||||
} | |||||
static void freelocale(locale_t locale) { _free_locale(locale); } | |||||
static double strtod_l(const char* nptr, char** endptr, _locale_t locale) { | |||||
return _strtod_l(nptr, endptr, locale); | |||||
} | |||||
# endif | |||||
locale_t locale_; | |||||
Locale(const Locale&) = delete; | |||||
void operator=(const Locale&) = delete; | |||||
public: | |||||
using type = locale_t; | |||||
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", nullptr)) { | |||||
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale")); | |||||
} | |||||
~Locale() { freelocale(locale_); } | |||||
type get() const { return locale_; } | |||||
// Converts string to floating-point number and advances str past the end | |||||
// of the parsed input. | |||||
double strtod(const char*& str) const { | |||||
char* end = nullptr; | |||||
double result = strtod_l(str, &end, locale_); | |||||
str = end; | |||||
return result; | |||||
} | |||||
}; | |||||
#endif // FMT_LOCALE | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_POSIX_H_ |
@@ -0,0 +1,715 @@ | |||||
// Formatting library for C++ | |||||
// | |||||
// Copyright (c) 2012 - 2016, Victor Zverovich | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
#ifndef FMT_PRINTF_H_ | |||||
#define FMT_PRINTF_H_ | |||||
#include <algorithm> // std::fill_n | |||||
#include <limits> // std::numeric_limits | |||||
#include "ostream.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
namespace internal { | |||||
// A helper function to suppress bogus "conditional expression is constant" | |||||
// warnings. | |||||
template <typename T> inline T const_check(T value) { return value; } | |||||
// Checks if a value fits in int - used to avoid warnings about comparing | |||||
// signed and unsigned integers. | |||||
template <bool IsSigned> struct int_checker { | |||||
template <typename T> static bool fits_in_int(T value) { | |||||
unsigned max = std::numeric_limits<int>::max(); | |||||
return value <= max; | |||||
} | |||||
static bool fits_in_int(bool) { return true; } | |||||
}; | |||||
template <> struct int_checker<true> { | |||||
template <typename T> static bool fits_in_int(T value) { | |||||
return value >= std::numeric_limits<int>::min() && | |||||
value <= std::numeric_limits<int>::max(); | |||||
} | |||||
static bool fits_in_int(int) { return true; } | |||||
}; | |||||
class printf_precision_handler { | |||||
public: | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
int operator()(T value) { | |||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) | |||||
FMT_THROW(format_error("number is too big")); | |||||
return (std::max)(static_cast<int>(value), 0); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
int operator()(T) { | |||||
FMT_THROW(format_error("precision is not integer")); | |||||
return 0; | |||||
} | |||||
}; | |||||
// An argument visitor that returns true iff arg is a zero integer. | |||||
class is_zero_int { | |||||
public: | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
bool operator()(T value) { | |||||
return value == 0; | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
bool operator()(T) { | |||||
return false; | |||||
} | |||||
}; | |||||
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {}; | |||||
template <> struct make_unsigned_or_bool<bool> { using type = bool; }; | |||||
template <typename T, typename Context> class arg_converter { | |||||
private: | |||||
using char_type = typename Context::char_type; | |||||
basic_format_arg<Context>& arg_; | |||||
char_type type_; | |||||
public: | |||||
arg_converter(basic_format_arg<Context>& arg, char_type type) | |||||
: arg_(arg), type_(type) {} | |||||
void operator()(bool value) { | |||||
if (type_ != 's') operator()<bool>(value); | |||||
} | |||||
template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)> | |||||
void operator()(U value) { | |||||
bool is_signed = type_ == 'd' || type_ == 'i'; | |||||
using target_type = conditional_t<std::is_same<T, void>::value, U, T>; | |||||
if (const_check(sizeof(target_type) <= sizeof(int))) { | |||||
// Extra casts are used to silence warnings. | |||||
if (is_signed) { | |||||
arg_ = internal::make_arg<Context>( | |||||
static_cast<int>(static_cast<target_type>(value))); | |||||
} else { | |||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type; | |||||
arg_ = internal::make_arg<Context>( | |||||
static_cast<unsigned>(static_cast<unsigned_type>(value))); | |||||
} | |||||
} else { | |||||
if (is_signed) { | |||||
// glibc's printf doesn't sign extend arguments of smaller types: | |||||
// std::printf("%lld", -42); // prints "4294967254" | |||||
// but we don't have to do the same because it's a UB. | |||||
arg_ = internal::make_arg<Context>(static_cast<long long>(value)); | |||||
} else { | |||||
arg_ = internal::make_arg<Context>( | |||||
static_cast<typename make_unsigned_or_bool<U>::type>(value)); | |||||
} | |||||
} | |||||
} | |||||
template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)> | |||||
void operator()(U) {} // No conversion needed for non-integral types. | |||||
}; | |||||
// Converts an integer argument to T for printf, if T is an integral type. | |||||
// If T is void, the argument is converted to corresponding signed or unsigned | |||||
// type depending on the type specifier: 'd' and 'i' - signed, other - | |||||
// unsigned). | |||||
template <typename T, typename Context, typename Char> | |||||
void convert_arg(basic_format_arg<Context>& arg, Char type) { | |||||
visit_format_arg(arg_converter<T, Context>(arg, type), arg); | |||||
} | |||||
// Converts an integer argument to char for printf. | |||||
template <typename Context> class char_converter { | |||||
private: | |||||
basic_format_arg<Context>& arg_; | |||||
public: | |||||
explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {} | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
void operator()(T value) { | |||||
arg_ = internal::make_arg<Context>( | |||||
static_cast<typename Context::char_type>(value)); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
void operator()(T) {} // No conversion needed for non-integral types. | |||||
}; | |||||
// Checks if an argument is a valid printf width specifier and sets | |||||
// left alignment if it is negative. | |||||
template <typename Char> class printf_width_handler { | |||||
private: | |||||
using format_specs = basic_format_specs<Char>; | |||||
format_specs& specs_; | |||||
public: | |||||
explicit printf_width_handler(format_specs& specs) : specs_(specs) {} | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
unsigned operator()(T value) { | |||||
auto width = static_cast<uint32_or_64_t<T>>(value); | |||||
if (internal::is_negative(value)) { | |||||
specs_.align = align::left; | |||||
width = 0 - width; | |||||
} | |||||
unsigned int_max = std::numeric_limits<int>::max(); | |||||
if (width > int_max) FMT_THROW(format_error("number is too big")); | |||||
return static_cast<unsigned>(width); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
unsigned operator()(T) { | |||||
FMT_THROW(format_error("width is not integer")); | |||||
return 0; | |||||
} | |||||
}; | |||||
template <typename Char, typename Context> | |||||
void printf(buffer<Char>& buf, basic_string_view<Char> format, | |||||
basic_format_args<Context> args) { | |||||
Context(std::back_inserter(buf), format, args).format(); | |||||
} | |||||
template <typename OutputIt, typename Char, typename Context> | |||||
internal::truncating_iterator<OutputIt> printf( | |||||
internal::truncating_iterator<OutputIt> it, basic_string_view<Char> format, | |||||
basic_format_args<Context> args) { | |||||
return Context(it, format, args).format(); | |||||
} | |||||
} // namespace internal | |||||
using internal::printf; // For printing into memory_buffer. | |||||
template <typename Range> class printf_arg_formatter; | |||||
template <typename OutputIt, typename Char> class basic_printf_context; | |||||
/** | |||||
\rst | |||||
The ``printf`` argument formatter. | |||||
\endrst | |||||
*/ | |||||
template <typename Range> | |||||
class printf_arg_formatter : public internal::arg_formatter_base<Range> { | |||||
public: | |||||
using iterator = typename Range::iterator; | |||||
private: | |||||
using char_type = typename Range::value_type; | |||||
using base = internal::arg_formatter_base<Range>; | |||||
using context_type = basic_printf_context<iterator, char_type>; | |||||
context_type& context_; | |||||
void write_null_pointer(char) { | |||||
this->specs()->type = 0; | |||||
this->write("(nil)"); | |||||
} | |||||
void write_null_pointer(wchar_t) { | |||||
this->specs()->type = 0; | |||||
this->write(L"(nil)"); | |||||
} | |||||
public: | |||||
using format_specs = typename base::format_specs; | |||||
/** | |||||
\rst | |||||
Constructs an argument formatter object. | |||||
*buffer* is a reference to the output buffer and *specs* contains format | |||||
specifier information for standard argument types. | |||||
\endrst | |||||
*/ | |||||
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx) | |||||
: base(Range(iter), &specs, internal::locale_ref()), context_(ctx) {} | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
iterator operator()(T value) { | |||||
// MSVC2013 fails to compile separate overloads for bool and char_type so | |||||
// use std::is_same instead. | |||||
if (std::is_same<T, bool>::value) { | |||||
format_specs& fmt_specs = *this->specs(); | |||||
if (fmt_specs.type != 's') return base::operator()(value ? 1 : 0); | |||||
fmt_specs.type = 0; | |||||
this->write(value != 0); | |||||
} else if (std::is_same<T, char_type>::value) { | |||||
format_specs& fmt_specs = *this->specs(); | |||||
if (fmt_specs.type && fmt_specs.type != 'c') | |||||
return (*this)(static_cast<int>(value)); | |||||
fmt_specs.sign = sign::none; | |||||
fmt_specs.alt = false; | |||||
fmt_specs.align = align::right; | |||||
return base::operator()(value); | |||||
} else { | |||||
return base::operator()(value); | |||||
} | |||||
return this->out(); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||||
iterator operator()(T value) { | |||||
return base::operator()(value); | |||||
} | |||||
/** Formats a null-terminated C string. */ | |||||
iterator operator()(const char* value) { | |||||
if (value) | |||||
base::operator()(value); | |||||
else if (this->specs()->type == 'p') | |||||
write_null_pointer(char_type()); | |||||
else | |||||
this->write("(null)"); | |||||
return this->out(); | |||||
} | |||||
/** Formats a null-terminated wide C string. */ | |||||
iterator operator()(const wchar_t* value) { | |||||
if (value) | |||||
base::operator()(value); | |||||
else if (this->specs()->type == 'p') | |||||
write_null_pointer(char_type()); | |||||
else | |||||
this->write(L"(null)"); | |||||
return this->out(); | |||||
} | |||||
iterator operator()(basic_string_view<char_type> value) { | |||||
return base::operator()(value); | |||||
} | |||||
iterator operator()(monostate value) { return base::operator()(value); } | |||||
/** Formats a pointer. */ | |||||
iterator operator()(const void* value) { | |||||
if (value) return base::operator()(value); | |||||
this->specs()->type = 0; | |||||
write_null_pointer(char_type()); | |||||
return this->out(); | |||||
} | |||||
/** Formats an argument of a custom (user-defined) type. */ | |||||
iterator operator()(typename basic_format_arg<context_type>::handle handle) { | |||||
handle.format(context_.parse_context(), context_); | |||||
return this->out(); | |||||
} | |||||
}; | |||||
template <typename T> struct printf_formatter { | |||||
template <typename ParseContext> | |||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||||
return ctx.begin(); | |||||
} | |||||
template <typename FormatContext> | |||||
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) { | |||||
internal::format_value(internal::get_container(ctx.out()), value); | |||||
return ctx.out(); | |||||
} | |||||
}; | |||||
/** This template formats data and writes the output to a writer. */ | |||||
template <typename OutputIt, typename Char> class basic_printf_context { | |||||
public: | |||||
/** The character type for the output. */ | |||||
using char_type = Char; | |||||
using format_arg = basic_format_arg<basic_printf_context>; | |||||
template <typename T> using formatter_type = printf_formatter<T>; | |||||
private: | |||||
using format_specs = basic_format_specs<char_type>; | |||||
OutputIt out_; | |||||
basic_format_args<basic_printf_context> args_; | |||||
basic_parse_context<Char> parse_ctx_; | |||||
static void parse_flags(format_specs& specs, const Char*& it, | |||||
const Char* end); | |||||
// Returns the argument with specified index or, if arg_index is equal | |||||
// to the maximum unsigned value, the next argument. | |||||
format_arg get_arg(unsigned arg_index = std::numeric_limits<unsigned>::max()); | |||||
// Parses argument index, flags and width and returns the argument index. | |||||
unsigned parse_header(const Char*& it, const Char* end, format_specs& specs); | |||||
public: | |||||
/** | |||||
\rst | |||||
Constructs a ``printf_context`` object. References to the arguments and | |||||
the writer are stored in the context object so make sure they have | |||||
appropriate lifetimes. | |||||
\endrst | |||||
*/ | |||||
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str, | |||||
basic_format_args<basic_printf_context> args) | |||||
: out_(out), args_(args), parse_ctx_(format_str) {} | |||||
OutputIt out() { return out_; } | |||||
void advance_to(OutputIt it) { out_ = it; } | |||||
format_arg arg(unsigned id) const { return args_.get(id); } | |||||
basic_parse_context<Char>& parse_context() { return parse_ctx_; } | |||||
FMT_CONSTEXPR void on_error(const char* message) { | |||||
parse_ctx_.on_error(message); | |||||
} | |||||
/** Formats stored arguments and writes the output to the range. */ | |||||
template <typename ArgFormatter = | |||||
printf_arg_formatter<internal::buffer_range<Char>>> | |||||
OutputIt format(); | |||||
}; | |||||
template <typename OutputIt, typename Char> | |||||
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs, | |||||
const Char*& it, | |||||
const Char* end) { | |||||
for (; it != end; ++it) { | |||||
switch (*it) { | |||||
case '-': | |||||
specs.align = align::left; | |||||
break; | |||||
case '+': | |||||
specs.sign = sign::plus; | |||||
break; | |||||
case '0': | |||||
specs.fill[0] = '0'; | |||||
break; | |||||
case ' ': | |||||
specs.sign = sign::space; | |||||
break; | |||||
case '#': | |||||
specs.alt = true; | |||||
break; | |||||
default: | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
template <typename OutputIt, typename Char> | |||||
typename basic_printf_context<OutputIt, Char>::format_arg | |||||
basic_printf_context<OutputIt, Char>::get_arg(unsigned arg_index) { | |||||
if (arg_index == std::numeric_limits<unsigned>::max()) | |||||
arg_index = parse_ctx_.next_arg_id(); | |||||
else | |||||
parse_ctx_.check_arg_id(--arg_index); | |||||
return internal::get_arg(*this, arg_index); | |||||
} | |||||
template <typename OutputIt, typename Char> | |||||
unsigned basic_printf_context<OutputIt, Char>::parse_header( | |||||
const Char*& it, const Char* end, format_specs& specs) { | |||||
unsigned arg_index = std::numeric_limits<unsigned>::max(); | |||||
char_type c = *it; | |||||
if (c >= '0' && c <= '9') { | |||||
// Parse an argument index (if followed by '$') or a width possibly | |||||
// preceded with '0' flag(s). | |||||
internal::error_handler eh; | |||||
unsigned value = parse_nonnegative_int(it, end, eh); | |||||
if (it != end && *it == '$') { // value is an argument index | |||||
++it; | |||||
arg_index = value; | |||||
} else { | |||||
if (c == '0') specs.fill[0] = '0'; | |||||
if (value != 0) { | |||||
// Nonzero value means that we parsed width and don't need to | |||||
// parse it or flags again, so return now. | |||||
specs.width = value; | |||||
return arg_index; | |||||
} | |||||
} | |||||
} | |||||
parse_flags(specs, it, end); | |||||
// Parse width. | |||||
if (it != end) { | |||||
if (*it >= '0' && *it <= '9') { | |||||
internal::error_handler eh; | |||||
specs.width = parse_nonnegative_int(it, end, eh); | |||||
} else if (*it == '*') { | |||||
++it; | |||||
specs.width = visit_format_arg( | |||||
internal::printf_width_handler<char_type>(specs), get_arg()); | |||||
} | |||||
} | |||||
return arg_index; | |||||
} | |||||
template <typename OutputIt, typename Char> | |||||
template <typename ArgFormatter> | |||||
OutputIt basic_printf_context<OutputIt, Char>::format() { | |||||
auto out = this->out(); | |||||
const Char* start = parse_ctx_.begin(); | |||||
const Char* end = parse_ctx_.end(); | |||||
auto it = start; | |||||
while (it != end) { | |||||
char_type c = *it++; | |||||
if (c != '%') continue; | |||||
if (it != end && *it == c) { | |||||
out = std::copy(start, it, out); | |||||
start = ++it; | |||||
continue; | |||||
} | |||||
out = std::copy(start, it - 1, out); | |||||
format_specs specs; | |||||
specs.align = align::right; | |||||
// Parse argument index, flags and width. | |||||
unsigned arg_index = parse_header(it, end, specs); | |||||
// Parse precision. | |||||
if (it != end && *it == '.') { | |||||
++it; | |||||
c = it != end ? *it : 0; | |||||
if ('0' <= c && c <= '9') { | |||||
internal::error_handler eh; | |||||
specs.precision = static_cast<int>(parse_nonnegative_int(it, end, eh)); | |||||
} else if (c == '*') { | |||||
++it; | |||||
specs.precision = | |||||
visit_format_arg(internal::printf_precision_handler(), get_arg()); | |||||
} else { | |||||
specs.precision = 0; | |||||
} | |||||
} | |||||
format_arg arg = get_arg(arg_index); | |||||
if (specs.alt && visit_format_arg(internal::is_zero_int(), arg)) | |||||
specs.alt = false; | |||||
if (specs.fill[0] == '0') { | |||||
if (arg.is_arithmetic()) | |||||
specs.align = align::numeric; | |||||
else | |||||
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types. | |||||
} | |||||
// Parse length and convert the argument to the required type. | |||||
c = it != end ? *it++ : 0; | |||||
char_type t = it != end ? *it : 0; | |||||
using internal::convert_arg; | |||||
switch (c) { | |||||
case 'h': | |||||
if (t == 'h') { | |||||
++it; | |||||
t = it != end ? *it : 0; | |||||
convert_arg<signed char>(arg, t); | |||||
} else { | |||||
convert_arg<short>(arg, t); | |||||
} | |||||
break; | |||||
case 'l': | |||||
if (t == 'l') { | |||||
++it; | |||||
t = it != end ? *it : 0; | |||||
convert_arg<long long>(arg, t); | |||||
} else { | |||||
convert_arg<long>(arg, t); | |||||
} | |||||
break; | |||||
case 'j': | |||||
convert_arg<intmax_t>(arg, t); | |||||
break; | |||||
case 'z': | |||||
convert_arg<std::size_t>(arg, t); | |||||
break; | |||||
case 't': | |||||
convert_arg<std::ptrdiff_t>(arg, t); | |||||
break; | |||||
case 'L': | |||||
// printf produces garbage when 'L' is omitted for long double, no | |||||
// need to do the same. | |||||
break; | |||||
default: | |||||
--it; | |||||
convert_arg<void>(arg, c); | |||||
} | |||||
// Parse type. | |||||
if (it == end) FMT_THROW(format_error("invalid format string")); | |||||
specs.type = static_cast<char>(*it++); | |||||
if (arg.is_integral()) { | |||||
// Normalize type. | |||||
switch (specs.type) { | |||||
case 'i': | |||||
case 'u': | |||||
specs.type = 'd'; | |||||
break; | |||||
case 'c': | |||||
visit_format_arg(internal::char_converter<basic_printf_context>(arg), | |||||
arg); | |||||
break; | |||||
} | |||||
} | |||||
start = it; | |||||
// Format argument. | |||||
visit_format_arg(ArgFormatter(out, specs, *this), arg); | |||||
} | |||||
return std::copy(start, it, out); | |||||
} | |||||
template <typename Char> | |||||
using basic_printf_context_t = | |||||
basic_printf_context<std::back_insert_iterator<internal::buffer<Char>>, | |||||
Char>; | |||||
using printf_context = basic_printf_context_t<char>; | |||||
using wprintf_context = basic_printf_context_t<wchar_t>; | |||||
using printf_args = basic_format_args<printf_context>; | |||||
using wprintf_args = basic_format_args<wprintf_context>; | |||||
/** | |||||
\rst | |||||
Constructs an `~fmt::format_arg_store` object that contains references to | |||||
arguments and can be implicitly converted to `~fmt::printf_args`. | |||||
\endrst | |||||
*/ | |||||
template <typename... Args> | |||||
inline format_arg_store<printf_context, Args...> make_printf_args( | |||||
const Args&... args) { | |||||
return {args...}; | |||||
} | |||||
/** | |||||
\rst | |||||
Constructs an `~fmt::format_arg_store` object that contains references to | |||||
arguments and can be implicitly converted to `~fmt::wprintf_args`. | |||||
\endrst | |||||
*/ | |||||
template <typename... Args> | |||||
inline format_arg_store<wprintf_context, Args...> make_wprintf_args( | |||||
const Args&... args) { | |||||
return {args...}; | |||||
} | |||||
template <typename S, typename Char = char_t<S>> | |||||
inline std::basic_string<Char> vsprintf( | |||||
const S& format, basic_format_args<basic_printf_context_t<Char>> args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
printf(buffer, to_string_view(format), args); | |||||
return to_string(buffer); | |||||
} | |||||
/** | |||||
\rst | |||||
Formats arguments and returns the result as a string. | |||||
**Example**:: | |||||
std::string message = fmt::sprintf("The answer is %d", 42); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>> | |||||
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) { | |||||
using context = basic_printf_context_t<Char>; | |||||
return vsprintf(to_string_view(format), {make_format_args<context>(args...)}); | |||||
} | |||||
template <typename S, typename Char = char_t<S>> | |||||
inline int vfprintf(std::FILE* f, const S& format, | |||||
basic_format_args<basic_printf_context_t<Char>> args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
printf(buffer, to_string_view(format), args); | |||||
std::size_t size = buffer.size(); | |||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size | |||||
? -1 | |||||
: static_cast<int>(size); | |||||
} | |||||
/** | |||||
\rst | |||||
Prints formatted data to the file *f*. | |||||
**Example**:: | |||||
fmt::fprintf(stderr, "Don't %s!", "panic"); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>> | |||||
inline int fprintf(std::FILE* f, const S& format, const Args&... args) { | |||||
using context = basic_printf_context_t<Char>; | |||||
return vfprintf(f, to_string_view(format), | |||||
{make_format_args<context>(args...)}); | |||||
} | |||||
template <typename S, typename Char = char_t<S>> | |||||
inline int vprintf(const S& format, | |||||
basic_format_args<basic_printf_context_t<Char>> args) { | |||||
return vfprintf(stdout, to_string_view(format), args); | |||||
} | |||||
/** | |||||
\rst | |||||
Prints formatted data to ``stdout``. | |||||
**Example**:: | |||||
fmt::printf("Elapsed time: %.2f seconds", 1.23); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
FMT_ENABLE_IF(internal::is_string<S>::value)> | |||||
inline int printf(const S& format_str, const Args&... args) { | |||||
using context = basic_printf_context_t<char_t<S>>; | |||||
return vprintf(to_string_view(format_str), | |||||
{make_format_args<context>(args...)}); | |||||
} | |||||
template <typename S, typename Char = char_t<S>> | |||||
inline int vfprintf(std::basic_ostream<Char>& os, const S& format, | |||||
basic_format_args<basic_printf_context_t<Char>> args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
printf(buffer, to_string_view(format), args); | |||||
internal::write(os, buffer); | |||||
return static_cast<int>(buffer.size()); | |||||
} | |||||
/** Formats arguments and writes the output to the range. */ | |||||
template <typename ArgFormatter, typename Char, | |||||
typename Context = | |||||
basic_printf_context<typename ArgFormatter::iterator, Char>> | |||||
typename ArgFormatter::iterator vprintf(internal::buffer<Char>& out, | |||||
basic_string_view<Char> format_str, | |||||
basic_format_args<Context> args) { | |||||
typename ArgFormatter::iterator iter(out); | |||||
Context(iter, format_str, args).template format<ArgFormatter>(); | |||||
return iter; | |||||
} | |||||
/** | |||||
\rst | |||||
Prints formatted data to the stream *os*. | |||||
**Example**:: | |||||
fmt::fprintf(cerr, "Don't %s!", "panic"); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, typename Char = char_t<S>> | |||||
inline int fprintf(std::basic_ostream<Char>& os, const S& format_str, | |||||
const Args&... args) { | |||||
using context = basic_printf_context_t<Char>; | |||||
return vfprintf(os, to_string_view(format_str), | |||||
{make_format_args<context>(args...)}); | |||||
} | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_PRINTF_H_ |
@@ -0,0 +1,288 @@ | |||||
// Formatting library for C++ - experimental range support | |||||
// | |||||
// Copyright (c) 2012 - present, Victor Zverovich | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
// | |||||
// Copyright (c) 2018 - present, Remotion (Igor Schulz) | |||||
// All Rights Reserved | |||||
// {fmt} support for ranges, containers and types tuple interface. | |||||
#ifndef FMT_RANGES_H_ | |||||
#define FMT_RANGES_H_ | |||||
#include <type_traits> | |||||
#include "format.h" | |||||
// output only up to N items from the range. | |||||
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT | |||||
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256 | |||||
#endif | |||||
FMT_BEGIN_NAMESPACE | |||||
template <typename Char> struct formatting_base { | |||||
template <typename ParseContext> | |||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||||
return ctx.begin(); | |||||
} | |||||
}; | |||||
template <typename Char, typename Enable = void> | |||||
struct formatting_range : formatting_base<Char> { | |||||
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit = | |||||
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the | |||||
// range. | |||||
Char prefix; | |||||
Char delimiter; | |||||
Char postfix; | |||||
formatting_range() : prefix('{'), delimiter(','), postfix('}') {} | |||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; | |||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; | |||||
}; | |||||
template <typename Char, typename Enable = void> | |||||
struct formatting_tuple : formatting_base<Char> { | |||||
Char prefix; | |||||
Char delimiter; | |||||
Char postfix; | |||||
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {} | |||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; | |||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; | |||||
}; | |||||
namespace internal { | |||||
template <typename RangeT, typename OutputIterator> | |||||
OutputIterator copy(const RangeT& range, OutputIterator out) { | |||||
for (auto it = range.begin(), end = range.end(); it != end; ++it) | |||||
*out++ = *it; | |||||
return out; | |||||
} | |||||
template <typename OutputIterator> | |||||
OutputIterator copy(const char* str, OutputIterator out) { | |||||
while (*str) *out++ = *str++; | |||||
return out; | |||||
} | |||||
template <typename OutputIterator> | |||||
OutputIterator copy(char ch, OutputIterator out) { | |||||
*out++ = ch; | |||||
return out; | |||||
} | |||||
/// Return true value if T has std::string interface, like std::string_view. | |||||
template <typename T> class is_like_std_string { | |||||
template <typename U> | |||||
static auto check(U* p) | |||||
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int()); | |||||
template <typename> static void check(...); | |||||
public: | |||||
static FMT_CONSTEXPR_DECL const bool value = | |||||
is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value; | |||||
}; | |||||
template <typename Char> | |||||
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {}; | |||||
template <typename... Ts> struct conditional_helper {}; | |||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {}; | |||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800 | |||||
template <typename T> | |||||
struct is_range_< | |||||
T, conditional_t<false, | |||||
conditional_helper<decltype(std::declval<T>().begin()), | |||||
decltype(std::declval<T>().end())>, | |||||
void>> : std::true_type {}; | |||||
#endif | |||||
/// tuple_size and tuple_element check. | |||||
template <typename T> class is_tuple_like_ { | |||||
template <typename U> | |||||
static auto check(U* p) | |||||
-> decltype(std::tuple_size<U>::value, | |||||
(void)std::declval<typename std::tuple_element<0, U>::type>(), | |||||
int()); | |||||
template <typename> static void check(...); | |||||
public: | |||||
static FMT_CONSTEXPR_DECL const bool value = | |||||
!std::is_void<decltype(check<T>(nullptr))>::value; | |||||
}; | |||||
// Check for integer_sequence | |||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900 | |||||
template <typename T, T... N> | |||||
using integer_sequence = std::integer_sequence<T, N...>; | |||||
template <std::size_t... N> using index_sequence = std::index_sequence<N...>; | |||||
template <std::size_t N> | |||||
using make_index_sequence = std::make_index_sequence<N>; | |||||
#else | |||||
template <typename T, T... N> struct integer_sequence { | |||||
using value_type = T; | |||||
static FMT_CONSTEXPR std::size_t size() { return sizeof...(N); } | |||||
}; | |||||
template <std::size_t... N> | |||||
using index_sequence = integer_sequence<std::size_t, N...>; | |||||
template <typename T, std::size_t N, T... Ns> | |||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {}; | |||||
template <typename T, T... Ns> | |||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {}; | |||||
template <std::size_t N> | |||||
using make_index_sequence = make_integer_sequence<std::size_t, N>; | |||||
#endif | |||||
template <class Tuple, class F, size_t... Is> | |||||
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT { | |||||
using std::get; | |||||
// using free function get<I>(T) now. | |||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...}; | |||||
(void)_; // blocks warnings | |||||
} | |||||
template <class T> | |||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes( | |||||
T const&) { | |||||
return {}; | |||||
} | |||||
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) { | |||||
const auto indexes = get_indexes(tup); | |||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); | |||||
} | |||||
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string< | |||||
typename std::decay<Arg>::type>::value)> | |||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) { | |||||
return add_space ? " {}" : "{}"; | |||||
} | |||||
template <typename Arg, FMT_ENABLE_IF(is_like_std_string< | |||||
typename std::decay<Arg>::type>::value)> | |||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) { | |||||
return add_space ? " \"{}\"" : "\"{}\""; | |||||
} | |||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) { | |||||
return add_space ? " \"{}\"" : "\"{}\""; | |||||
} | |||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) { | |||||
return add_space ? L" \"{}\"" : L"\"{}\""; | |||||
} | |||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) { | |||||
return add_space ? " '{}'" : "'{}'"; | |||||
} | |||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) { | |||||
return add_space ? L" '{}'" : L"'{}'"; | |||||
} | |||||
} // namespace internal | |||||
template <typename T> struct is_tuple_like { | |||||
static FMT_CONSTEXPR_DECL const bool value = | |||||
internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value; | |||||
}; | |||||
template <typename TupleT, typename Char> | |||||
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> { | |||||
private: | |||||
// C++11 generic lambda for format() | |||||
template <typename FormatContext> struct format_each { | |||||
template <typename T> void operator()(const T& v) { | |||||
if (i > 0) { | |||||
if (formatting.add_prepostfix_space) { | |||||
*out++ = ' '; | |||||
} | |||||
out = internal::copy(formatting.delimiter, out); | |||||
} | |||||
out = format_to(out, | |||||
internal::format_str_quoted( | |||||
(formatting.add_delimiter_spaces && i > 0), v), | |||||
v); | |||||
++i; | |||||
} | |||||
formatting_tuple<Char>& formatting; | |||||
std::size_t& i; | |||||
typename std::add_lvalue_reference<decltype( | |||||
std::declval<FormatContext>().out())>::type out; | |||||
}; | |||||
public: | |||||
formatting_tuple<Char> formatting; | |||||
template <typename ParseContext> | |||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||||
return formatting.parse(ctx); | |||||
} | |||||
template <typename FormatContext = format_context> | |||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) { | |||||
auto out = ctx.out(); | |||||
std::size_t i = 0; | |||||
internal::copy(formatting.prefix, out); | |||||
internal::for_each(values, format_each<FormatContext>{formatting, i, out}); | |||||
if (formatting.add_prepostfix_space) { | |||||
*out++ = ' '; | |||||
} | |||||
internal::copy(formatting.postfix, out); | |||||
return ctx.out(); | |||||
} | |||||
}; | |||||
template <typename T, typename Char> struct is_range { | |||||
static FMT_CONSTEXPR_DECL const bool value = | |||||
internal::is_range_<T>::value && | |||||
!internal::is_like_std_string<T>::value && | |||||
!std::is_convertible<T, std::basic_string<Char>>::value; | |||||
}; | |||||
template <typename RangeT, typename Char> | |||||
struct formatter<RangeT, Char, | |||||
enable_if_t<fmt::is_range<RangeT, Char>::value>> { | |||||
formatting_range<Char> formatting; | |||||
template <typename ParseContext> | |||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||||
return formatting.parse(ctx); | |||||
} | |||||
template <typename FormatContext> | |||||
typename FormatContext::iterator format(const RangeT& values, | |||||
FormatContext& ctx) { | |||||
auto out = internal::copy(formatting.prefix, ctx.out()); | |||||
std::size_t i = 0; | |||||
for (auto it = values.begin(), end = values.end(); it != end; ++it) { | |||||
if (i > 0) { | |||||
if (formatting.add_prepostfix_space) *out++ = ' '; | |||||
out = internal::copy(formatting.delimiter, out); | |||||
} | |||||
out = format_to(out, | |||||
internal::format_str_quoted( | |||||
(formatting.add_delimiter_spaces && i > 0), *it), | |||||
*it); | |||||
if (++i > formatting.range_length_limit) { | |||||
out = format_to(out, " ... <other elements>"); | |||||
break; | |||||
} | |||||
} | |||||
if (formatting.add_prepostfix_space) *out++ = ' '; | |||||
return internal::copy(formatting.postfix, out); | |||||
} | |||||
}; | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_RANGES_H_ |
@@ -0,0 +1,293 @@ | |||||
/* | |||||
* For conversion between std::chrono::durations without undefined | |||||
* behaviour or erroneous results. | |||||
* This is a stripped down version of duration_cast, for inclusion in fmt. | |||||
* See https://github.com/pauldreik/safe_duration_cast | |||||
* | |||||
* Copyright Paul Dreik 2019 | |||||
* | |||||
* This file is licensed under the fmt license, see format.h | |||||
*/ | |||||
#include <chrono> | |||||
#include <cmath> | |||||
#include <limits> | |||||
#include <type_traits> | |||||
#include "format.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
namespace safe_duration_cast { | |||||
template <typename To, typename From, | |||||
FMT_ENABLE_IF(!std::is_same<From, To>::value && | |||||
std::numeric_limits<From>::is_signed == | |||||
std::numeric_limits<To>::is_signed)> | |||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { | |||||
ec = 0; | |||||
using F = std::numeric_limits<From>; | |||||
using T = std::numeric_limits<To>; | |||||
static_assert(F::is_integer, "From must be integral"); | |||||
static_assert(T::is_integer, "To must be integral"); | |||||
// A and B are both signed, or both unsigned. | |||||
if (F::digits <= T::digits) { | |||||
// From fits in To without any problem. | |||||
} else { | |||||
// From does not always fit in To, resort to a dynamic check. | |||||
if (from < T::min() || from > T::max()) { | |||||
// outside range. | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
} | |||||
return static_cast<To>(from); | |||||
} | |||||
/** | |||||
* converts From to To, without loss. If the dynamic value of from | |||||
* can't be converted to To without loss, ec is set. | |||||
*/ | |||||
template <typename To, typename From, | |||||
FMT_ENABLE_IF(!std::is_same<From, To>::value && | |||||
std::numeric_limits<From>::is_signed != | |||||
std::numeric_limits<To>::is_signed)> | |||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { | |||||
ec = 0; | |||||
using F = std::numeric_limits<From>; | |||||
using T = std::numeric_limits<To>; | |||||
static_assert(F::is_integer, "From must be integral"); | |||||
static_assert(T::is_integer, "To must be integral"); | |||||
if (F::is_signed && !T::is_signed) { | |||||
// From may be negative, not allowed! | |||||
if (from < 0) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
// From is positive. Can it always fit in To? | |||||
if (F::digits <= T::digits) { | |||||
// yes, From always fits in To. | |||||
} else { | |||||
// from may not fit in To, we have to do a dynamic check | |||||
if (from > static_cast<From>(T::max())) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
} | |||||
} | |||||
if (!F::is_signed && T::is_signed) { | |||||
// can from be held in To? | |||||
if (F::digits < T::digits) { | |||||
// yes, From always fits in To. | |||||
} else { | |||||
// from may not fit in To, we have to do a dynamic check | |||||
if (from > static_cast<From>(T::max())) { | |||||
// outside range. | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
} | |||||
} | |||||
// reaching here means all is ok for lossless conversion. | |||||
return static_cast<To>(from); | |||||
} // function | |||||
template <typename To, typename From, | |||||
FMT_ENABLE_IF(std::is_same<From, To>::value)> | |||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { | |||||
ec = 0; | |||||
return from; | |||||
} // function | |||||
// clang-format off | |||||
/** | |||||
* converts From to To if possible, otherwise ec is set. | |||||
* | |||||
* input | output | |||||
* ---------------------------------|--------------- | |||||
* NaN | NaN | |||||
* Inf | Inf | |||||
* normal, fits in output | converted (possibly lossy) | |||||
* normal, does not fit in output | ec is set | |||||
* subnormal | best effort | |||||
* -Inf | -Inf | |||||
*/ | |||||
// clang-format on | |||||
template <typename To, typename From, | |||||
FMT_ENABLE_IF(!std::is_same<From, To>::value)> | |||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { | |||||
ec = 0; | |||||
using T = std::numeric_limits<To>; | |||||
static_assert(std::is_floating_point<From>::value, "From must be floating"); | |||||
static_assert(std::is_floating_point<To>::value, "To must be floating"); | |||||
// catch the only happy case | |||||
if (std::isfinite(from)) { | |||||
if (from >= T::lowest() && from <= T::max()) { | |||||
return static_cast<To>(from); | |||||
} | |||||
// not within range. | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
// nan and inf will be preserved | |||||
return static_cast<To>(from); | |||||
} // function | |||||
template <typename To, typename From, | |||||
FMT_ENABLE_IF(std::is_same<From, To>::value)> | |||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { | |||||
ec = 0; | |||||
static_assert(std::is_floating_point<From>::value, "From must be floating"); | |||||
return from; | |||||
} | |||||
/** | |||||
* safe duration cast between integral durations | |||||
*/ | |||||
template <typename To, typename FromRep, typename FromPeriod, | |||||
FMT_ENABLE_IF(std::is_integral<FromRep>::value), | |||||
FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)> | |||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, | |||||
int& ec) { | |||||
using From = std::chrono::duration<FromRep, FromPeriod>; | |||||
ec = 0; | |||||
// the basic idea is that we need to convert from count() in the from type | |||||
// to count() in the To type, by multiplying it with this: | |||||
using Factor = std::ratio_divide<typename From::period, typename To::period>; | |||||
static_assert(Factor::num > 0, "num must be positive"); | |||||
static_assert(Factor::den > 0, "den must be positive"); | |||||
// the conversion is like this: multiply from.count() with Factor::num | |||||
// /Factor::den and convert it to To::rep, all this without | |||||
// overflow/underflow. let's start by finding a suitable type that can hold | |||||
// both To, From and Factor::num | |||||
using IntermediateRep = | |||||
typename std::common_type<typename From::rep, typename To::rep, | |||||
decltype(Factor::num)>::type; | |||||
// safe conversion to IntermediateRep | |||||
IntermediateRep count = | |||||
lossless_integral_conversion<IntermediateRep>(from.count(), ec); | |||||
if (ec) { | |||||
return {}; | |||||
} | |||||
// multiply with Factor::num without overflow or underflow | |||||
if (Factor::num != 1) { | |||||
constexpr auto max1 = | |||||
std::numeric_limits<IntermediateRep>::max() / Factor::num; | |||||
if (count > max1) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
constexpr auto min1 = | |||||
std::numeric_limits<IntermediateRep>::min() / Factor::num; | |||||
if (count < min1) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
count *= Factor::num; | |||||
} | |||||
// this can't go wrong, right? den>0 is checked earlier. | |||||
if (Factor::den != 1) { | |||||
count /= Factor::den; | |||||
} | |||||
// convert to the to type, safely | |||||
using ToRep = typename To::rep; | |||||
const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec); | |||||
if (ec) { | |||||
return {}; | |||||
} | |||||
return To{tocount}; | |||||
} | |||||
/** | |||||
* safe duration_cast between floating point durations | |||||
*/ | |||||
template <typename To, typename FromRep, typename FromPeriod, | |||||
FMT_ENABLE_IF(std::is_floating_point<FromRep>::value), | |||||
FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)> | |||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, | |||||
int& ec) { | |||||
using From = std::chrono::duration<FromRep, FromPeriod>; | |||||
ec = 0; | |||||
if (std::isnan(from.count())) { | |||||
// nan in, gives nan out. easy. | |||||
return To{std::numeric_limits<typename To::rep>::quiet_NaN()}; | |||||
} | |||||
// maybe we should also check if from is denormal, and decide what to do about | |||||
// it. | |||||
// +-inf should be preserved. | |||||
if (std::isinf(from.count())) { | |||||
return To{from.count()}; | |||||
} | |||||
// the basic idea is that we need to convert from count() in the from type | |||||
// to count() in the To type, by multiplying it with this: | |||||
using Factor = std::ratio_divide<typename From::period, typename To::period>; | |||||
static_assert(Factor::num > 0, "num must be positive"); | |||||
static_assert(Factor::den > 0, "den must be positive"); | |||||
// the conversion is like this: multiply from.count() with Factor::num | |||||
// /Factor::den and convert it to To::rep, all this without | |||||
// overflow/underflow. let's start by finding a suitable type that can hold | |||||
// both To, From and Factor::num | |||||
using IntermediateRep = | |||||
typename std::common_type<typename From::rep, typename To::rep, | |||||
decltype(Factor::num)>::type; | |||||
// force conversion of From::rep -> IntermediateRep to be safe, | |||||
// even if it will never happen be narrowing in this context. | |||||
IntermediateRep count = | |||||
safe_float_conversion<IntermediateRep>(from.count(), ec); | |||||
if (ec) { | |||||
return {}; | |||||
} | |||||
// multiply with Factor::num without overflow or underflow | |||||
if (Factor::num != 1) { | |||||
constexpr auto max1 = std::numeric_limits<IntermediateRep>::max() / | |||||
static_cast<IntermediateRep>(Factor::num); | |||||
if (count > max1) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() / | |||||
static_cast<IntermediateRep>(Factor::num); | |||||
if (count < min1) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
count *= static_cast<IntermediateRep>(Factor::num); | |||||
} | |||||
// this can't go wrong, right? den>0 is checked earlier. | |||||
if (Factor::den != 1) { | |||||
using common_t = typename std::common_type<IntermediateRep, intmax_t>::type; | |||||
count /= static_cast<common_t>(Factor::den); | |||||
} | |||||
// convert to the to type, safely | |||||
using ToRep = typename To::rep; | |||||
const ToRep tocount = safe_float_conversion<ToRep>(count, ec); | |||||
if (ec) { | |||||
return {}; | |||||
} | |||||
return To{tocount}; | |||||
} | |||||
} // namespace safe_duration_cast | |||||
FMT_END_NAMESPACE |
@@ -0,0 +1,27 @@ | |||||
// | |||||
// Copyright(c) 2016-2018 Gabi Melman. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
// | |||||
#pragma once | |||||
// | |||||
// Include a bundled header-only copy of fmtlib or an external one. | |||||
// By default spdlog include its own copy. | |||||
// | |||||
#if !defined(SPDLOG_FMT_EXTERNAL) | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#ifndef FMT_HEADER_ONLY | |||||
#define FMT_HEADER_ONLY | |||||
#endif | |||||
#endif | |||||
#ifndef FMT_USE_WINDOWS_H | |||||
#define FMT_USE_WINDOWS_H 0 | |||||
#endif | |||||
#include "bundled/core.h" | |||||
#include "bundled/format.h" | |||||
#else // SPDLOG_FMT_EXTERNAL is defined - use external fmtlib | |||||
#include <fmt/core.h> | |||||
#include <fmt/format.h> | |||||
#endif |
@@ -0,0 +1,18 @@ | |||||
// | |||||
// Copyright(c) 2016 Gabi Melman. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
// | |||||
#pragma once | |||||
// | |||||
// include bundled or external copy of fmtlib's ostream support | |||||
// | |||||
#if !defined(SPDLOG_FMT_EXTERNAL) | |||||
#ifndef FMT_HEADER_ONLY | |||||
#define FMT_HEADER_ONLY | |||||
#endif | |||||
#include "bundled/ostream.h" | |||||
#include "fmt.h" | |||||
#else | |||||
#include <fmt/ostream.h> | |||||
#endif |
@@ -0,0 +1,18 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/fmt/fmt.h> | |||||
#include <spdlog/details/log_msg.h> | |||||
namespace spdlog { | |||||
class formatter | |||||
{ | |||||
public: | |||||
virtual ~formatter() = default; | |||||
virtual void format(const details::log_msg &msg, memory_buf_t &dest) = 0; | |||||
virtual std::unique_ptr<formatter> clone() const = 0; | |||||
}; | |||||
} // namespace spdlog |
@@ -0,0 +1,252 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/logger.h> | |||||
#endif | |||||
#include <spdlog/sinks/sink.h> | |||||
#include <spdlog/details/backtracer.h> | |||||
#include <spdlog/details/pattern_formatter.h> | |||||
#include <cstdio> | |||||
namespace spdlog { | |||||
// public methods | |||||
SPDLOG_INLINE logger::logger(const logger &other) | |||||
: name_(other.name_) | |||||
, sinks_(other.sinks_) | |||||
, level_(other.level_.load(std::memory_order_relaxed)) | |||||
, flush_level_(other.flush_level_.load(std::memory_order_relaxed)) | |||||
, custom_err_handler_(other.custom_err_handler_) | |||||
, tracer_(other.tracer_) | |||||
{} | |||||
SPDLOG_INLINE logger::logger(logger &&other) SPDLOG_NOEXCEPT : name_(std::move(other.name_)), | |||||
sinks_(std::move(other.sinks_)), | |||||
level_(other.level_.load(std::memory_order_relaxed)), | |||||
flush_level_(other.flush_level_.load(std::memory_order_relaxed)), | |||||
custom_err_handler_(std::move(other.custom_err_handler_)), | |||||
tracer_(std::move(other.tracer_)) | |||||
{} | |||||
SPDLOG_INLINE logger &logger::operator=(logger other) SPDLOG_NOEXCEPT | |||||
{ | |||||
this->swap(other); | |||||
return *this; | |||||
} | |||||
SPDLOG_INLINE void logger::swap(spdlog::logger &other) SPDLOG_NOEXCEPT | |||||
{ | |||||
name_.swap(other.name_); | |||||
sinks_.swap(other.sinks_); | |||||
// swap level_ | |||||
auto other_level = other.level_.load(); | |||||
auto my_level = level_.exchange(other_level); | |||||
other.level_.store(my_level); | |||||
// swap flush level_ | |||||
other_level = other.flush_level_.load(); | |||||
my_level = flush_level_.exchange(other_level); | |||||
other.flush_level_.store(my_level); | |||||
custom_err_handler_.swap(other.custom_err_handler_); | |||||
std::swap(tracer_, other.tracer_); | |||||
} | |||||
SPDLOG_INLINE void swap(logger &a, logger &b) | |||||
{ | |||||
a.swap(b); | |||||
} | |||||
SPDLOG_INLINE void logger::set_level(level::level_enum log_level) | |||||
{ | |||||
level_.store(log_level); | |||||
} | |||||
SPDLOG_INLINE level::level_enum logger::level() const | |||||
{ | |||||
return static_cast<level::level_enum>(level_.load(std::memory_order_relaxed)); | |||||
} | |||||
SPDLOG_INLINE const std::string &logger::name() const | |||||
{ | |||||
return name_; | |||||
} | |||||
// set formatting for the sinks in this logger. | |||||
// each sink will get a separate instance of the formatter object. | |||||
SPDLOG_INLINE void logger::set_formatter(std::unique_ptr<formatter> f) | |||||
{ | |||||
for (auto it = sinks_.begin(); it != sinks_.end(); ++it) | |||||
{ | |||||
if (std::next(it) == sinks_.end()) | |||||
{ | |||||
// last element - we can be move it. | |||||
(*it)->set_formatter(std::move(f)); | |||||
} | |||||
else | |||||
{ | |||||
(*it)->set_formatter(f->clone()); | |||||
} | |||||
} | |||||
} | |||||
SPDLOG_INLINE void logger::set_pattern(std::string pattern, pattern_time_type time_type) | |||||
{ | |||||
auto new_formatter = details::make_unique<pattern_formatter>(std::move(pattern), time_type); | |||||
set_formatter(std::move(new_formatter)); | |||||
} | |||||
// create new backtrace sink and move to it all our child sinks | |||||
SPDLOG_INLINE void logger::enable_backtrace(size_t n_messages) | |||||
{ | |||||
tracer_.enable(n_messages); | |||||
} | |||||
// restore orig sinks and level and delete the backtrace sink | |||||
SPDLOG_INLINE void logger::disable_backtrace() | |||||
{ | |||||
tracer_.disable(); | |||||
} | |||||
SPDLOG_INLINE void logger::dump_backtrace() | |||||
{ | |||||
dump_backtrace_(); | |||||
} | |||||
// flush functions | |||||
SPDLOG_INLINE void logger::flush() | |||||
{ | |||||
flush_(); | |||||
} | |||||
SPDLOG_INLINE void logger::flush_on(level::level_enum log_level) | |||||
{ | |||||
flush_level_.store(log_level); | |||||
} | |||||
SPDLOG_INLINE level::level_enum logger::flush_level() const | |||||
{ | |||||
return static_cast<level::level_enum>(flush_level_.load(std::memory_order_relaxed)); | |||||
} | |||||
// sinks | |||||
SPDLOG_INLINE const std::vector<sink_ptr> &logger::sinks() const | |||||
{ | |||||
return sinks_; | |||||
} | |||||
SPDLOG_INLINE std::vector<sink_ptr> &logger::sinks() | |||||
{ | |||||
return sinks_; | |||||
} | |||||
// error handler | |||||
SPDLOG_INLINE void logger::set_error_handler(err_handler handler) | |||||
{ | |||||
custom_err_handler_ = std::move(handler); | |||||
} | |||||
// create new logger with same sinks and configuration. | |||||
SPDLOG_INLINE std::shared_ptr<logger> logger::clone(std::string logger_name) | |||||
{ | |||||
auto cloned = std::make_shared<logger>(*this); | |||||
cloned->name_ = std::move(logger_name); | |||||
return cloned; | |||||
} | |||||
// protected methods | |||||
SPDLOG_INLINE void logger::log_it_(const spdlog::details::log_msg &log_msg, bool log_enabled, bool traceback_enabled) | |||||
{ | |||||
if (log_enabled) | |||||
{ | |||||
sink_it_(log_msg); | |||||
} | |||||
if (traceback_enabled) | |||||
{ | |||||
tracer_.push_back(log_msg); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void logger::sink_it_(const details::log_msg &msg) | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
if (sink->should_log(msg.level)) | |||||
{ | |||||
SPDLOG_TRY | |||||
{ | |||||
sink->log(msg); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
} | |||||
if (should_flush_(msg)) | |||||
{ | |||||
flush_(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void logger::flush_() | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
SPDLOG_TRY | |||||
{ | |||||
sink->flush(); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
} | |||||
SPDLOG_INLINE void logger::dump_backtrace_() | |||||
{ | |||||
using details::log_msg; | |||||
if (tracer_.enabled()) | |||||
{ | |||||
sink_it_(log_msg{name(), level::info, "****************** Backtrace Start ******************"}); | |||||
tracer_.foreach_pop([this](const log_msg &msg) { this->sink_it_(msg); }); | |||||
sink_it_(log_msg{name(), level::info, "****************** Backtrace End ********************"}); | |||||
} | |||||
} | |||||
SPDLOG_INLINE bool logger::should_flush_(const details::log_msg &msg) | |||||
{ | |||||
auto flush_level = flush_level_.load(std::memory_order_relaxed); | |||||
return (msg.level >= flush_level) && (msg.level != level::off); | |||||
} | |||||
SPDLOG_INLINE void logger::err_handler_(const std::string &msg) | |||||
{ | |||||
if (custom_err_handler_) | |||||
{ | |||||
custom_err_handler_(msg); | |||||
} | |||||
else | |||||
{ | |||||
using std::chrono::system_clock; | |||||
static std::mutex mutex; | |||||
static std::chrono::system_clock::time_point last_report_time; | |||||
static size_t err_counter = 0; | |||||
std::lock_guard<std::mutex> lk{mutex}; | |||||
auto now = system_clock::now(); | |||||
err_counter++; | |||||
if (now - last_report_time < std::chrono::seconds(1)) | |||||
{ | |||||
return; | |||||
} | |||||
last_report_time = now; | |||||
auto tm_time = details::os::localtime(system_clock::to_time_t(now)); | |||||
char date_buf[64]; | |||||
std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time); | |||||
fprintf(stderr, "[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, name().c_str(), msg.c_str()); | |||||
} | |||||
} | |||||
} // namespace spdlog |
@@ -0,0 +1,374 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// Thread safe logger (except for set_error_handler()) | |||||
// Has name, log level, vector of std::shared sink pointers and formatter | |||||
// Upon each log write the logger: | |||||
// 1. Checks if its log level is enough to log the message and if yes: | |||||
// 2. Call the underlying sinks to do the job. | |||||
// 3. Each sink use its own private copy of a formatter to format the message | |||||
// and send to its destination. | |||||
// | |||||
// The use of private formatter per sink provides the opportunity to cache some | |||||
// formatted data, and support for different format per sink. | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/log_msg.h> | |||||
#include <spdlog/details/backtracer.h> | |||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
#include <spdlog/details/os.h> | |||||
#endif | |||||
#include <vector> | |||||
#ifndef SPDLOG_NO_EXCEPTIONS | |||||
#define SPDLOG_LOGGER_CATCH() \ | |||||
catch (const std::exception &ex) \ | |||||
{ \ | |||||
err_handler_(ex.what()); \ | |||||
} \ | |||||
catch (...) \ | |||||
{ \ | |||||
err_handler_("Unknown exception in logger"); \ | |||||
} | |||||
#else | |||||
#define SPDLOG_LOGGER_CATCH() | |||||
#endif | |||||
namespace spdlog { | |||||
class logger | |||||
{ | |||||
public: | |||||
// Empty logger | |||||
explicit logger(std::string name) | |||||
: name_(std::move(name)) | |||||
, sinks_() | |||||
{} | |||||
// Logger with range on sinks | |||||
template<typename It> | |||||
logger(std::string name, It begin, It end) | |||||
: name_(std::move(name)) | |||||
, sinks_(begin, end) | |||||
{} | |||||
// Logger with single sink | |||||
logger(std::string name, sink_ptr single_sink) | |||||
: logger(std::move(name), {std::move(single_sink)}) | |||||
{} | |||||
// Logger with sinks init list | |||||
logger(std::string name, sinks_init_list sinks) | |||||
: logger(std::move(name), sinks.begin(), sinks.end()) | |||||
{} | |||||
virtual ~logger() = default; | |||||
logger(const logger &other); | |||||
logger(logger &&other) SPDLOG_NOEXCEPT; | |||||
logger &operator=(logger other) SPDLOG_NOEXCEPT; | |||||
void swap(spdlog::logger &other) SPDLOG_NOEXCEPT; | |||||
template<typename... Args> | |||||
void log(source_loc loc, level::level_enum lvl, string_view_t fmt, const Args &... args) | |||||
{ | |||||
bool log_enabled = should_log(lvl); | |||||
bool traceback_enabled = tracer_.enabled(); | |||||
if (!log_enabled && !traceback_enabled) | |||||
{ | |||||
return; | |||||
} | |||||
SPDLOG_TRY | |||||
{ | |||||
memory_buf_t buf; | |||||
fmt::format_to(buf, fmt, args...); | |||||
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||||
log_it_(log_msg, log_enabled, traceback_enabled); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
template<typename... Args> | |||||
void log(level::level_enum lvl, string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(source_loc{}, lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void trace(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::trace, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void debug(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::debug, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void info(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::info, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void warn(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::warn, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void error(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::err, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void critical(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::critical, fmt, args...); | |||||
} | |||||
template<typename T> | |||||
void log(level::level_enum lvl, const T &msg) | |||||
{ | |||||
log(source_loc{}, lvl, msg); | |||||
} | |||||
// T can be statically converted to string_view | |||||
template<class T, typename std::enable_if<std::is_convertible<const T &, spdlog::string_view_t>::value, T>::type * = nullptr> | |||||
void log(source_loc loc, level::level_enum lvl, const T &msg) | |||||
{ | |||||
bool log_enabled = should_log(lvl); | |||||
bool traceback_enabled = tracer_.enabled(); | |||||
if (!log_enabled && !traceback_enabled) | |||||
{ | |||||
return; | |||||
} | |||||
details::log_msg log_msg(loc, name_, lvl, msg); | |||||
log_it_(log_msg, log_enabled, traceback_enabled); | |||||
} | |||||
void log(level::level_enum lvl, string_view_t msg) | |||||
{ | |||||
log(source_loc{}, lvl, msg); | |||||
} | |||||
// T cannot be statically converted to string_view or wstring_view | |||||
template<class T, typename std::enable_if<!std::is_convertible<const T &, spdlog::string_view_t>::value && | |||||
!is_convertible_to_wstring_view<const T &>::value, | |||||
T>::type * = nullptr> | |||||
void log(source_loc loc, level::level_enum lvl, const T &msg) | |||||
{ | |||||
log(loc, lvl, "{}", msg); | |||||
} | |||||
template<typename T> | |||||
void trace(const T &msg) | |||||
{ | |||||
log(level::trace, msg); | |||||
} | |||||
template<typename T> | |||||
void debug(const T &msg) | |||||
{ | |||||
log(level::debug, msg); | |||||
} | |||||
template<typename T> | |||||
void info(const T &msg) | |||||
{ | |||||
log(level::info, msg); | |||||
} | |||||
template<typename T> | |||||
void warn(const T &msg) | |||||
{ | |||||
log(level::warn, msg); | |||||
} | |||||
template<typename T> | |||||
void error(const T &msg) | |||||
{ | |||||
log(level::err, msg); | |||||
} | |||||
template<typename T> | |||||
void critical(const T &msg) | |||||
{ | |||||
log(level::critical, msg); | |||||
} | |||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
#ifndef _WIN32 | |||||
#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows | |||||
#else | |||||
template<typename... Args> | |||||
void log(source_loc loc, level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
bool log_enabled = should_log(lvl); | |||||
bool traceback_enabled = tracer_.enabled(); | |||||
if (!log_enabled && !traceback_enabled) | |||||
{ | |||||
return; | |||||
} | |||||
SPDLOG_TRY | |||||
{ | |||||
// format to wmemory_buffer and convert to utf8 | |||||
fmt::wmemory_buffer wbuf; | |||||
fmt::format_to(wbuf, fmt, args...); | |||||
memory_buf_t buf; | |||||
details::os::wstr_to_utf8buf(wstring_view_t(wbuf.data(), wbuf.size()), buf); | |||||
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||||
log_it_(log_msg, log_enabled, traceback_enabled); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
template<typename... Args> | |||||
void log(level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(source_loc{}, lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void trace(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::trace, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void debug(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::debug, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void info(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::info, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void warn(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::warn, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void error(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::err, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void critical(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::critical, fmt, args...); | |||||
} | |||||
// T can be statically converted to wstring_view | |||||
template<class T, typename std::enable_if<is_convertible_to_wstring_view<const T &>::value, T>::type * = nullptr> | |||||
void log(source_loc loc, level::level_enum lvl, const T &msg) | |||||
{ | |||||
bool log_enabled = should_log(lvl); | |||||
bool traceback_enabled = tracer_.enabled(); | |||||
if (!log_enabled && !traceback_enabled) | |||||
{ | |||||
return; | |||||
} | |||||
SPDLOG_TRY | |||||
{ | |||||
memory_buf_t buf; | |||||
details::os::wstr_to_utf8buf(msg, buf); | |||||
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||||
log_it_(log_msg, log_enabled, traceback_enabled); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
#endif // _WIN32 | |||||
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
// return true logging is enabled for the given level. | |||||
bool should_log(level::level_enum msg_level) const | |||||
{ | |||||
return msg_level >= level_.load(std::memory_order_relaxed); | |||||
} | |||||
// return true if backtrace logging is enabled. | |||||
bool should_backtrace() const | |||||
{ | |||||
return tracer_.enabled(); | |||||
} | |||||
void set_level(level::level_enum log_level); | |||||
level::level_enum level() const; | |||||
const std::string &name() const; | |||||
// set formatting for the sinks in this logger. | |||||
// each sink will get a separate instance of the formatter object. | |||||
void set_formatter(std::unique_ptr<formatter> f); | |||||
void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local); | |||||
// backtrace support. | |||||
// efficiently store all debug/trace messages in a circular buffer until needed for debugging. | |||||
void enable_backtrace(size_t n_messages); | |||||
void disable_backtrace(); | |||||
void dump_backtrace(); | |||||
// flush functions | |||||
void flush(); | |||||
void flush_on(level::level_enum log_level); | |||||
level::level_enum flush_level() const; | |||||
// sinks | |||||
const std::vector<sink_ptr> &sinks() const; | |||||
std::vector<sink_ptr> &sinks(); | |||||
// error handler | |||||
void set_error_handler(err_handler); | |||||
// create new logger with same sinks and configuration. | |||||
virtual std::shared_ptr<logger> clone(std::string logger_name); | |||||
protected: | |||||
std::string name_; | |||||
std::vector<sink_ptr> sinks_; | |||||
spdlog::level_t level_{level::info}; | |||||
spdlog::level_t flush_level_{level::off}; | |||||
err_handler custom_err_handler_{nullptr}; | |||||
details::backtracer tracer_; | |||||
// log the given message (if the given log level is high enough), | |||||
// and save backtrace (if backtrace is enabled). | |||||
void log_it_(const details::log_msg &log_msg, bool log_enabled, bool traceback_enabled); | |||||
virtual void sink_it_(const details::log_msg &msg); | |||||
virtual void flush_(); | |||||
void dump_backtrace_(); | |||||
bool should_flush_(const details::log_msg &msg); | |||||
// handle errors during logging. | |||||
// default handler prints the error to stderr at max rate of 1 message/sec. | |||||
void err_handler_(const std::string &msg); | |||||
}; | |||||
void swap(logger &a, logger &b); | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "logger-inl.h" | |||||
#endif |
@@ -0,0 +1,119 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifdef __ANDROID__ | |||||
#include <spdlog/details/fmt_helper.h> | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/details/os.h> | |||||
#include <spdlog/sinks/base_sink.h> | |||||
#include <spdlog/details/synchronous_factory.h> | |||||
#include <android/log.h> | |||||
#include <chrono> | |||||
#include <mutex> | |||||
#include <string> | |||||
#include <thread> | |||||
#if !defined(SPDLOG_ANDROID_RETRIES) | |||||
#define SPDLOG_ANDROID_RETRIES 2 | |||||
#endif | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/* | |||||
* Android sink (logging using __android_log_write) | |||||
*/ | |||||
template<typename Mutex> | |||||
class android_sink final : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
explicit android_sink(std::string tag = "spdlog", bool use_raw_msg = false) | |||||
: tag_(std::move(tag)) | |||||
, use_raw_msg_(use_raw_msg) | |||||
{} | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
const android_LogPriority priority = convert_to_android_(msg.level); | |||||
memory_buf_t formatted; | |||||
if (use_raw_msg_) | |||||
{ | |||||
details::fmt_helper::append_string_view(msg.payload, formatted); | |||||
} | |||||
else | |||||
{ | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
} | |||||
formatted.push_back('\0'); | |||||
const char *msg_output = formatted.data(); | |||||
// See system/core/liblog/logger_write.c for explanation of return value | |||||
int ret = __android_log_write(priority, tag_.c_str(), msg_output); | |||||
int retry_count = 0; | |||||
while ((ret == -11 /*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES)) | |||||
{ | |||||
details::os::sleep_for_millis(5); | |||||
ret = __android_log_write(priority, tag_.c_str(), msg_output); | |||||
retry_count++; | |||||
} | |||||
if (ret < 0) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("__android_log_write() failed", ret)); | |||||
} | |||||
} | |||||
void flush_() override {} | |||||
private: | |||||
static android_LogPriority convert_to_android_(spdlog::level::level_enum level) | |||||
{ | |||||
switch (level) | |||||
{ | |||||
case spdlog::level::trace: | |||||
return ANDROID_LOG_VERBOSE; | |||||
case spdlog::level::debug: | |||||
return ANDROID_LOG_DEBUG; | |||||
case spdlog::level::info: | |||||
return ANDROID_LOG_INFO; | |||||
case spdlog::level::warn: | |||||
return ANDROID_LOG_WARN; | |||||
case spdlog::level::err: | |||||
return ANDROID_LOG_ERROR; | |||||
case spdlog::level::critical: | |||||
return ANDROID_LOG_FATAL; | |||||
default: | |||||
return ANDROID_LOG_DEFAULT; | |||||
} | |||||
} | |||||
std::string tag_; | |||||
bool use_raw_msg_; | |||||
}; | |||||
using android_sink_mt = android_sink<std::mutex>; | |||||
using android_sink_st = android_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// Create and register android syslog logger | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> android_logger_mt(const std::string &logger_name, const std::string &tag = "spdlog") | |||||
{ | |||||
return Factory::template create<sinks::android_sink_mt>(logger_name, tag); | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> android_logger_st(const std::string &logger_name, const std::string &tag = "spdlog") | |||||
{ | |||||
return Factory::template create<sinks::android_sink_st>(logger_name, tag); | |||||
} | |||||
} // namespace spdlog | |||||
#endif // __ANDROID__ |
@@ -0,0 +1,136 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/sinks/ansicolor_sink.h> | |||||
#endif | |||||
#include <spdlog/details/pattern_formatter.h> | |||||
#include <spdlog/details/os.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE ansicolor_sink<ConsoleMutex>::ansicolor_sink(FILE *target_file, color_mode mode) | |||||
: target_file_(target_file) | |||||
, mutex_(ConsoleMutex::mutex()) | |||||
, formatter_(details::make_unique<spdlog::pattern_formatter>()) | |||||
{ | |||||
set_color_mode(mode); | |||||
colors_[level::trace] = white; | |||||
colors_[level::debug] = cyan; | |||||
colors_[level::info] = green; | |||||
colors_[level::warn] = yellow_bold; | |||||
colors_[level::err] = red_bold; | |||||
colors_[level::critical] = bold_on_red; | |||||
colors_[level::off] = reset; | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color(level::level_enum color_level, string_view_t color) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
colors_[color_level] = color; | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::log(const details::log_msg &msg) | |||||
{ | |||||
// Wrap the originally formatted message in color codes. | |||||
// If color is not supported in the terminal, log as is instead. | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
memory_buf_t formatted; | |||||
formatter_->format(msg, formatted); | |||||
if (should_do_colors_ && msg.color_range_end > msg.color_range_start) | |||||
{ | |||||
// before color range | |||||
print_range_(formatted, 0, msg.color_range_start); | |||||
// in color range | |||||
print_ccode_(colors_[msg.level]); | |||||
print_range_(formatted, msg.color_range_start, msg.color_range_end); | |||||
print_ccode_(reset); | |||||
// after color range | |||||
print_range_(formatted, msg.color_range_end, formatted.size()); | |||||
} | |||||
else // no color | |||||
{ | |||||
print_range_(formatted, 0, formatted.size()); | |||||
} | |||||
fflush(target_file_); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::flush() | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
fflush(target_file_); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_pattern(const std::string &pattern) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::move(sink_formatter); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE bool ansicolor_sink<ConsoleMutex>::should_color() | |||||
{ | |||||
return should_do_colors_; | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color_mode(color_mode mode) | |||||
{ | |||||
switch (mode) | |||||
{ | |||||
case color_mode::always: | |||||
should_do_colors_ = true; | |||||
return; | |||||
case color_mode::automatic: | |||||
should_do_colors_ = details::os::in_terminal(target_file_) && details::os::is_color_terminal(); | |||||
return; | |||||
case color_mode::never: | |||||
should_do_colors_ = false; | |||||
return; | |||||
} | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_ccode_(const string_view_t &color_code) | |||||
{ | |||||
fwrite(color_code.data(), sizeof(string_view_t::char_type), color_code.size(), target_file_); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_range_(const memory_buf_t &formatted, size_t start, size_t end) | |||||
{ | |||||
fwrite(formatted.data() + start, sizeof(char), end - start, target_file_); | |||||
} | |||||
// ansicolor_stdout_sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE ansicolor_stdout_sink<ConsoleMutex>::ansicolor_stdout_sink(color_mode mode) | |||||
: ansicolor_sink<ConsoleMutex>(stdout, mode) | |||||
{} | |||||
// ansicolor_stderr_sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE ansicolor_stderr_sink<ConsoleMutex>::ansicolor_stderr_sink(color_mode mode) | |||||
: ansicolor_sink<ConsoleMutex>(stderr, mode) | |||||
{} | |||||
} // namespace sinks | |||||
} // namespace spdlog |
@@ -0,0 +1,113 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/details/console_globals.h> | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/sinks/sink.h> | |||||
#include <memory> | |||||
#include <mutex> | |||||
#include <string> | |||||
#include <unordered_map> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/** | |||||
* This sink prefixes the output with an ANSI escape sequence color code | |||||
* depending on the severity | |||||
* of the message. | |||||
* If no color terminal detected, omit the escape codes. | |||||
*/ | |||||
template<typename ConsoleMutex> | |||||
class ansicolor_sink : public sink | |||||
{ | |||||
public: | |||||
using mutex_t = typename ConsoleMutex::mutex_t; | |||||
ansicolor_sink(FILE *target_file, color_mode mode); | |||||
~ansicolor_sink() override = default; | |||||
ansicolor_sink(const ansicolor_sink &other) = delete; | |||||
ansicolor_sink &operator=(const ansicolor_sink &other) = delete; | |||||
void set_color(level::level_enum color_level, string_view_t color); | |||||
void set_color_mode(color_mode mode); | |||||
bool should_color(); | |||||
void log(const details::log_msg &msg) override; | |||||
void flush() override; | |||||
void set_pattern(const std::string &pattern) final; | |||||
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override; | |||||
// Formatting codes | |||||
const string_view_t reset = "\033[m"; | |||||
const string_view_t bold = "\033[1m"; | |||||
const string_view_t dark = "\033[2m"; | |||||
const string_view_t underline = "\033[4m"; | |||||
const string_view_t blink = "\033[5m"; | |||||
const string_view_t reverse = "\033[7m"; | |||||
const string_view_t concealed = "\033[8m"; | |||||
const string_view_t clear_line = "\033[K"; | |||||
// Foreground colors | |||||
const string_view_t black = "\033[30m"; | |||||
const string_view_t red = "\033[31m"; | |||||
const string_view_t green = "\033[32m"; | |||||
const string_view_t yellow = "\033[33m"; | |||||
const string_view_t blue = "\033[34m"; | |||||
const string_view_t magenta = "\033[35m"; | |||||
const string_view_t cyan = "\033[36m"; | |||||
const string_view_t white = "\033[37m"; | |||||
/// Background colors | |||||
const string_view_t on_black = "\033[40m"; | |||||
const string_view_t on_red = "\033[41m"; | |||||
const string_view_t on_green = "\033[42m"; | |||||
const string_view_t on_yellow = "\033[43m"; | |||||
const string_view_t on_blue = "\033[44m"; | |||||
const string_view_t on_magenta = "\033[45m"; | |||||
const string_view_t on_cyan = "\033[46m"; | |||||
const string_view_t on_white = "\033[47m"; | |||||
/// Bold colors | |||||
const string_view_t yellow_bold = "\033[33m\033[1m"; | |||||
const string_view_t red_bold = "\033[31m\033[1m"; | |||||
const string_view_t bold_on_red = "\033[1m\033[41m"; | |||||
private: | |||||
FILE *target_file_; | |||||
mutex_t &mutex_; | |||||
bool should_do_colors_; | |||||
std::unique_ptr<spdlog::formatter> formatter_; | |||||
std::unordered_map<level::level_enum, string_view_t, level::level_hasher> colors_; | |||||
void print_ccode_(const string_view_t &color_code); | |||||
void print_range_(const memory_buf_t &formatted, size_t start, size_t end); | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class ansicolor_stdout_sink : public ansicolor_sink<ConsoleMutex> | |||||
{ | |||||
public: | |||||
explicit ansicolor_stdout_sink(color_mode mode = color_mode::automatic); | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class ansicolor_stderr_sink : public ansicolor_sink<ConsoleMutex> | |||||
{ | |||||
public: | |||||
explicit ansicolor_stderr_sink(color_mode mode = color_mode::automatic); | |||||
}; | |||||
using ansicolor_stdout_sink_mt = ansicolor_stdout_sink<details::console_mutex>; | |||||
using ansicolor_stdout_sink_st = ansicolor_stdout_sink<details::console_nullmutex>; | |||||
using ansicolor_stderr_sink_mt = ansicolor_stderr_sink<details::console_mutex>; | |||||
using ansicolor_stderr_sink_st = ansicolor_stderr_sink<details::console_nullmutex>; | |||||
} // namespace sinks | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "ansicolor_sink-inl.h" | |||||
#endif |
@@ -0,0 +1,63 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/sinks/base_sink.h> | |||||
#endif | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/pattern_formatter.h> | |||||
#include <memory> | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::base_sink() | |||||
: formatter_{details::make_unique<spdlog::pattern_formatter>()} | |||||
{} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::base_sink(std::unique_ptr<spdlog::formatter> formatter) | |||||
: formatter_{std::move(formatter)} | |||||
{} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::log(const details::log_msg &msg) | |||||
{ | |||||
std::lock_guard<Mutex> lock(mutex_); | |||||
sink_it_(msg); | |||||
} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::flush() | |||||
{ | |||||
std::lock_guard<Mutex> lock(mutex_); | |||||
flush_(); | |||||
} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_pattern(const std::string &pattern) | |||||
{ | |||||
std::lock_guard<Mutex> lock(mutex_); | |||||
set_pattern_(pattern); | |||||
} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
{ | |||||
std::lock_guard<Mutex> lock(mutex_); | |||||
set_formatter_(std::move(sink_formatter)); | |||||
} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_pattern_(const std::string &pattern) | |||||
{ | |||||
set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); | |||||
} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
{ | |||||
formatter_ = std::move(sink_formatter); | |||||
} |
@@ -0,0 +1,46 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// | |||||
// base sink templated over a mutex (either dummy or real) | |||||
// concrete implementation should override the sink_it_() and flush_() methods. | |||||
// locking is taken care of in this class - no locking needed by the | |||||
// implementers.. | |||||
// | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/log_msg.h> | |||||
#include <spdlog/sinks/sink.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
class base_sink : public sink | |||||
{ | |||||
public: | |||||
base_sink(); | |||||
explicit base_sink(std::unique_ptr<spdlog::formatter> formatter); | |||||
base_sink(const base_sink &) = delete; | |||||
base_sink &operator=(const base_sink &) = delete; | |||||
void log(const details::log_msg &msg) final; | |||||
void flush() final; | |||||
void set_pattern(const std::string &pattern) final; | |||||
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final; | |||||
protected: | |||||
// sink formatter | |||||
std::unique_ptr<spdlog::formatter> formatter_; | |||||
Mutex mutex_; | |||||
virtual void sink_it_(const details::log_msg &msg) = 0; | |||||
virtual void flush_() = 0; | |||||
virtual void set_pattern_(const std::string &pattern); | |||||
virtual void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter); | |||||
}; | |||||
} // namespace sinks | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "base_sink-inl.h" | |||||
#endif |
@@ -0,0 +1,43 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/sinks/basic_file_sink.h> | |||||
#endif | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/os.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE basic_file_sink<Mutex>::basic_file_sink(const filename_t &filename, bool truncate) | |||||
{ | |||||
file_helper_.open(filename, truncate); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE const filename_t &basic_file_sink<Mutex>::filename() const | |||||
{ | |||||
return file_helper_.filename(); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE void basic_file_sink<Mutex>::sink_it_(const details::log_msg &msg) | |||||
{ | |||||
memory_buf_t formatted; | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
file_helper_.write(formatted); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE void basic_file_sink<Mutex>::flush_() | |||||
{ | |||||
file_helper_.flush(); | |||||
} | |||||
} // namespace sinks | |||||
} // namespace spdlog |
@@ -0,0 +1,58 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/details/file_helper.h> | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/sinks/base_sink.h> | |||||
#include <spdlog/details/synchronous_factory.h> | |||||
#include <mutex> | |||||
#include <string> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/* | |||||
* Trivial file sink with single file as target | |||||
*/ | |||||
template<typename Mutex> | |||||
class basic_file_sink final : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
explicit basic_file_sink(const filename_t &filename, bool truncate = false); | |||||
const filename_t &filename() const; | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override; | |||||
void flush_() override; | |||||
private: | |||||
details::file_helper file_helper_; | |||||
}; | |||||
using basic_file_sink_mt = basic_file_sink<std::mutex>; | |||||
using basic_file_sink_st = basic_file_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// | |||||
// factory functions | |||||
// | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> basic_logger_mt(const std::string &logger_name, const filename_t &filename, bool truncate = false) | |||||
{ | |||||
return Factory::template create<sinks::basic_file_sink_mt>(logger_name, filename, truncate); | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> basic_logger_st(const std::string &logger_name, const filename_t &filename, bool truncate = false) | |||||
{ | |||||
return Factory::template create<sinks::basic_file_sink_st>(logger_name, filename, truncate); | |||||
} | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "basic_file_sink-inl.h" | |||||
#endif |
@@ -0,0 +1,180 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/file_helper.h> | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/fmt/fmt.h> | |||||
#include <spdlog/sinks/base_sink.h> | |||||
#include <spdlog/details/os.h> | |||||
#include <spdlog/details/synchronous_factory.h> | |||||
#include <chrono> | |||||
#include <cstdio> | |||||
#include <ctime> | |||||
#include <mutex> | |||||
#include <string> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/* | |||||
* Generator of daily log file names in format basename.YYYY-MM-DD.ext | |||||
*/ | |||||
struct daily_filename_calculator | |||||
{ | |||||
// Create filename for the form basename.YYYY-MM-DD | |||||
static filename_t calc_filename(const filename_t &filename, const tm &now_tm) | |||||
{ | |||||
filename_t basename, ext; | |||||
std::tie(basename, ext) = details::file_helper::split_by_extension(filename); | |||||
return fmt::format( | |||||
SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext); | |||||
} | |||||
}; | |||||
/* | |||||
* Rotating file sink based on date. | |||||
* If truncate != false , the created file will be truncated. | |||||
* If max_files > 0, retain only the last max_files and delete previous. | |||||
*/ | |||||
template<typename Mutex, typename FileNameCalc = daily_filename_calculator> | |||||
class daily_file_sink final : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
// create daily file sink which rotates on given time | |||||
daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false, uint16_t max_files = 0) | |||||
: base_filename_(std::move(base_filename)) | |||||
, rotation_h_(rotation_hour) | |||||
, rotation_m_(rotation_minute) | |||||
, truncate_(truncate) | |||||
, max_files_(max_files) | |||||
, filenames_q_() | |||||
{ | |||||
if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("daily_file_sink: Invalid rotation time in ctor")); | |||||
} | |||||
auto now = log_clock::now(); | |||||
auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now)); | |||||
file_helper_.open(filename, truncate_); | |||||
rotation_tp_ = next_rotation_tp_(); | |||||
if (max_files_ > 0) | |||||
{ | |||||
filenames_q_ = details::circular_q<filename_t>(static_cast<size_t>(max_files_)); | |||||
filenames_q_.push_back(std::move(filename)); | |||||
} | |||||
} | |||||
const filename_t &filename() const | |||||
{ | |||||
return file_helper_.filename(); | |||||
} | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
auto time = msg.time; | |||||
bool should_rotate = time >= rotation_tp_; | |||||
if (should_rotate) | |||||
{ | |||||
auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(time)); | |||||
file_helper_.open(filename, truncate_); | |||||
rotation_tp_ = next_rotation_tp_(); | |||||
} | |||||
memory_buf_t formatted; | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
file_helper_.write(formatted); | |||||
// Do the cleaning only at the end because it might throw on failure. | |||||
if (should_rotate && max_files_ > 0) | |||||
{ | |||||
delete_old_(); | |||||
} | |||||
} | |||||
void flush_() override | |||||
{ | |||||
file_helper_.flush(); | |||||
} | |||||
private: | |||||
tm now_tm(log_clock::time_point tp) | |||||
{ | |||||
time_t tnow = log_clock::to_time_t(tp); | |||||
return spdlog::details::os::localtime(tnow); | |||||
} | |||||
log_clock::time_point next_rotation_tp_() | |||||
{ | |||||
auto now = log_clock::now(); | |||||
tm date = now_tm(now); | |||||
date.tm_hour = rotation_h_; | |||||
date.tm_min = rotation_m_; | |||||
date.tm_sec = 0; | |||||
auto rotation_time = log_clock::from_time_t(std::mktime(&date)); | |||||
if (rotation_time > now) | |||||
{ | |||||
return rotation_time; | |||||
} | |||||
return {rotation_time + std::chrono::hours(24)}; | |||||
} | |||||
// Delete the file N rotations ago. | |||||
// Throw spdlog_ex on failure to delete the old file. | |||||
void delete_old_() | |||||
{ | |||||
using details::os::filename_to_str; | |||||
using details::os::remove_if_exists; | |||||
filename_t current_file = filename(); | |||||
if (filenames_q_.full()) | |||||
{ | |||||
auto old_filename = std::move(filenames_q_.front()); | |||||
filenames_q_.pop_front(); | |||||
bool ok = remove_if_exists(old_filename) == 0; | |||||
if (!ok) | |||||
{ | |||||
filenames_q_.push_back(std::move(current_file)); | |||||
SPDLOG_THROW(spdlog_ex("Failed removing daily file " + filename_to_str(old_filename), errno)); | |||||
} | |||||
} | |||||
filenames_q_.push_back(std::move(current_file)); | |||||
} | |||||
filename_t base_filename_; | |||||
int rotation_h_; | |||||
int rotation_m_; | |||||
log_clock::time_point rotation_tp_; | |||||
details::file_helper file_helper_; | |||||
bool truncate_; | |||||
uint16_t max_files_; | |||||
details::circular_q<filename_t> filenames_q_; | |||||
}; | |||||
using daily_file_sink_mt = daily_file_sink<std::mutex>; | |||||
using daily_file_sink_st = daily_file_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// | |||||
// factory functions | |||||
// | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> daily_logger_mt( | |||||
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) | |||||
{ | |||||
return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate); | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> daily_logger_st( | |||||
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) | |||||
{ | |||||
return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate); | |||||
} | |||||
} // namespace spdlog |
@@ -0,0 +1,93 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "base_sink.h" | |||||
#include <spdlog/details/log_msg.h> | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/details/pattern_formatter.h> | |||||
#include <algorithm> | |||||
#include <memory> | |||||
#include <mutex> | |||||
#include <vector> | |||||
// Distribution sink (mux). Stores a vector of sinks which get called when log | |||||
// is called | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
class dist_sink : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
dist_sink() = default; | |||||
dist_sink(const dist_sink &) = delete; | |||||
dist_sink &operator=(const dist_sink &) = delete; | |||||
void add_sink(std::shared_ptr<sink> sink) | |||||
{ | |||||
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
sinks_.push_back(sink); | |||||
} | |||||
void remove_sink(std::shared_ptr<sink> sink) | |||||
{ | |||||
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sink), sinks_.end()); | |||||
} | |||||
void set_sinks(std::vector<std::shared_ptr<sink>> sinks) | |||||
{ | |||||
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
sinks_ = std::move(sinks); | |||||
} | |||||
std::vector<std::shared_ptr<sink>> &sinks() | |||||
{ | |||||
return sinks_; | |||||
} | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
if (sink->should_log(msg.level)) | |||||
{ | |||||
sink->log(msg); | |||||
} | |||||
} | |||||
} | |||||
void flush_() override | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
sink->flush(); | |||||
} | |||||
} | |||||
void set_pattern_(const std::string &pattern) override | |||||
{ | |||||
set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); | |||||
} | |||||
void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) override | |||||
{ | |||||
base_sink<Mutex>::formatter_ = std::move(sink_formatter); | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
sink->set_formatter(base_sink<Mutex>::formatter_->clone()); | |||||
} | |||||
} | |||||
std::vector<std::shared_ptr<sink>> sinks_; | |||||
}; | |||||
using dist_sink_mt = dist_sink<std::mutex>; | |||||
using dist_sink_st = dist_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
} // namespace spdlog |
@@ -0,0 +1,90 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "dist_sink.h" | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/details/log_msg.h> | |||||
#include <mutex> | |||||
#include <string> | |||||
#include <chrono> | |||||
// Duplicate message removal sink. | |||||
// Skip the message if previous one is identical and less than "max_skip_duration" have passed | |||||
// | |||||
// Example: | |||||
// | |||||
// #include <spdlog/sinks/dup_filter_sink.h> | |||||
// | |||||
// int main() { | |||||
// auto dup_filter = std::make_shared<dup_filter_sink_st>(std::chrono::seconds(5)); | |||||
// dup_filter->add_sink(std::make_shared<stdout_color_sink_mt>()); | |||||
// spdlog::logger l("logger", dup_filter); | |||||
// l.info("Hello"); | |||||
// l.info("Hello"); | |||||
// l.info("Hello"); | |||||
// l.info("Different Hello"); | |||||
// } | |||||
// | |||||
// Will produce: | |||||
// [2019-06-25 17:50:56.511] [logger] [info] Hello | |||||
// [2019-06-25 17:50:56.512] [logger] [info] Skipped 3 duplicate messages.. | |||||
// [2019-06-25 17:50:56.512] [logger] [info] Different Hello | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
class dup_filter_sink : public dist_sink<Mutex> | |||||
{ | |||||
public: | |||||
template<class Rep, class Period> | |||||
explicit dup_filter_sink(std::chrono::duration<Rep, Period> max_skip_duration) | |||||
: max_skip_duration_{max_skip_duration} | |||||
{} | |||||
protected: | |||||
std::chrono::microseconds max_skip_duration_; | |||||
log_clock::time_point last_msg_time_; | |||||
std::string last_msg_payload_; | |||||
size_t skip_counter_ = 0; | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
bool filtered = filter_(msg); | |||||
if (!filtered) | |||||
{ | |||||
skip_counter_ += 1; | |||||
return; | |||||
} | |||||
// log the "skipped.." message | |||||
if (skip_counter_ > 0) | |||||
{ | |||||
memory_buf_t buf; | |||||
fmt::format_to(buf, "Skipped {} duplicate messages..", skip_counter_); | |||||
details::log_msg skipped_msg{msg.logger_name, msg.level, string_view_t{buf.data(), buf.size()}}; | |||||
dist_sink<Mutex>::sink_it_(skipped_msg); | |||||
} | |||||
// log current message | |||||
dist_sink<Mutex>::sink_it_(msg); | |||||
last_msg_time_ = msg.time; | |||||
skip_counter_ = 0; | |||||
last_msg_payload_.assign(msg.payload.data(), msg.payload.data() + msg.payload.size()); | |||||
} | |||||
// return whether the log msg should be displayed (true) or skipped (false) | |||||
bool filter_(const details::log_msg &msg) | |||||
{ | |||||
auto filter_duration = msg.time - last_msg_time_; | |||||
return (filter_duration > max_skip_duration_) || (msg.payload != last_msg_payload_); | |||||
} | |||||
}; | |||||
using dup_filter_sink_mt = dup_filter_sink<std::mutex>; | |||||
using dup_filter_sink_st = dup_filter_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
} // namespace spdlog |
@@ -0,0 +1,48 @@ | |||||
// Copyright(c) 2016 Alexander Dalshov. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#if defined(_WIN32) | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/sinks/base_sink.h> | |||||
#include <winbase.h> | |||||
#include <mutex> | |||||
#include <string> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/* | |||||
* MSVC sink (logging using OutputDebugStringA) | |||||
*/ | |||||
template<typename Mutex> | |||||
class msvc_sink : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
explicit msvc_sink() {} | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
memory_buf_t formatted; | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
OutputDebugStringA(fmt::to_string(formatted).c_str()); | |||||
} | |||||
void flush_() override {} | |||||
}; | |||||
using msvc_sink_mt = msvc_sink<std::mutex>; | |||||
using msvc_sink_st = msvc_sink<details::null_mutex>; | |||||
using windebug_sink_mt = msvc_sink_mt; | |||||
using windebug_sink_st = msvc_sink_st; | |||||
} // namespace sinks | |||||
} // namespace spdlog | |||||
#endif |
@@ -0,0 +1,44 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/sinks/base_sink.h> | |||||
#include <spdlog/details/synchronous_factory.h> | |||||
#include <mutex> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
class null_sink : public base_sink<Mutex> | |||||
{ | |||||
protected: | |||||
void sink_it_(const details::log_msg &) override {} | |||||
void flush_() override {} | |||||
}; | |||||
using null_sink_mt = null_sink<details::null_mutex>; | |||||
using null_sink_st = null_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> null_logger_mt(const std::string &logger_name) | |||||
{ | |||||
auto null_logger = Factory::template create<sinks::null_sink_mt>(logger_name); | |||||
null_logger->set_level(level::off); | |||||
return null_logger; | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> null_logger_st(const std::string &logger_name) | |||||
{ | |||||
auto null_logger = Factory::template create<sinks::null_sink_st>(logger_name); | |||||
null_logger->set_level(level::off); | |||||
return null_logger; | |||||
} | |||||
} // namespace spdlog |
@@ -0,0 +1,50 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/sinks/base_sink.h> | |||||
#include <mutex> | |||||
#include <ostream> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
class ostream_sink final : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
explicit ostream_sink(std::ostream &os, bool force_flush = false) | |||||
: ostream_(os) | |||||
, force_flush_(force_flush) | |||||
{} | |||||
ostream_sink(const ostream_sink &) = delete; | |||||
ostream_sink &operator=(const ostream_sink &) = delete; | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
memory_buf_t formatted; | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
ostream_.write(formatted.data(), static_cast<std::streamsize>(formatted.size())); | |||||
if (force_flush_) | |||||
{ | |||||
ostream_.flush(); | |||||
} | |||||
} | |||||
void flush_() override | |||||
{ | |||||
ostream_.flush(); | |||||
} | |||||
std::ostream &ostream_; | |||||
bool force_flush_; | |||||
}; | |||||
using ostream_sink_mt = ostream_sink<std::mutex>; | |||||
using ostream_sink_st = ostream_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
} // namespace spdlog |
@@ -0,0 +1,72 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/sinks/base_sink.h" | |||||
#include "spdlog/details/circular_q.h" | |||||
#include "spdlog/details/log_msg_buffer.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include <mutex> | |||||
#include <string> | |||||
#include <vector> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/* | |||||
* Ring buffer sink | |||||
*/ | |||||
template<typename Mutex> | |||||
class ringbuffer_sink final : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
explicit ringbuffer_sink(size_t n_items) | |||||
: q_{n_items} | |||||
{} | |||||
std::vector<details::log_msg_buffer> last_raw(size_t lim = 0) | |||||
{ | |||||
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
auto n_items = lim > 0 ? (std::min)(lim, q_.size()) : q_.size(); | |||||
std::vector<details::log_msg_buffer> ret; | |||||
ret.reserve(n_items); | |||||
for (size_t i = 0; i < n_items; i++) | |||||
{ | |||||
ret.push_back(q_.at(i)); | |||||
} | |||||
return ret; | |||||
} | |||||
std::vector<std::string> last_formatted(size_t lim = 0) | |||||
{ | |||||
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
auto n_items = lim > 0 ? (std::min)(lim, q_.size()) : q_.size(); | |||||
std::vector<std::string> ret; | |||||
ret.reserve(n_items); | |||||
for (size_t i = 0; i < n_items; i++) | |||||
{ | |||||
memory_buf_t formatted; | |||||
base_sink<Mutex>::formatter_->format(q_.at(i), formatted); | |||||
ret.push_back(fmt::to_string(formatted)); | |||||
} | |||||
return ret; | |||||
} | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
q_.push_back(details::log_msg_buffer{msg}); | |||||
} | |||||
void flush_() override {} | |||||
private: | |||||
details::circular_q<details::log_msg_buffer> q_; | |||||
}; | |||||
using ringbuffer_sink_mt = ringbuffer_sink<std::mutex>; | |||||
using ringbuffer_sink_st = ringbuffer_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
} // namespace spdlog |
@@ -0,0 +1,131 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/sinks/rotating_file_sink.h> | |||||
#endif | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/file_helper.h> | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/fmt/fmt.h> | |||||
#include <cerrno> | |||||
#include <chrono> | |||||
#include <ctime> | |||||
#include <mutex> | |||||
#include <string> | |||||
#include <tuple> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE rotating_file_sink<Mutex>::rotating_file_sink( | |||||
filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open) | |||||
: base_filename_(std::move(base_filename)) | |||||
, max_size_(max_size) | |||||
, max_files_(max_files) | |||||
{ | |||||
file_helper_.open(calc_filename(base_filename_, 0)); | |||||
current_size_ = file_helper_.size(); // expensive. called only once | |||||
if (rotate_on_open && current_size_ > 0) | |||||
{ | |||||
rotate_(); | |||||
} | |||||
} | |||||
// calc filename according to index and file extension if exists. | |||||
// e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::calc_filename(const filename_t &filename, std::size_t index) | |||||
{ | |||||
if (index == 0u) | |||||
{ | |||||
return filename; | |||||
} | |||||
filename_t basename, ext; | |||||
std::tie(basename, ext) = details::file_helper::split_by_extension(filename); | |||||
return fmt::format(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE const filename_t &rotating_file_sink<Mutex>::filename() const | |||||
{ | |||||
return file_helper_.filename(); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE void rotating_file_sink<Mutex>::sink_it_(const details::log_msg &msg) | |||||
{ | |||||
memory_buf_t formatted; | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
current_size_ += formatted.size(); | |||||
if (current_size_ > max_size_) | |||||
{ | |||||
rotate_(); | |||||
current_size_ = formatted.size(); | |||||
} | |||||
file_helper_.write(formatted); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE void rotating_file_sink<Mutex>::flush_() | |||||
{ | |||||
file_helper_.flush(); | |||||
} | |||||
// Rotate files: | |||||
// log.txt -> log.1.txt | |||||
// log.1.txt -> log.2.txt | |||||
// log.2.txt -> log.3.txt | |||||
// log.3.txt -> delete | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_() | |||||
{ | |||||
using details::os::filename_to_str; | |||||
using details::os::path_exists; | |||||
file_helper_.close(); | |||||
for (auto i = max_files_; i > 0; --i) | |||||
{ | |||||
filename_t src = calc_filename(base_filename_, i - 1); | |||||
if (!path_exists(src)) | |||||
{ | |||||
continue; | |||||
} | |||||
filename_t target = calc_filename(base_filename_, i); | |||||
if (!rename_file(src, target)) | |||||
{ | |||||
// if failed try again after a small delay. | |||||
// this is a workaround to a windows issue, where very high rotation | |||||
// rates can cause the rename to fail with permission denied (because of antivirus?). | |||||
details::os::sleep_for_millis(100); | |||||
if (!rename_file(src, target)) | |||||
{ | |||||
file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit! | |||||
current_size_ = 0; | |||||
SPDLOG_THROW( | |||||
spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno)); | |||||
} | |||||
} | |||||
} | |||||
file_helper_.reopen(true); | |||||
} | |||||
// delete the target if exists, and rename the src file to target | |||||
// return true on success, false otherwise. | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE bool rotating_file_sink<Mutex>::rename_file(const filename_t &src_filename, const filename_t &target_filename) | |||||
{ | |||||
// try to delete the target file in case it already exists. | |||||
(void)details::os::remove(target_filename); | |||||
return details::os::rename(src_filename, target_filename) == 0; | |||||
} | |||||
} // namespace sinks | |||||
} // namespace spdlog |
@@ -0,0 +1,78 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/sinks/base_sink.h> | |||||
#include <spdlog/details/file_helper.h> | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/details/synchronous_factory.h> | |||||
#include <chrono> | |||||
#include <mutex> | |||||
#include <string> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
// | |||||
// Rotating file sink based on size | |||||
// | |||||
template<typename Mutex> | |||||
class rotating_file_sink final : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open = false); | |||||
static filename_t calc_filename(const filename_t &filename, std::size_t index); | |||||
const filename_t &filename() const; | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override; | |||||
void flush_() override; | |||||
private: | |||||
// Rotate files: | |||||
// log.txt -> log.1.txt | |||||
// log.1.txt -> log.2.txt | |||||
// log.2.txt -> log.3.txt | |||||
// log.3.txt -> delete | |||||
void rotate_(); | |||||
// delete the target if exists, and rename the src file to target | |||||
// return true on success, false otherwise. | |||||
bool rename_file(const filename_t &src_filename, const filename_t &target_filename); | |||||
filename_t base_filename_; | |||||
std::size_t max_size_; | |||||
std::size_t max_files_; | |||||
std::size_t current_size_; | |||||
details::file_helper file_helper_; | |||||
}; | |||||
using rotating_file_sink_mt = rotating_file_sink<std::mutex>; | |||||
using rotating_file_sink_st = rotating_file_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// | |||||
// factory functions | |||||
// | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> rotating_logger_mt( | |||||
const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files, bool rotate_on_open = false) | |||||
{ | |||||
return Factory::template create<sinks::rotating_file_sink_mt>(logger_name, filename, max_file_size, max_files, rotate_on_open); | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> rotating_logger_st( | |||||
const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files, bool rotate_on_open = false) | |||||
{ | |||||
return Factory::template create<sinks::rotating_file_sink_st>(logger_name, filename, max_file_size, max_files, rotate_on_open); | |||||
} | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "rotating_file_sink-inl.h" | |||||
#endif |
@@ -0,0 +1,25 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/sinks/sink.h> | |||||
#endif | |||||
#include <spdlog/common.h> | |||||
SPDLOG_INLINE bool spdlog::sinks::sink::should_log(spdlog::level::level_enum msg_level) const | |||||
{ | |||||
return msg_level >= level_.load(std::memory_order_relaxed); | |||||
} | |||||
SPDLOG_INLINE void spdlog::sinks::sink::set_level(level::level_enum log_level) | |||||
{ | |||||
level_.store(log_level, std::memory_order_relaxed); | |||||
} | |||||
SPDLOG_INLINE spdlog::level::level_enum spdlog::sinks::sink::level() const | |||||
{ | |||||
return static_cast<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed)); | |||||
} |
@@ -0,0 +1,35 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/details/log_msg.h> | |||||
#include <spdlog/formatter.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
class sink | |||||
{ | |||||
public: | |||||
virtual ~sink() = default; | |||||
virtual void log(const details::log_msg &msg) = 0; | |||||
virtual void flush() = 0; | |||||
virtual void set_pattern(const std::string &pattern) = 0; | |||||
virtual void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) = 0; | |||||
void set_level(level::level_enum log_level); | |||||
level::level_enum level() const; | |||||
bool should_log(level::level_enum msg_level) const; | |||||
protected: | |||||
// sink log level - default is all | |||||
level_t level_{level::trace}; | |||||
}; | |||||
} // namespace sinks | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "sink-inl.h" | |||||
#endif |
@@ -0,0 +1,38 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/sinks/stdout_color_sinks.h> | |||||
#endif | |||||
#include <spdlog/logger.h> | |||||
#include <spdlog/common.h> | |||||
namespace spdlog { | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name, color_mode mode) | |||||
{ | |||||
return Factory::template create<sinks::stdout_color_sink_mt>(logger_name, mode); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stdout_color_st(const std::string &logger_name, color_mode mode) | |||||
{ | |||||
return Factory::template create<sinks::stdout_color_sink_st>(logger_name, mode); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name, color_mode mode) | |||||
{ | |||||
return Factory::template create<sinks::stderr_color_sink_mt>(logger_name, mode); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stderr_color_st(const std::string &logger_name, color_mode mode) | |||||
{ | |||||
return Factory::template create<sinks::stderr_color_sink_st>(logger_name, mode); | |||||
} | |||||
} // namespace spdlog |
@@ -0,0 +1,45 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifdef _WIN32 | |||||
#include <spdlog/sinks/wincolor_sink.h> | |||||
#else | |||||
#include <spdlog/sinks/ansicolor_sink.h> | |||||
#endif | |||||
#include <spdlog/details/synchronous_factory.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
#ifdef _WIN32 | |||||
using stdout_color_sink_mt = wincolor_stdout_sink_mt; | |||||
using stdout_color_sink_st = wincolor_stdout_sink_st; | |||||
using stderr_color_sink_mt = wincolor_stderr_sink_mt; | |||||
using stderr_color_sink_st = wincolor_stderr_sink_st; | |||||
#else | |||||
using stdout_color_sink_mt = ansicolor_stdout_sink_mt; | |||||
using stdout_color_sink_st = ansicolor_stdout_sink_st; | |||||
using stderr_color_sink_mt = ansicolor_stderr_sink_mt; | |||||
using stderr_color_sink_st = ansicolor_stderr_sink_st; | |||||
#endif | |||||
} // namespace sinks | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name, color_mode mode = color_mode::automatic); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stdout_color_st(const std::string &logger_name, color_mode mode = color_mode::automatic); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name, color_mode mode = color_mode::automatic); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stderr_color_st(const std::string &logger_name, color_mode mode = color_mode::automatic); | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "stdout_color_sinks-inl.h" | |||||
#endif |
@@ -0,0 +1,94 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/sinks/stdout_sinks.h> | |||||
#endif | |||||
#include <spdlog/details/console_globals.h> | |||||
#include <spdlog/details/pattern_formatter.h> | |||||
#include <memory> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE stdout_sink_base<ConsoleMutex>::stdout_sink_base(FILE *file) | |||||
: mutex_(ConsoleMutex::mutex()) | |||||
, file_(file) | |||||
, formatter_(details::make_unique<spdlog::pattern_formatter>()) | |||||
{} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::log(const details::log_msg &msg) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
memory_buf_t formatted; | |||||
formatter_->format(msg, formatted); | |||||
fwrite(formatted.data(), sizeof(char), formatted.size(), file_); | |||||
fflush(file_); // flush every line to terminal | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::flush() | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
fflush(file_); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::set_pattern(const std::string &pattern) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::move(sink_formatter); | |||||
} | |||||
// stdout sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE stdout_sink<ConsoleMutex>::stdout_sink() | |||||
: stdout_sink_base<ConsoleMutex>(stdout) | |||||
{} | |||||
// stderr sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE stderr_sink<ConsoleMutex>::stderr_sink() | |||||
: stdout_sink_base<ConsoleMutex>(stderr) | |||||
{} | |||||
} // namespace sinks | |||||
// factory methods | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stdout_logger_mt(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::stdout_sink_mt>(logger_name); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stdout_logger_st(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::stdout_sink_st>(logger_name); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stderr_logger_mt(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::stderr_sink_mt>(logger_name); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stderr_logger_st(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::stderr_sink_st>(logger_name); | |||||
} | |||||
} // namespace spdlog |
@@ -0,0 +1,76 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/details/console_globals.h> | |||||
#include <spdlog/details/synchronous_factory.h> | |||||
#include <spdlog/sinks/sink.h> | |||||
#include <cstdio> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename ConsoleMutex> | |||||
class stdout_sink_base : public sink | |||||
{ | |||||
public: | |||||
using mutex_t = typename ConsoleMutex::mutex_t; | |||||
explicit stdout_sink_base(FILE *file); | |||||
~stdout_sink_base() override = default; | |||||
stdout_sink_base(const stdout_sink_base &other) = delete; | |||||
stdout_sink_base &operator=(const stdout_sink_base &other) = delete; | |||||
void log(const details::log_msg &msg) override; | |||||
void flush() override; | |||||
void set_pattern(const std::string &pattern) override; | |||||
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override; | |||||
protected: | |||||
mutex_t &mutex_; | |||||
FILE *file_; | |||||
std::unique_ptr<spdlog::formatter> formatter_; | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class stdout_sink : public stdout_sink_base<ConsoleMutex> | |||||
{ | |||||
public: | |||||
stdout_sink(); | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class stderr_sink : public stdout_sink_base<ConsoleMutex> | |||||
{ | |||||
public: | |||||
stderr_sink(); | |||||
}; | |||||
using stdout_sink_mt = stdout_sink<details::console_mutex>; | |||||
using stdout_sink_st = stdout_sink<details::console_nullmutex>; | |||||
using stderr_sink_mt = stderr_sink<details::console_mutex>; | |||||
using stderr_sink_st = stderr_sink<details::console_nullmutex>; | |||||
} // namespace sinks | |||||
// factory methods | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stdout_logger_mt(const std::string &logger_name); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stdout_logger_st(const std::string &logger_name); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stderr_logger_mt(const std::string &logger_name); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stderr_logger_st(const std::string &logger_name); | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "stdout_sinks-inl.h" | |||||
#endif |
@@ -0,0 +1,108 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/sinks/base_sink.h> | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <array> | |||||
#include <string> | |||||
#include <syslog.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/** | |||||
* Sink that write to syslog using the `syscall()` library call. | |||||
*/ | |||||
template<typename Mutex> | |||||
class syslog_sink : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
syslog_sink(std::string ident, int syslog_option, int syslog_facility, bool enable_formatting) | |||||
: enable_formatting_{enable_formatting} | |||||
, syslog_levels_{{/* spdlog::level::trace */ LOG_DEBUG, | |||||
/* spdlog::level::debug */ LOG_DEBUG, | |||||
/* spdlog::level::info */ LOG_INFO, | |||||
/* spdlog::level::warn */ LOG_WARNING, | |||||
/* spdlog::level::err */ LOG_ERR, | |||||
/* spdlog::level::critical */ LOG_CRIT, | |||||
/* spdlog::level::off */ LOG_INFO}} | |||||
, ident_{std::move(ident)} | |||||
{ | |||||
// set ident to be program name if empty | |||||
::openlog(ident_.empty() ? nullptr : ident_.c_str(), syslog_option, syslog_facility); | |||||
} | |||||
~syslog_sink() override | |||||
{ | |||||
::closelog(); | |||||
} | |||||
syslog_sink(const syslog_sink &) = delete; | |||||
syslog_sink &operator=(const syslog_sink &) = delete; | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
string_view_t payload; | |||||
memory_buf_t formatted; | |||||
if (enable_formatting_) | |||||
{ | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
payload = string_view_t(formatted.data(), formatted.size()); | |||||
} | |||||
else | |||||
{ | |||||
payload = msg.payload; | |||||
} | |||||
size_t length = payload.size(); | |||||
// limit to max int | |||||
if (length > static_cast<size_t>(std::numeric_limits<int>::max())) | |||||
{ | |||||
length = static_cast<size_t>(std::numeric_limits<int>::max()); | |||||
} | |||||
::syslog(syslog_prio_from_level(msg), "%.*s", static_cast<int>(length), payload.data()); | |||||
} | |||||
void flush_() override {} | |||||
bool enable_formatting_ = false; | |||||
private: | |||||
using levels_array = std::array<int, 7>; | |||||
levels_array syslog_levels_; | |||||
// must store the ident because the man says openlog might use the pointer as | |||||
// is and not a string copy | |||||
const std::string ident_; | |||||
// | |||||
// Simply maps spdlog's log level to syslog priority level. | |||||
// | |||||
int syslog_prio_from_level(const details::log_msg &msg) const | |||||
{ | |||||
return syslog_levels_.at(static_cast<levels_array::size_type>(msg.level)); | |||||
} | |||||
}; | |||||
using syslog_sink_mt = syslog_sink<std::mutex>; | |||||
using syslog_sink_st = syslog_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// Create and register a syslog logger | |||||
template<typename Factory = default_factory> | |||||
inline std::shared_ptr<logger> syslog_logger_mt(const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, | |||||
int syslog_facility = LOG_USER, bool enable_formatting = false) | |||||
{ | |||||
return Factory::template create<sinks::syslog_sink_mt>(logger_name, syslog_ident, syslog_option, syslog_facility, enable_formatting); | |||||
} | |||||
template<typename Factory = default_factory> | |||||
inline std::shared_ptr<logger> syslog_logger_st(const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, | |||||
int syslog_facility = LOG_USER, bool enable_formatting = false) | |||||
{ | |||||
return Factory::template create<sinks::syslog_sink_st>(logger_name, syslog_ident, syslog_option, syslog_facility, enable_formatting); | |||||
} | |||||
} // namespace spdlog |
@@ -0,0 +1,103 @@ | |||||
// Copyright(c) 2019 ZVYAGIN.Alexander@gmail.com | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/sinks/base_sink.h> | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/details/synchronous_factory.h> | |||||
#include <array> | |||||
#ifndef SD_JOURNAL_SUPPRESS_LOCATION | |||||
#define SD_JOURNAL_SUPPRESS_LOCATION | |||||
#endif | |||||
#include <systemd/sd-journal.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/** | |||||
* Sink that write to systemd journal using the `sd_journal_send()` library call. | |||||
* | |||||
* Locking is not needed, as `sd_journal_send()` itself is thread-safe. | |||||
*/ | |||||
template<typename Mutex> | |||||
class systemd_sink : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
// | |||||
systemd_sink() | |||||
: syslog_levels_{{/* spdlog::level::trace */ LOG_DEBUG, | |||||
/* spdlog::level::debug */ LOG_DEBUG, | |||||
/* spdlog::level::info */ LOG_INFO, | |||||
/* spdlog::level::warn */ LOG_WARNING, | |||||
/* spdlog::level::err */ LOG_ERR, | |||||
/* spdlog::level::critical */ LOG_CRIT, | |||||
/* spdlog::level::off */ LOG_INFO}} | |||||
{} | |||||
~systemd_sink() override {} | |||||
systemd_sink(const systemd_sink &) = delete; | |||||
systemd_sink &operator=(const systemd_sink &) = delete; | |||||
protected: | |||||
using levels_array = std::array<int, 7>; | |||||
levels_array syslog_levels_; | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
int err; | |||||
size_t length = msg.payload.size(); | |||||
// limit to max int | |||||
if (length > static_cast<size_t>(std::numeric_limits<int>::max())) | |||||
{ | |||||
length = static_cast<size_t>(std::numeric_limits<int>::max()); | |||||
} | |||||
// Do not send source location if not available | |||||
if (msg.source.empty()) | |||||
{ | |||||
// Note: function call inside '()' to avoid macro expansion | |||||
err = (sd_journal_send)("MESSAGE=%.*s", static_cast<int>(length), msg.payload.data(), "PRIORITY=%d", syslog_level(msg.level), | |||||
"SYSLOG_IDENTIFIER=%.*s", static_cast<int>(msg.logger_name.size()), msg.logger_name.data(), nullptr); | |||||
} | |||||
else | |||||
{ | |||||
err = (sd_journal_send)("MESSAGE=%.*s", static_cast<int>(length), msg.payload.data(), "PRIORITY=%d", syslog_level(msg.level), | |||||
"SYSLOG_IDENTIFIER=%.*s", static_cast<int>(msg.logger_name.size()), msg.logger_name.data(), "CODE_FILE=%s", | |||||
msg.source.filename, "CODE_LINE=%d", msg.source.line, "CODE_FUNC=%s", msg.source.funcname, nullptr); | |||||
} | |||||
if (err) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("Failed writing to systemd", errno)); | |||||
} | |||||
} | |||||
int syslog_level(level::level_enum l) | |||||
{ | |||||
return syslog_levels_.at(static_cast<levels_array::size_type>(l)); | |||||
} | |||||
void flush_() override {} | |||||
}; | |||||
using systemd_sink_mt = systemd_sink<std::mutex>; | |||||
using systemd_sink_st = systemd_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// Create and register a syslog logger | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> systemd_logger_mt(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::systemd_sink_mt>(logger_name); | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> systemd_logger_st(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::systemd_sink_st>(logger_name); | |||||
} | |||||
} // namespace spdlog |
@@ -0,0 +1,179 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/sinks/wincolor_sink.h> | |||||
#endif | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/pattern_formatter.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE wincolor_sink<ConsoleMutex>::wincolor_sink(HANDLE out_handle, color_mode mode) | |||||
: out_handle_(out_handle) | |||||
, mutex_(ConsoleMutex::mutex()) | |||||
, formatter_(details::make_unique<spdlog::pattern_formatter>()) | |||||
{ | |||||
// check if out_handle is points to the actual console. | |||||
// ::GetConsoleMode() should return 0 if it is redirected or not valid console handle. | |||||
DWORD console_mode; | |||||
in_console_ = ::GetConsoleMode(out_handle, &console_mode) != 0; | |||||
set_color_mode(mode); | |||||
colors_[level::trace] = WHITE; | |||||
colors_[level::debug] = CYAN; | |||||
colors_[level::info] = GREEN; | |||||
colors_[level::warn] = YELLOW | BOLD; | |||||
colors_[level::err] = RED | BOLD; // red bold | |||||
colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background | |||||
colors_[level::off] = 0; | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE wincolor_sink<ConsoleMutex>::~wincolor_sink() | |||||
{ | |||||
this->flush(); | |||||
} | |||||
// change the color for the given level | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color(level::level_enum level, WORD color) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
colors_[level] = color; | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::log(const details::log_msg &msg) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
memory_buf_t formatted; | |||||
formatter_->format(msg, formatted); | |||||
if (!in_console_) | |||||
{ | |||||
write_to_file_(formatted); | |||||
return; | |||||
} | |||||
if (should_do_colors_ && msg.color_range_end > msg.color_range_start) | |||||
{ | |||||
// before color range | |||||
print_range_(formatted, 0, msg.color_range_start); | |||||
// in color range | |||||
auto orig_attribs = set_foreground_color_(colors_[msg.level]); | |||||
print_range_(formatted, msg.color_range_start, msg.color_range_end); | |||||
// reset to orig colors | |||||
::SetConsoleTextAttribute(out_handle_, orig_attribs); | |||||
print_range_(formatted, msg.color_range_end, formatted.size()); | |||||
} | |||||
else // print without colors if color range is invalid (or color is disabled) | |||||
{ | |||||
print_range_(formatted, 0, formatted.size()); | |||||
} | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::flush() | |||||
{ | |||||
// windows console always flushed? | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_pattern(const std::string &pattern) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::move(sink_formatter); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color_mode(color_mode mode) | |||||
{ | |||||
switch (mode) | |||||
{ | |||||
case color_mode::always: | |||||
case color_mode::automatic: | |||||
should_do_colors_ = true; | |||||
break; | |||||
case color_mode::never: | |||||
should_do_colors_ = false; | |||||
break; | |||||
default: | |||||
should_do_colors_ = true; | |||||
} | |||||
} | |||||
// set foreground color and return the orig console attributes (for resetting later) | |||||
template<typename ConsoleMutex> | |||||
WORD SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_foreground_color_(WORD attribs) | |||||
{ | |||||
CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; | |||||
::GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info); | |||||
WORD back_color = orig_buffer_info.wAttributes; | |||||
// retrieve the current background color | |||||
back_color &= static_cast<WORD>(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)); | |||||
// keep the background color unchanged | |||||
::SetConsoleTextAttribute(out_handle_, attribs | back_color); | |||||
return orig_buffer_info.wAttributes; // return orig attribs | |||||
} | |||||
// print a range of formatted message to console | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::print_range_(const memory_buf_t &formatted, size_t start, size_t end) | |||||
{ | |||||
auto size = static_cast<DWORD>(end - start); | |||||
::WriteConsoleA(out_handle_, formatted.data() + start, size, nullptr, nullptr); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::write_to_file_(const memory_buf_t &formatted) | |||||
{ | |||||
if (out_handle_ == nullptr) // no console and no file redirect | |||||
{ | |||||
return; | |||||
} | |||||
auto size = static_cast<DWORD>(formatted.size()); | |||||
if (size == 0) | |||||
{ | |||||
return; | |||||
} | |||||
DWORD total_written = 0; | |||||
do | |||||
{ | |||||
DWORD bytes_written = 0; | |||||
bool ok = ::WriteFile(out_handle_, formatted.data() + total_written, size - total_written, &bytes_written, nullptr) != 0; | |||||
if (!ok || bytes_written == 0) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("wincolor_sink: write_to_file_ failed. GetLastError(): " + std::to_string(::GetLastError()))); | |||||
} | |||||
total_written += bytes_written; | |||||
} while (total_written < size); | |||||
} | |||||
// wincolor_stdout_sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE wincolor_stdout_sink<ConsoleMutex>::wincolor_stdout_sink(color_mode mode) | |||||
: wincolor_sink<ConsoleMutex>(::GetStdHandle(STD_OUTPUT_HANDLE), mode) | |||||
{} | |||||
// wincolor_stderr_sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE wincolor_stderr_sink<ConsoleMutex>::wincolor_stderr_sink(color_mode mode) | |||||
: wincolor_sink<ConsoleMutex>(::GetStdHandle(STD_ERROR_HANDLE), mode) | |||||
{} | |||||
} // namespace sinks | |||||
} // namespace spdlog |
@@ -0,0 +1,92 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/console_globals.h> | |||||
#include <spdlog/details/null_mutex.h> | |||||
#include <spdlog/sinks/sink.h> | |||||
#include <memory> | |||||
#include <mutex> | |||||
#include <string> | |||||
#include <unordered_map> | |||||
#include <wincon.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/* | |||||
* Windows color console sink. Uses WriteConsoleA to write to the console with | |||||
* colors | |||||
*/ | |||||
template<typename ConsoleMutex> | |||||
class wincolor_sink : public sink | |||||
{ | |||||
public: | |||||
const WORD BOLD = FOREGROUND_INTENSITY; | |||||
const WORD RED = FOREGROUND_RED; | |||||
const WORD GREEN = FOREGROUND_GREEN; | |||||
const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE; | |||||
const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; | |||||
const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; | |||||
wincolor_sink(HANDLE out_handle, color_mode mode); | |||||
~wincolor_sink() override; | |||||
wincolor_sink(const wincolor_sink &other) = delete; | |||||
wincolor_sink &operator=(const wincolor_sink &other) = delete; | |||||
// change the color for the given level | |||||
void set_color(level::level_enum level, WORD color); | |||||
void log(const details::log_msg &msg) final override; | |||||
void flush() final override; | |||||
void set_pattern(const std::string &pattern) override final; | |||||
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override final; | |||||
void set_color_mode(color_mode mode); | |||||
protected: | |||||
using mutex_t = typename ConsoleMutex::mutex_t; | |||||
HANDLE out_handle_; | |||||
mutex_t &mutex_; | |||||
bool in_console_; | |||||
bool should_do_colors_; | |||||
std::unique_ptr<spdlog::formatter> formatter_; | |||||
std::unordered_map<level::level_enum, WORD, level::level_hasher> colors_; | |||||
// set foreground color and return the orig console attributes (for resetting later) | |||||
WORD set_foreground_color_(WORD attribs); | |||||
// print a range of formatted message to console | |||||
void print_range_(const memory_buf_t &formatted, size_t start, size_t end); | |||||
// in case we are redirected to file (not in console mode) | |||||
void write_to_file_(const memory_buf_t &formatted); | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class wincolor_stdout_sink : public wincolor_sink<ConsoleMutex> | |||||
{ | |||||
public: | |||||
explicit wincolor_stdout_sink(color_mode mode = color_mode::automatic); | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class wincolor_stderr_sink : public wincolor_sink<ConsoleMutex> | |||||
{ | |||||
public: | |||||
explicit wincolor_stderr_sink(color_mode mode = color_mode::automatic); | |||||
}; | |||||
using wincolor_stdout_sink_mt = wincolor_stdout_sink<details::console_mutex>; | |||||
using wincolor_stdout_sink_st = wincolor_stdout_sink<details::console_nullmutex>; | |||||
using wincolor_stderr_sink_mt = wincolor_stderr_sink<details::console_mutex>; | |||||
using wincolor_stderr_sink_st = wincolor_stderr_sink<details::console_nullmutex>; | |||||
} // namespace sinks | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "wincolor_sink-inl.h" | |||||
#endif |
@@ -0,0 +1,115 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include <spdlog/spdlog.h> | |||||
#endif | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/pattern_formatter.h> | |||||
namespace spdlog { | |||||
SPDLOG_INLINE void initialize_logger(std::shared_ptr<logger> logger) | |||||
{ | |||||
details::registry::instance().initialize_logger(std::move(logger)); | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<logger> get(const std::string &name) | |||||
{ | |||||
return details::registry::instance().get(name); | |||||
} | |||||
SPDLOG_INLINE void set_formatter(std::unique_ptr<spdlog::formatter> formatter) | |||||
{ | |||||
details::registry::instance().set_formatter(std::move(formatter)); | |||||
} | |||||
SPDLOG_INLINE void set_pattern(std::string pattern, pattern_time_type time_type) | |||||
{ | |||||
set_formatter(std::unique_ptr<spdlog::formatter>(new pattern_formatter(std::move(pattern), time_type))); | |||||
} | |||||
SPDLOG_INLINE void enable_backtrace(size_t n_messages) | |||||
{ | |||||
details::registry::instance().enable_backtrace(n_messages); | |||||
} | |||||
SPDLOG_INLINE void disable_backtrace() | |||||
{ | |||||
details::registry::instance().disable_backtrace(); | |||||
} | |||||
SPDLOG_INLINE void dump_backtrace() | |||||
{ | |||||
default_logger_raw()->dump_backtrace(); | |||||
} | |||||
SPDLOG_INLINE void set_level(level::level_enum log_level) | |||||
{ | |||||
details::registry::instance().set_level(log_level); | |||||
} | |||||
SPDLOG_INLINE void flush_on(level::level_enum log_level) | |||||
{ | |||||
details::registry::instance().flush_on(log_level); | |||||
} | |||||
SPDLOG_INLINE void flush_every(std::chrono::seconds interval) | |||||
{ | |||||
details::registry::instance().flush_every(interval); | |||||
} | |||||
SPDLOG_INLINE void set_error_handler(void (*handler)(const std::string &msg)) | |||||
{ | |||||
details::registry::instance().set_error_handler(handler); | |||||
} | |||||
SPDLOG_INLINE void register_logger(std::shared_ptr<logger> logger) | |||||
{ | |||||
details::registry::instance().register_logger(std::move(logger)); | |||||
} | |||||
SPDLOG_INLINE void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun) | |||||
{ | |||||
details::registry::instance().apply_all(fun); | |||||
} | |||||
SPDLOG_INLINE void drop(const std::string &name) | |||||
{ | |||||
details::registry::instance().drop(name); | |||||
} | |||||
SPDLOG_INLINE void drop_all() | |||||
{ | |||||
details::registry::instance().drop_all(); | |||||
} | |||||
SPDLOG_INLINE void shutdown() | |||||
{ | |||||
details::registry::instance().shutdown(); | |||||
} | |||||
SPDLOG_INLINE void set_automatic_registration(bool automatic_registration) | |||||
{ | |||||
details::registry::instance().set_automatic_registration(automatic_registration); | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<spdlog::logger> default_logger() | |||||
{ | |||||
return details::registry::instance().default_logger(); | |||||
} | |||||
SPDLOG_INLINE spdlog::logger *default_logger_raw() | |||||
{ | |||||
return details::registry::instance().get_default_raw(); | |||||
} | |||||
SPDLOG_INLINE void set_default_logger(std::shared_ptr<spdlog::logger> default_logger) | |||||
{ | |||||
details::registry::instance().set_default_logger(std::move(default_logger)); | |||||
} | |||||
} // namespace spdlog |
@@ -0,0 +1,342 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
// spdlog main header file. | |||||
// see example.cpp for usage example | |||||
#ifndef SPDLOG_H | |||||
#define SPDLOG_H | |||||
#pragma once | |||||
#include <spdlog/common.h> | |||||
#include <spdlog/details/registry.h> | |||||
#include <spdlog/logger.h> | |||||
#include <spdlog/version.h> | |||||
#include <spdlog/details/synchronous_factory.h> | |||||
#include <chrono> | |||||
#include <functional> | |||||
#include <memory> | |||||
#include <string> | |||||
namespace spdlog { | |||||
using default_factory = synchronous_factory; | |||||
// Create and register a logger with a templated sink type | |||||
// The logger's level, formatter and flush level will be set according the | |||||
// global settings. | |||||
// | |||||
// Example: | |||||
// spdlog::create<daily_file_sink_st>("logger_name", "dailylog_filename", 11, 59); | |||||
template<typename Sink, typename... SinkArgs> | |||||
inline std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... sink_args) | |||||
{ | |||||
return default_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); | |||||
} | |||||
// Initialize and register a logger, | |||||
// formatter and flush level will be set according the global settings. | |||||
// | |||||
// NOTE: | |||||
// Use this function when creating loggers manually. | |||||
// | |||||
// Example: | |||||
// auto console_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>(); | |||||
// auto console_logger = std::make_shared<spdlog::logger>("console_logger", console_sink); | |||||
// spdlog::initialize_logger(console_logger); | |||||
void initialize_logger(std::shared_ptr<logger> logger); | |||||
// Return an existing logger or nullptr if a logger with such name doesn't | |||||
// exist. | |||||
// example: spdlog::get("my_logger")->info("hello {}", "world"); | |||||
std::shared_ptr<logger> get(const std::string &name); | |||||
// Set global formatter. Each sink in each logger will get a clone of this object | |||||
void set_formatter(std::unique_ptr<spdlog::formatter> formatter); | |||||
// Set global format string. | |||||
// example: spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v"); | |||||
void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local); | |||||
// enable global backtrace support | |||||
void enable_backtrace(size_t n_messages); | |||||
// disable global backtrace support | |||||
void disable_backtrace(); | |||||
// call dump backtrace on default logger | |||||
void dump_backtrace(); | |||||
// Set global logging level | |||||
void set_level(level::level_enum log_level); | |||||
// Set global flush level | |||||
void flush_on(level::level_enum log_level); | |||||
// Start/Restart a periodic flusher thread | |||||
// Warning: Use only if all your loggers are thread safe! | |||||
void flush_every(std::chrono::seconds interval); | |||||
// Set global error handler | |||||
void set_error_handler(void (*handler)(const std::string &msg)); | |||||
// Register the given logger with the given name | |||||
void register_logger(std::shared_ptr<logger> logger); | |||||
// Apply a user defined function on all registered loggers | |||||
// Example: | |||||
// spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) {l->flush();}); | |||||
void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun); | |||||
// Drop the reference to the given logger | |||||
void drop(const std::string &name); | |||||
// Drop all references from the registry | |||||
void drop_all(); | |||||
// stop any running threads started by spdlog and clean registry loggers | |||||
void shutdown(); | |||||
// Automatic registration of loggers when using spdlog::create() or spdlog::create_async | |||||
void set_automatic_registration(bool automatic_registration); | |||||
// API for using default logger (stdout_color_mt), | |||||
// e.g: spdlog::info("Message {}", 1); | |||||
// | |||||
// The default logger object can be accessed using the spdlog::default_logger(): | |||||
// For example, to add another sink to it: | |||||
// spdlog::default_logger()->sinks()->push_back(some_sink); | |||||
// | |||||
// The default logger can replaced using spdlog::set_default_logger(new_logger). | |||||
// For example, to replace it with a file logger. | |||||
// | |||||
// IMPORTANT: | |||||
// The default API is thread safe (for _mt loggers), but: | |||||
// set_default_logger() *should not* be used concurrently with the default API. | |||||
// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. | |||||
std::shared_ptr<spdlog::logger> default_logger(); | |||||
spdlog::logger *default_logger_raw(); | |||||
void set_default_logger(std::shared_ptr<spdlog::logger> default_logger); | |||||
template<typename... Args> | |||||
inline void log(source_loc source, level::level_enum lvl, string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->log(source, lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void log(level::level_enum lvl, string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->log(source_loc{}, lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void trace(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->trace(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void debug(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->debug(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void info(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->info(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void warn(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->warn(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void error(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->error(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void critical(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->critical(fmt, args...); | |||||
} | |||||
template<typename T> | |||||
inline void log(source_loc source, level::level_enum lvl, const T &msg) | |||||
{ | |||||
default_logger_raw()->log(source, lvl, msg); | |||||
} | |||||
template<typename T> | |||||
inline void log(level::level_enum lvl, const T &msg) | |||||
{ | |||||
default_logger_raw()->log(lvl, msg); | |||||
} | |||||
template<typename T> | |||||
inline void trace(const T &msg) | |||||
{ | |||||
default_logger_raw()->trace(msg); | |||||
} | |||||
template<typename T> | |||||
inline void debug(const T &msg) | |||||
{ | |||||
default_logger_raw()->debug(msg); | |||||
} | |||||
template<typename T> | |||||
inline void info(const T &msg) | |||||
{ | |||||
default_logger_raw()->info(msg); | |||||
} | |||||
template<typename T> | |||||
inline void warn(const T &msg) | |||||
{ | |||||
default_logger_raw()->warn(msg); | |||||
} | |||||
template<typename T> | |||||
inline void error(const T &msg) | |||||
{ | |||||
default_logger_raw()->error(msg); | |||||
} | |||||
template<typename T> | |||||
inline void critical(const T &msg) | |||||
{ | |||||
default_logger_raw()->critical(msg); | |||||
} | |||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
template<typename... Args> | |||||
inline void log(source_loc source, level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->log(source, lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void log(level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->log(lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void trace(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->trace(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void debug(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->debug(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void info(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->info(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void warn(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->warn(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void error(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->error(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void critical(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->critical(fmt, args...); | |||||
} | |||||
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
} // namespace spdlog | |||||
// | |||||
// enable/disable log calls at compile time according to global level. | |||||
// | |||||
// define SPDLOG_ACTIVE_LEVEL to one of those (before including spdlog.h): | |||||
// SPDLOG_LEVEL_TRACE, | |||||
// SPDLOG_LEVEL_DEBUG, | |||||
// SPDLOG_LEVEL_INFO, | |||||
// SPDLOG_LEVEL_WARN, | |||||
// SPDLOG_LEVEL_ERROR, | |||||
// SPDLOG_LEVEL_CRITICAL, | |||||
// SPDLOG_LEVEL_OFF | |||||
// | |||||
#define SPDLOG_LOGGER_CALL(logger, level, ...) (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, level, __VA_ARGS__) | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_TRACE | |||||
#define SPDLOG_LOGGER_TRACE(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::trace, __VA_ARGS__) | |||||
#define SPDLOG_TRACE(...) SPDLOG_LOGGER_TRACE(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_TRACE(logger, ...) (void)0 | |||||
#define SPDLOG_TRACE(...) (void)0 | |||||
#endif | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG | |||||
#define SPDLOG_LOGGER_DEBUG(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::debug, __VA_ARGS__) | |||||
#define SPDLOG_DEBUG(...) SPDLOG_LOGGER_DEBUG(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_DEBUG(logger, ...) (void)0 | |||||
#define SPDLOG_DEBUG(...) (void)0 | |||||
#endif | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_INFO | |||||
#define SPDLOG_LOGGER_INFO(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::info, __VA_ARGS__) | |||||
#define SPDLOG_INFO(...) SPDLOG_LOGGER_INFO(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_INFO(logger, ...) (void)0 | |||||
#define SPDLOG_INFO(...) (void)0 | |||||
#endif | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_WARN | |||||
#define SPDLOG_LOGGER_WARN(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::warn, __VA_ARGS__) | |||||
#define SPDLOG_WARN(...) SPDLOG_LOGGER_WARN(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_WARN(logger, ...) (void)0 | |||||
#define SPDLOG_WARN(...) (void)0 | |||||
#endif | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_ERROR | |||||
#define SPDLOG_LOGGER_ERROR(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::err, __VA_ARGS__) | |||||
#define SPDLOG_ERROR(...) SPDLOG_LOGGER_ERROR(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_ERROR(logger, ...) (void)0 | |||||
#define SPDLOG_ERROR(...) (void)0 | |||||
#endif | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_CRITICAL | |||||
#define SPDLOG_LOGGER_CRITICAL(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::critical, __VA_ARGS__) | |||||
#define SPDLOG_CRITICAL(...) SPDLOG_LOGGER_CRITICAL(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_CRITICAL(logger, ...) (void)0 | |||||
#define SPDLOG_CRITICAL(...) (void)0 | |||||
#endif | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "spdlog-inl.h" | |||||
#endif | |||||
#endif // SPDLOG_H |
@@ -0,0 +1,123 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// | |||||
// Edit this file to squeeze more performance, and to customize supported | |||||
// features | |||||
// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Under Linux, the much faster CLOCK_REALTIME_COARSE clock can be used. | |||||
// This clock is less accurate - can be off by dozens of millis - depending on | |||||
// the kernel HZ. | |||||
// Uncomment to use it instead of the regular clock. | |||||
// | |||||
// #define SPDLOG_CLOCK_COARSE | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment if thread id logging is not needed (i.e. no %t in the log pattern). | |||||
// This will prevent spdlog from querying the thread id on each log call. | |||||
// | |||||
// WARNING: If the log pattern contains thread id (i.e, %t) while this flag is | |||||
// on, the result is undefined. | |||||
// | |||||
// #define SPDLOG_NO_THREAD_ID | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to prevent spdlog from using thread local storage. | |||||
// | |||||
// WARNING: if your program forks, UNCOMMENT this flag to prevent undefined | |||||
// thread ids in the children logs. | |||||
// | |||||
// #define SPDLOG_NO_TLS | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment if logger name logging is not needed. | |||||
// This will prevent spdlog from copying the logger name on each log call. | |||||
// | |||||
// #define SPDLOG_NO_NAME | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to avoid spdlog's usage of atomic log levels | |||||
// Use only if your code never modifies a logger's log levels concurrently by | |||||
// different threads. | |||||
// | |||||
// #define SPDLOG_NO_ATOMIC_LEVELS | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to enable usage of wchar_t for file names on Windows. | |||||
// | |||||
// #define SPDLOG_WCHAR_FILENAMES | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to override default eol ("\n" or "\r\n" under Linux/Windows) | |||||
// | |||||
// #define SPDLOG_EOL ";-)\n" | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to use your own copy of the fmt library instead of spdlog's copy. | |||||
// In this case spdlog will try to include <fmt/format.h> so set your -I flag | |||||
// accordingly. | |||||
// | |||||
// #define SPDLOG_FMT_EXTERNAL | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to enable wchar_t support (convert to utf8) | |||||
// | |||||
// #define SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to prevent child processes from inheriting log file descriptors | |||||
// | |||||
// #define SPDLOG_PREVENT_CHILD_FD | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to customize level names (e.g. "MT TRACE") | |||||
// | |||||
// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", | |||||
// "MY ERROR", "MY CRITICAL", "OFF" } | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to customize short level names (e.g. "MT") | |||||
// These can be longer than one character. | |||||
// | |||||
// #define SPDLOG_SHORT_LEVEL_NAMES { "T", "D", "I", "W", "E", "C", "O" } | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to disable default logger creation. | |||||
// This might save some (very) small initialization time if no default logger is needed. | |||||
// | |||||
// #define SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment and set to compile time level with zero cost (default is INFO). | |||||
// Macros like SPDLOG_DEBUG(..), SPDLOG_INFO(..) will expand to empty statements if not enabled | |||||
// | |||||
// #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment (and change if desired) macro to use for function names. | |||||
// This is compiler dependent. | |||||
// __PRETTY_FUNCTION__ might be nicer in clang/gcc, and __FUNCTION__ in msvc. | |||||
// Defaults to __FUNCTION__ (should work on all compilers) if not defined. | |||||
// | |||||
// #define SPDLOG_FUNCTION __PRETTY_FUNCTION__ | |||||
/////////////////////////////////////////////////////////////////////////////// |
@@ -0,0 +1,10 @@ | |||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#define SPDLOG_VER_MAJOR 1 | |||||
#define SPDLOG_VER_MINOR 4 | |||||
#define SPDLOG_VER_PATCH 3 | |||||
#define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH) |
@@ -74,7 +74,7 @@ | |||||
<PrecompiledHeader>NotUsing</PrecompiledHeader> | <PrecompiledHeader>NotUsing</PrecompiledHeader> | ||||
<WarningLevel>Level3</WarningLevel> | <WarningLevel>Level3</WarningLevel> | ||||
<DebugInformationFormat>EditAndContinue</DebugInformationFormat> | <DebugInformationFormat>EditAndContinue</DebugInformationFormat> | ||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\Protobuf;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
<AdditionalIncludeDirectories>$(SolutionDir)Source\Protobuf;$(SolutionDir)Source\Spdlog;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||||
</ClCompile> | </ClCompile> | ||||
<ResourceCompile> | <ResourceCompile> | ||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> | <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||