win_eventlog_sink.h 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. // Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
  2. // Distributed under the MIT License (http://opensource.org/licenses/MIT)
  3. // Writing to Windows Event Log requires the registry entries below to be present, with the following modifications:
  4. // 1. <log_name> should be replaced with your log name (e.g. your application name)
  5. // 2. <source_name> should be replaced with the specific source name and the key should be duplicated for
  6. // each source used in the application
  7. //
  8. // Since typically modifications of this kind require elevation, it's better to do it as a part of setup procedure.
  9. // The snippet below uses mscoree.dll as the message file as it exists on most of the Windows systems anyway and
  10. // happens to contain the needed resource.
  11. //
  12. // You can also specify a custom message file if needed.
  13. // Please refer to Event Log functions descriptions in MSDN for more details on custom message files.
  14. /*---------------------------------------------------------------------------------------
  15. Windows Registry Editor Version 5.00
  16. [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\<log_name>]
  17. [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\<log_name>\<source_name>]
  18. "TypesSupported"=dword:00000007
  19. "EventMessageFile"=hex(2):25,00,73,00,79,00,73,00,74,00,65,00,6d,00,72,00,6f,\
  20. 00,6f,00,74,00,25,00,5c,00,53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,\
  21. 5c,00,6d,00,73,00,63,00,6f,00,72,00,65,00,65,00,2e,00,64,00,6c,00,6c,00,00,\
  22. 00
  23. -----------------------------------------------------------------------------------------*/
  24. #pragma once
  25. #include <spdlog/details/null_mutex.h>
  26. #include <spdlog/sinks/base_sink.h>
  27. #include <spdlog/details/windows_include.h>
  28. #include <winbase.h>
  29. #include <mutex>
  30. #include <string>
  31. #include <vector>
  32. namespace spdlog {
  33. namespace sinks {
  34. namespace win_eventlog {
  35. namespace internal {
  36. struct local_alloc_t
  37. {
  38. HLOCAL hlocal_;
  39. SPDLOG_CONSTEXPR local_alloc_t() SPDLOG_NOEXCEPT : hlocal_(nullptr) {}
  40. local_alloc_t(local_alloc_t const &) = delete;
  41. local_alloc_t &operator=(local_alloc_t const &) = delete;
  42. ~local_alloc_t() SPDLOG_NOEXCEPT
  43. {
  44. if (hlocal_)
  45. {
  46. LocalFree(hlocal_);
  47. }
  48. }
  49. };
  50. /** Windows error */
  51. struct win32_error : public spdlog_ex
  52. {
  53. /** Formats an error report line: "user-message: error-code (system message)" */
  54. static std::string format(std::string const &user_message, DWORD error_code = GetLastError())
  55. {
  56. std::string system_message;
  57. local_alloc_t format_message_result{};
  58. auto format_message_succeeded =
  59. ::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
  60. error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&format_message_result.hlocal_, 0, nullptr);
  61. if (format_message_succeeded && format_message_result.hlocal_)
  62. {
  63. system_message = fmt_lib::format(" ({})", (LPSTR)format_message_result.hlocal_);
  64. }
  65. return fmt_lib::format("{}: {}{}", user_message, error_code, system_message);
  66. }
  67. explicit win32_error(std::string const &func_name, DWORD error = GetLastError())
  68. : spdlog_ex(format(func_name, error))
  69. {}
  70. };
  71. /** Wrapper for security identifiers (SID) on Windows */
  72. struct sid_t
  73. {
  74. std::vector<char> buffer_;
  75. public:
  76. sid_t() {}
  77. /** creates a wrapped SID copy */
  78. static sid_t duplicate_sid(PSID psid)
  79. {
  80. if (!::IsValidSid(psid))
  81. {
  82. throw_spdlog_ex("sid_t::sid_t(): invalid SID received");
  83. }
  84. auto const sid_length{::GetLengthSid(psid)};
  85. sid_t result;
  86. result.buffer_.resize(sid_length);
  87. if (!::CopySid(sid_length, (PSID)result.as_sid(), psid))
  88. {
  89. SPDLOG_THROW(win32_error("CopySid"));
  90. }
  91. return result;
  92. }
  93. /** Retrieves pointer to the internal buffer contents as SID* */
  94. SID *as_sid() const
  95. {
  96. return buffer_.empty() ? nullptr : (SID *)buffer_.data();
  97. }
  98. /** Get SID for the current user */
  99. static sid_t get_current_user_sid()
  100. {
  101. /* create and init RAII holder for process token */
  102. struct process_token_t
  103. {
  104. HANDLE token_handle_ = INVALID_HANDLE_VALUE;
  105. explicit process_token_t(HANDLE process)
  106. {
  107. if (!::OpenProcessToken(process, TOKEN_QUERY, &token_handle_))
  108. {
  109. SPDLOG_THROW(win32_error("OpenProcessToken"));
  110. }
  111. }
  112. ~process_token_t()
  113. {
  114. ::CloseHandle(token_handle_);
  115. }
  116. } current_process_token(::GetCurrentProcess()); // GetCurrentProcess returns pseudohandle, no leak here!
  117. // Get the required size, this is expected to fail with ERROR_INSUFFICIENT_BUFFER and return the token size
  118. DWORD tusize = 0;
  119. if (::GetTokenInformation(current_process_token.token_handle_, TokenUser, NULL, 0, &tusize))
  120. {
  121. SPDLOG_THROW(win32_error("GetTokenInformation should fail"));
  122. }
  123. // get user token
  124. std::vector<unsigned char> buffer(static_cast<size_t>(tusize));
  125. if (!::GetTokenInformation(current_process_token.token_handle_, TokenUser, (LPVOID)buffer.data(), tusize, &tusize))
  126. {
  127. SPDLOG_THROW(win32_error("GetTokenInformation"));
  128. }
  129. // create a wrapper of the SID data as stored in the user token
  130. return sid_t::duplicate_sid(((TOKEN_USER *)buffer.data())->User.Sid);
  131. }
  132. };
  133. struct eventlog
  134. {
  135. static WORD get_event_type(details::log_msg const &msg)
  136. {
  137. switch (msg.level)
  138. {
  139. case level::trace:
  140. case level::debug:
  141. return EVENTLOG_SUCCESS;
  142. case level::info:
  143. return EVENTLOG_INFORMATION_TYPE;
  144. case level::warn:
  145. return EVENTLOG_WARNING_TYPE;
  146. case level::err:
  147. case level::critical:
  148. case level::off:
  149. return EVENTLOG_ERROR_TYPE;
  150. default:
  151. return EVENTLOG_INFORMATION_TYPE;
  152. }
  153. }
  154. static WORD get_event_category(details::log_msg const &msg)
  155. {
  156. return (WORD)msg.level;
  157. }
  158. };
  159. } // namespace internal
  160. /*
  161. * Windows Event Log sink
  162. */
  163. template<typename Mutex>
  164. class win_eventlog_sink : public base_sink<Mutex>
  165. {
  166. private:
  167. HANDLE hEventLog_{NULL};
  168. internal::sid_t current_user_sid_;
  169. std::string source_;
  170. DWORD event_id_;
  171. HANDLE event_log_handle()
  172. {
  173. if (!hEventLog_)
  174. {
  175. hEventLog_ = ::RegisterEventSourceA(nullptr, source_.c_str());
  176. if (!hEventLog_ || hEventLog_ == (HANDLE)ERROR_ACCESS_DENIED)
  177. {
  178. SPDLOG_THROW(internal::win32_error("RegisterEventSource"));
  179. }
  180. }
  181. return hEventLog_;
  182. }
  183. protected:
  184. void sink_it_(const details::log_msg &msg) override
  185. {
  186. using namespace internal;
  187. bool succeeded;
  188. memory_buf_t formatted;
  189. base_sink<Mutex>::formatter_->format(msg, formatted);
  190. formatted.push_back('\0');
  191. #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
  192. wmemory_buf_t buf;
  193. details::os::utf8_to_wstrbuf(string_view_t(formatted.data(), formatted.size()), buf);
  194. LPCWSTR lp_wstr = buf.data();
  195. succeeded = static_cast<bool>(::ReportEventW(event_log_handle(), eventlog::get_event_type(msg), eventlog::get_event_category(msg),
  196. event_id_, current_user_sid_.as_sid(), 1, 0, &lp_wstr, nullptr));
  197. #else
  198. LPCSTR lp_str = formatted.data();
  199. succeeded = static_cast<bool>(::ReportEventA(event_log_handle(), eventlog::get_event_type(msg), eventlog::get_event_category(msg),
  200. event_id_, current_user_sid_.as_sid(), 1, 0, &lp_str, nullptr));
  201. #endif
  202. if (!succeeded)
  203. {
  204. SPDLOG_THROW(win32_error("ReportEvent"));
  205. }
  206. }
  207. void flush_() override {}
  208. public:
  209. win_eventlog_sink(std::string const &source, DWORD event_id = 1000 /* according to mscoree.dll */)
  210. : source_(source)
  211. , event_id_(event_id)
  212. {
  213. try
  214. {
  215. current_user_sid_ = internal::sid_t::get_current_user_sid();
  216. }
  217. catch (...)
  218. {
  219. // get_current_user_sid() is unlikely to fail and if it does, we can still proceed without
  220. // current_user_sid but in the event log the record will have no user name
  221. }
  222. }
  223. ~win_eventlog_sink()
  224. {
  225. if (hEventLog_)
  226. DeregisterEventSource(hEventLog_);
  227. }
  228. };
  229. } // namespace win_eventlog
  230. using win_eventlog_sink_mt = win_eventlog::win_eventlog_sink<std::mutex>;
  231. using win_eventlog_sink_st = win_eventlog::win_eventlog_sink<details::null_mutex>;
  232. } // namespace sinks
  233. } // namespace spdlog