logger.hpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. /*
  2. * Copyright (c) 2017-2023 zhllxt
  3. *
  4. * author : zhllxt
  5. * email : 37792738@qq.com
  6. *
  7. * Distributed under the Boost Software License, Version 1.0. (See accompanying
  8. * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  9. */
  10. #ifndef __ASIO2_LOGGER_HPP__
  11. #define __ASIO2_LOGGER_HPP__
  12. #if defined(_MSC_VER) && (_MSC_VER >= 1200)
  13. #pragma once
  14. #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
  15. #include <ctime>
  16. #include <cstring>
  17. #include <cstdarg>
  18. #include <memory>
  19. #include <mutex>
  20. #include <thread>
  21. #include <condition_variable>
  22. #include <functional>
  23. #include <iostream>
  24. #include <fstream>
  25. #include <string>
  26. /*
  27. *
  28. * How to determine whether to use <filesystem> or <experimental/filesystem>
  29. * https://stackoverflow.com/questions/53365538/how-to-determine-whether-to-use-filesystem-or-experimental-filesystem/53365539#53365539
  30. *
  31. *
  32. */
  33. // We haven't checked which filesystem to include yet
  34. #ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
  35. // Check for feature test macro for <filesystem>
  36. # if defined(__cpp_lib_filesystem)
  37. # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
  38. // Check for feature test macro for <experimental/filesystem>
  39. # elif defined(__cpp_lib_experimental_filesystem)
  40. # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
  41. // We can't check if headers exist...
  42. // Let's assume experimental to be safe
  43. # elif !defined(__has_include)
  44. # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
  45. // Check if the header "<filesystem>" exists
  46. # elif __has_include(<filesystem>)
  47. // If we're compiling on Visual Studio and are not compiling with C++17, we need to use experimental
  48. # ifdef _MSC_VER
  49. // Check and include header that defines "_HAS_CXX17"
  50. # if __has_include(<yvals_core.h>)
  51. # include <yvals_core.h>
  52. // Check for enabled C++17 support
  53. # if defined(_HAS_CXX17) && _HAS_CXX17
  54. // We're using C++17, so let's use the normal version
  55. # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
  56. # endif
  57. # endif
  58. // If the marco isn't defined yet, that means any of the other VS specific checks failed, so we need to use experimental
  59. # ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
  60. # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
  61. # endif
  62. // Not on Visual Studio. Let's use the normal version
  63. # else // #ifdef _MSC_VER
  64. # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
  65. # endif
  66. // Check if the header "<filesystem>" exists
  67. # elif __has_include(<experimental/filesystem>)
  68. # define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
  69. // Fail if neither header is available with a nice error message
  70. # else
  71. # error Could not find system header "<filesystem>" or "<experimental/filesystem>"
  72. # endif
  73. // We priously determined that we need the exprimental version
  74. # if INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
  75. // Include it
  76. # include <experimental/filesystem>
  77. // We need the alias from std::experimental::filesystem to std::filesystem
  78. namespace std {
  79. namespace filesystem = experimental::filesystem;
  80. }
  81. // We have a decent compiler and can use the normal version
  82. # else
  83. // Include it
  84. # include <filesystem>
  85. # endif
  86. #endif // #ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
  87. #ifndef FMT_HEADER_ONLY
  88. #define FMT_HEADER_ONLY
  89. #endif
  90. #include <fmt/format.h>
  91. #include <fmt/chrono.h>
  92. /*
  93. * mutex,thread:
  94. * Linux platform needs to add -lpthread option in link libraries
  95. * filesystem:
  96. * Linux platform needs to add -lstdc++fs option in link libraries, If it still doesn't work,
  97. * try adding stdc++fs to Library Dependencies.
  98. * try search file for libstdc++fs.a
  99. */
  100. // when use logger in multi module(eg:exe and dll),should #define ASIO2_LOGGER_MULTI_MODULE
  101. // #define ASIO2_LOGGER_MULTI_MODULE
  102. /*
  103. # don't use "FILE fwrite ..." to handle the file,because if you declare a logger object,
  104. when call "fopen" in exe module, and call "fwite" in dll module,it will crash. use
  105. std::ofstream can avoid this problem.
  106. # why use "ASIO2_LOGGER_MULTI_MODULE" and create the file in a thread ? when call
  107. std::ofstream::open in exe module,and close file in dll module,it will crash.
  108. we should ensure that which module create the object,the object must destroy by
  109. the same module. so we create a thread,when need recreate the file,we post a
  110. notify event to the thread,and the thread will create the file in the module
  111. which create the file.
  112. # why don't use boost::log ? beacuse if you want use boost::log on multi module
  113. (eg:exe and dll),the boost::log must be compilered by shared link(a dll),and
  114. when the exe is compilered with MT/MTd,there will be a lot of compilation problems.
  115. */
  116. namespace asio2
  117. {
  118. #if defined(_MSC_VER)
  119. # pragma warning(push)
  120. # pragma warning(disable:4996)
  121. #endif
  122. /**
  123. * a simple logger
  124. */
  125. class logger
  126. {
  127. public:
  128. // We define our own severity levels
  129. enum class severity_level : std::int8_t
  130. {
  131. trace,
  132. debug,
  133. info,
  134. warn,
  135. error,
  136. fatal,
  137. report
  138. };
  139. enum dest
  140. { // constants for file opening options
  141. dest_mask = 0xff
  142. };
  143. static constexpr dest console = (dest)0x01;
  144. static constexpr dest file = (dest)0x02;
  145. explicit logger(
  146. const std::string & filename = std::string(),
  147. severity_level level = severity_level::trace,
  148. unsigned int dest = console | file,
  149. std::size_t roll_size = 1024 * 1024 * 1024
  150. )
  151. : filename_(filename), level_(level), dest_(dest), roll_size_(roll_size)
  152. {
  153. if (this->roll_size_ < 1024)
  154. this->roll_size_ = 1024;
  155. if (this->level_ < severity_level::trace || this->level_ > severity_level::report)
  156. this->level_ = severity_level::debug;
  157. this->endl_ = { '\n' };
  158. this->preferred_ = { '/' };
  159. #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || \
  160. defined(_WINDOWS_) || defined(__WINDOWS__) || defined(__TOS_WIN__)
  161. this->endl_ = { '\r','\n' };
  162. this->preferred_ = { '\\' };
  163. #endif
  164. #if defined(ASIO2_LOGGER_MULTI_MODULE)
  165. std::thread t([this]() mutable
  166. {
  167. while (true)
  168. {
  169. std::unique_lock<std::mutex> lock(this->mtx_);
  170. this->cv_.wait(lock, [this]() { return this->is_stop_ || this->mkflag_; });
  171. if (this->is_stop_)
  172. return;
  173. if (this->mkflag_)
  174. {
  175. this->mkfile();
  176. this->mkflag_ = false;
  177. }
  178. }
  179. });
  180. this->thread_.swap(t);
  181. #endif
  182. this->mkfile();
  183. }
  184. ~logger()
  185. {
  186. #if defined(ASIO2_LOGGER_MULTI_MODULE)
  187. {
  188. std::unique_lock<std::mutex> lock(this->mtx_);
  189. this->is_stop_ = true;
  190. }
  191. this->cv_.notify_all();
  192. if (this->thread_.joinable())
  193. this->thread_.join();
  194. #endif
  195. std::lock_guard<std::mutex> g(this->mutex_);
  196. if (this->file_.is_open())
  197. {
  198. this->file_.flush();
  199. this->file_.close();
  200. }
  201. }
  202. static logger & get() noexcept { static logger log; return log; }
  203. inline const char * level2severity(severity_level level) noexcept
  204. {
  205. static const char * name[]{ "TRACE","DEBUG","INFOR","WARNS","ERROR","FATAL","REPOT", };
  206. return ((level >= severity_level::trace && level <= severity_level::report) ?
  207. name[static_cast<typename std::underlying_type<severity_level>::type>(level)] : "");
  208. }
  209. /**
  210. * set the log ouput level,if you has't call this function,the defaul level is trace.
  211. */
  212. inline logger & set_level(severity_level level) noexcept
  213. {
  214. std::lock_guard<std::mutex> g(this->mutex_);
  215. this->level_ = level;
  216. if (this->level_ < severity_level::trace || this->level_ > severity_level::report)
  217. this->level_ = severity_level::debug;
  218. return (*this);
  219. }
  220. /**
  221. * set the log ouput level,if you has't call this function,the defaul level is trace.
  222. * same as set_level
  223. */
  224. inline logger & level(severity_level level) noexcept
  225. {
  226. return this->set_level(level);
  227. }
  228. /**
  229. * get the log ouput level
  230. */
  231. inline severity_level get_level() noexcept
  232. {
  233. std::lock_guard<std::mutex> g(this->mutex_);
  234. return this->level_;
  235. }
  236. /**
  237. * get the log ouput level, same as get_level
  238. */
  239. inline severity_level level() noexcept
  240. {
  241. std::lock_guard<std::mutex> g(this->mutex_);
  242. return this->level_;
  243. }
  244. /**
  245. * set the log ouput dest, console or file or both.
  246. */
  247. inline logger & set_dest(unsigned int dest) noexcept
  248. {
  249. std::lock_guard<std::mutex> g(this->mutex_);
  250. this->dest_ = dest;
  251. return (*this);
  252. }
  253. /**
  254. * set the log ouput dest, console or file or both. same as set_dest
  255. */
  256. inline logger & dest(unsigned int dest) noexcept
  257. {
  258. return this->set_dest(dest);
  259. }
  260. /**
  261. * get the log ouput dest, console or file or both.
  262. */
  263. inline unsigned int get_dest() noexcept
  264. {
  265. std::lock_guard<std::mutex> g(this->mutex_);
  266. return this->dest_;
  267. }
  268. /**
  269. * get the log ouput dest, console or file or both. same as get_dest
  270. */
  271. inline unsigned int dest() noexcept
  272. {
  273. std::lock_guard<std::mutex> g(this->mutex_);
  274. return this->dest_;
  275. }
  276. /**
  277. * set a user custom function log target.
  278. * @Fun : void(const std::string & text)
  279. */
  280. template<class Fun>
  281. inline logger & set_target(Fun&& target) noexcept
  282. {
  283. std::lock_guard<std::mutex> g(this->mutex_);
  284. this->target_ = std::forward<Fun>(target);
  285. return (*this);
  286. }
  287. /**
  288. * set a user custom function log target. same as set_target
  289. * @Fun : void(const std::string & text)
  290. */
  291. template<class Fun>
  292. inline logger & target(Fun&& target) noexcept
  293. {
  294. return this->set_target(std::forward<Fun>(target));
  295. }
  296. /**
  297. * get the user custom function log target.
  298. */
  299. inline std::function<void(const std::string & text)> & get_target() noexcept
  300. {
  301. return this->target_;
  302. }
  303. /**
  304. * get the user custom function log target. same as get_target
  305. */
  306. inline std::function<void(const std::string & text)> & target() noexcept
  307. {
  308. return this->target_;
  309. }
  310. /**
  311. * convert the level value to string
  312. */
  313. inline const char * level2string(severity_level level) noexcept
  314. {
  315. static const char * name[]{ "trace","debug","info","warn","error","fatal","report", };
  316. return ((level >= severity_level::trace && level <= severity_level::report) ?
  317. name[static_cast<typename std::underlying_type<severity_level>::type>(level)] : "");
  318. }
  319. template <typename S, typename... Args>
  320. inline void log_trace(const S& format_str, Args&&... args)
  321. {
  322. if (logger::severity_level::trace >= this->level_)
  323. {
  324. this->log(logger::severity_level::trace, format_str, std::forward<Args>(args)...);
  325. }
  326. }
  327. template <typename S, typename... Args>
  328. inline void log_debug(const S& format_str, Args&&... args)
  329. {
  330. if (logger::severity_level::debug >= this->level_)
  331. {
  332. this->log(logger::severity_level::debug, format_str, std::forward<Args>(args)...);
  333. }
  334. }
  335. template <typename S, typename... Args>
  336. inline void log_info(const S& format_str, Args&&... args)
  337. {
  338. if (logger::severity_level::info >= this->level_)
  339. {
  340. this->log(logger::severity_level::info, format_str, std::forward<Args>(args)...);
  341. }
  342. }
  343. template <typename S, typename... Args>
  344. inline void log_warn(const S& format_str, Args&&... args)
  345. {
  346. if (logger::severity_level::warn >= this->level_)
  347. {
  348. this->log(logger::severity_level::warn, format_str, std::forward<Args>(args)...);
  349. }
  350. }
  351. template <typename S, typename... Args>
  352. inline void log_error(const S& format_str, Args&&... args)
  353. {
  354. if (logger::severity_level::error >= this->level_)
  355. {
  356. this->log(logger::severity_level::error, format_str, std::forward<Args>(args)...);
  357. }
  358. }
  359. template <typename S, typename... Args>
  360. inline void log_fatal(const S& format_str, Args&&... args)
  361. {
  362. if (logger::severity_level::fatal >= this->level_)
  363. {
  364. this->log(logger::severity_level::fatal, format_str, std::forward<Args>(args)...);
  365. }
  366. }
  367. template <typename S, typename... Args>
  368. inline void log_report(const S& format_str, Args&&... args)
  369. {
  370. if (logger::severity_level::report >= this->level_)
  371. {
  372. this->log(logger::severity_level::report, format_str, std::forward<Args>(args)...);
  373. }
  374. }
  375. template <typename S, typename... Args>
  376. void log(severity_level level, const S& format_str, Args&&... args)
  377. {
  378. std::lock_guard<std::mutex> g(this->mutex_);
  379. if (level < this->level_)
  380. return;
  381. std::time_t t = std::time(nullptr);
  382. std::string content;
  383. if constexpr (sizeof...(Args) == 0)
  384. {
  385. content =
  386. fmt::format("[{}] [{:%Y-%m-%d %H:%M:%S}] ", level2severity(level), *std::localtime(&t)) +
  387. fmt::format("{}", format_str) +
  388. this->endl_;
  389. }
  390. else
  391. {
  392. content =
  393. fmt::format("[{}] [{:%Y-%m-%d %H:%M:%S}] ", level2severity(level), *std::localtime(&t)) +
  394. fmt::vformat(format_str, fmt::make_format_args(std::forward<Args>(args)...)) +
  395. this->endl_;
  396. }
  397. // call user defined target function
  398. if (this->target_)
  399. {
  400. this->target_(content);
  401. }
  402. // print log into the console window
  403. if ((this->dest_ & dest::dest_mask) & console)
  404. {
  405. // don't use std::cout and printf together.
  406. //std::cout << content << std::endl;
  407. //std::printf("%.*s", static_cast<int>(content.size()), content.data());
  408. fmt::print("{}", content);
  409. }
  410. // save log into the file
  411. if ((this->dest_ & dest::dest_mask) & file)
  412. {
  413. if (this->file_.is_open())
  414. {
  415. this->file_.write(content.data(), content.size());
  416. this->size_ += content.size();
  417. // if file size is too large,close this file,and create a new file.
  418. if (this->size_ > this->roll_size_)
  419. {
  420. #if defined(ASIO2_LOGGER_MULTI_MODULE)
  421. this->mkflag_ = true;
  422. while (this->mkflag_)
  423. {
  424. std::this_thread::sleep_for(std::chrono::milliseconds(1));
  425. this->cv_.notify_one();
  426. }
  427. #endif
  428. this->mkfile();
  429. }
  430. }
  431. }
  432. }
  433. inline void flush()
  434. {
  435. std::lock_guard<std::mutex> g(this->mutex_);
  436. if (this->file_.is_open())
  437. {
  438. this->file_.flush();
  439. }
  440. }
  441. protected:
  442. logger & mkfile()
  443. {
  444. if (this->filename_.empty())
  445. {
  446. this->filename_ = std::filesystem::current_path().string();
  447. if (this->filename_.back() != '\\' && this->filename_.back() != '/')
  448. this->filename_ += this->preferred_;
  449. this->filename_ += "log";
  450. this->filename_ += this->preferred_;
  451. std::filesystem::create_directories(std::filesystem::path(this->filename_));
  452. time_t t;
  453. time(&t); /* Get time in seconds */
  454. struct tm tm = *localtime(&t); /* Convert time to struct */
  455. char tmbuf[20] = { 0 };
  456. strftime(tmbuf, 20, "%Y-%m-%d %H.%M.%S", std::addressof(tm));
  457. this->filename_ += tmbuf;
  458. this->filename_ += ".log";
  459. }
  460. else
  461. {
  462. // Compatible with three file name parameters :
  463. // abc.log
  464. // D:\log\abc.log
  465. // /usr/log/abc.log
  466. if (std::filesystem::is_directory(filename_))
  467. {
  468. if (this->filename_.back() != '\\' && this->filename_.back() != '/')
  469. this->filename_ += this->preferred_;
  470. this->filename_ += "log";
  471. this->filename_ += this->preferred_;
  472. std::filesystem::create_directories(std::filesystem::path(this->filename_));
  473. time_t t;
  474. time(&t); /* Get time in seconds */
  475. struct tm tm = *localtime(&t); /* Convert time to struct */
  476. char tmbuf[20] = { 0 };
  477. strftime(tmbuf, 20, "%Y-%m-%d %H.%M.%S", std::addressof(tm));
  478. this->filename_ += tmbuf;
  479. this->filename_ += ".log";
  480. }
  481. else
  482. {
  483. std::string::size_type slash = this->filename_.find_last_of("\\/");
  484. // abc.log
  485. if (slash == std::string::npos)
  486. {
  487. std::string name = this->filename_;
  488. this->filename_ = std::filesystem::current_path().string();
  489. if (this->filename_.back() != '\\' && this->filename_.back() != '/')
  490. this->filename_ += this->preferred_;
  491. this->filename_ += "log";
  492. this->filename_ += this->preferred_;
  493. std::filesystem::create_directories(std::filesystem::path(this->filename_));
  494. this->filename_ += name;
  495. }
  496. // D:\log\abc.log // /usr/log/abc.log
  497. else
  498. {
  499. std::filesystem::create_directories(
  500. std::filesystem::path(this->filename_.substr(0, slash)));
  501. }
  502. }
  503. }
  504. if (this->file_.is_open())
  505. {
  506. this->file_.flush();
  507. this->file_.close();
  508. }
  509. this->size_ = 0;
  510. this->file_.open(this->filename_,
  511. std::ofstream::binary | std::ofstream::out | std::ofstream::app);
  512. return (*this);
  513. }
  514. private:
  515. /// no copy construct function
  516. logger(const logger&) = delete;
  517. /// no operator equal function
  518. logger& operator=(const logger&) = delete;
  519. protected:
  520. std::string filename_;
  521. severity_level level_ = severity_level::trace;
  522. unsigned int dest_ = console | file;
  523. std::size_t roll_size_ = 1024 * 1024 * 1024;
  524. std::size_t size_ = 0;
  525. std::ofstream file_;
  526. std::string endl_;
  527. std::string preferred_;
  528. std::mutex mutex_;
  529. std::function<void(const std::string & text)> target_;
  530. #if defined(ASIO2_LOGGER_MULTI_MODULE)
  531. std::thread thread_;
  532. std::mutex mtx_;
  533. std::condition_variable cv_;
  534. bool is_stop_ = false;
  535. bool mkflag_ = false;
  536. #endif
  537. };
  538. #if defined(_MSC_VER)
  539. # pragma warning(pop)
  540. #endif
  541. }
  542. #endif // !__ASIO2_LOGGER_HPP__