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