OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/test/base/file_logger_win.h" | |
6 | |
7 #include <windows.h> | |
8 | |
9 #include <ios> // For std::hex | |
10 | |
11 #include "base/logging.h" | |
12 #include "base/file_path.h" | |
13 #include "base/logging_win.h" | |
14 #include "base/string16.h" | |
15 #include "base/utf_string_conversions.h" | |
16 #include "base/win/event_trace_consumer.h" | |
17 #include "base/win/registry.h" | |
18 #include "chrome/common/env_vars.h" | |
19 | |
20 namespace { | |
21 | |
22 bool g_is_initialized = false; | |
23 | |
24 const wchar_t kChromeTestSession[] = L"chrome_tests"; | |
25 | |
26 // From chrome_tab.cc: {0562BFC3-2550-45b4-BD8E-A310583D3A6F} | |
27 const GUID kChromeFrameProvider = | |
28 { 0x562bfc3, 0x2550, 0x45b4, | |
29 { 0xbd, 0x8e, 0xa3, 0x10, 0x58, 0x3d, 0x3a, 0x6f } }; | |
30 | |
31 // From chrome/common/logging_chrome.cc: {7FE69228-633E-4f06-80C1-527FEA23E3A7} | |
32 const GUID kChromeTraceProviderName = | |
33 { 0x7fe69228, 0x633e, 0x4f06, | |
34 { 0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7 } }; | |
35 | |
36 // {81729947-CD2A-49e6-8885-785429F339F5} | |
37 const GUID kChromeTestsProvider = | |
38 { 0x81729947, 0xcd2a, 0x49e6, | |
39 { 0x88, 0x85, 0x78, 0x54, 0x29, 0xf3, 0x39, 0xf5 } }; | |
40 | |
41 const GUID* const kDefaultProviders[] = { | |
42 &kChromeTraceProviderName, | |
43 &kChromeFrameProvider, | |
44 &kChromeTestsProvider, | |
45 }; | |
46 | |
47 const HKEY kEnvironmentRoot = HKEY_LOCAL_MACHINE; | |
robertshield
2012/03/02 22:37:41
I take it that this can only be used for tests run
grt (UTC plus 2)
2012/03/03 02:12:43
Yeah, firing up an ETW session requires admin, as
| |
48 const wchar_t kEnvironmentKey[] = | |
49 L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"; | |
50 const wchar_t kEnvironment[] = L"Environment"; | |
51 unsigned int kBroadcastTimeoutMilliseconds = 2 * 1000; | |
52 | |
53 // A helper class that dumps a log file to an output stream. Only one may be | |
54 // in use at a time. | |
55 class LogDumper { | |
56 public: | |
57 // Dump |log_file| to |out|. | |
58 static void DumpLog(const FilePath& log_file, std::ostream& out); | |
59 | |
60 private: | |
61 // An implementation of a trace consumer that sends the message text of | |
62 // events to a given output stream. | |
63 class TraceConsumer : public base::win::EtwTraceConsumerBase<TraceConsumer> { | |
64 public: | |
65 explicit TraceConsumer(std::ostream& out); | |
66 ~TraceConsumer(); | |
67 | |
68 static void ProcessEvent(EVENT_TRACE* event); | |
69 | |
70 private: | |
71 static std::ostream* out_; | |
72 DISALLOW_COPY_AND_ASSIGN(TraceConsumer); | |
73 }; | |
74 | |
75 DISALLOW_IMPLICIT_CONSTRUCTORS(LogDumper); | |
76 }; | |
77 | |
78 // static | |
79 std::ostream* LogDumper::TraceConsumer::out_ = NULL; | |
80 | |
81 LogDumper::TraceConsumer::TraceConsumer(std::ostream& out) { | |
82 DCHECK(out_ == NULL); | |
83 out_ = &out; | |
84 } | |
85 | |
86 LogDumper::TraceConsumer::~TraceConsumer() { | |
87 out_ = NULL; | |
88 } | |
89 | |
90 // static | |
91 void LogDumper::TraceConsumer::ProcessEvent(EVENT_TRACE* event) { | |
92 // The payload for events generated with the ENABLE_LOG_MESSAGE_ONLY and | |
93 // ENABLE_DETAILED_LOG_MESSAGE flags is simply the message text. | |
94 *out_ << reinterpret_cast<const char*>(event->MofData); | |
95 } | |
96 | |
97 // static | |
98 void LogDumper::DumpLog(const FilePath& log_file, std::ostream& out) { | |
99 TraceConsumer consumer(out); | |
100 HRESULT hr = S_OK; | |
101 | |
102 hr = consumer.OpenFileSession(log_file.value().c_str()); | |
103 if (FAILED(hr)) { | |
104 LOG(ERROR) << "Failed to open session for log file " << log_file.value() | |
105 << "; hr=" << std::hex << hr; | |
106 } else { | |
107 consumer.Consume(); | |
108 consumer.Close(); | |
109 out.flush(); | |
110 } | |
111 } | |
112 | |
113 } // namespace | |
114 | |
115 FileLogger::FileLogger() | |
116 : added_chrome_etw_variable_(false) { | |
117 } | |
118 | |
119 FileLogger::~FileLogger() { | |
120 if (!provider_names_.empty()) { | |
121 LOG(WARNING) | |
122 << __FUNCTION__ << " don't forget to call FileLogger::Uninitialize()"; | |
123 Uninitialize(); | |
124 } | |
125 } | |
126 | |
127 void FileLogger::ConfigureChromeEtwLogging() { | |
128 const string16 chrome_etw_logging(ASCIIToWide(env_vars::kEtwLogging)); | |
129 | |
130 // Set the value in this process and its children. | |
131 ::SetEnvironmentVariable(chrome_etw_logging.c_str(), L"1"); | |
132 | |
133 // Set the value for the whole system and ask everyone to refresh. | |
134 base::win::RegKey environment; | |
135 if (environment.Open(kEnvironmentRoot, kEnvironmentKey, | |
136 KEY_QUERY_VALUE | KEY_SET_VALUE) == ERROR_SUCCESS) { | |
137 string16 value; | |
138 if (environment.ReadValue(chrome_etw_logging.c_str(), | |
139 &value) != ERROR_SUCCESS && | |
140 environment.WriteValue(chrome_etw_logging.c_str(), | |
141 L"1") == ERROR_SUCCESS) { | |
142 environment.Close(); | |
143 added_chrome_etw_variable_ = true; | |
144 // Announce to the system that a change has been made so that the shell | |
145 // and other Windowsy bits pick it up; see | |
146 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682653.aspx. | |
147 SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, | |
148 reinterpret_cast<LPARAM>(kEnvironment), | |
149 SMTO_ABORTIFHUNG, | |
150 kBroadcastTimeoutMilliseconds, NULL); | |
151 } | |
152 } | |
153 } | |
154 | |
155 void FileLogger::RevertChromeEtwLogging() { | |
156 if (added_chrome_etw_variable_) { | |
157 base::win::RegKey environment; | |
158 if (environment.Open(kEnvironmentRoot, kEnvironmentKey, | |
159 KEY_SET_VALUE) == ERROR_SUCCESS) { | |
160 environment.DeleteValue(ASCIIToWide(env_vars::kEtwLogging).c_str()); | |
161 environment.Close(); | |
162 SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, | |
163 reinterpret_cast<LPARAM>(kEnvironment), | |
164 SMTO_ABORTIFHUNG, | |
165 kBroadcastTimeoutMilliseconds, NULL); | |
166 } | |
167 added_chrome_etw_variable_ = false; | |
168 } | |
169 } | |
170 | |
171 bool FileLogger::EnableProviders() { | |
172 bool result = true; | |
173 HRESULT hr = S_OK; | |
174 const std::vector<GUID>::const_iterator end(provider_names_.end()); | |
175 for (std::vector<GUID>::const_iterator scan(provider_names_.begin()); | |
176 scan != end; ++scan) { | |
177 hr = controller_.EnableProvider(*scan, 255, | |
178 (logging::ENABLE_LOG_MESSAGE_ONLY | | |
179 logging::ENABLE_DETAILED_LOG_MESSAGE)); | |
180 if (FAILED(hr)) { | |
181 result = false; | |
182 LOG(ERROR) << "Failed to enable event provider " | |
183 << scan - provider_names_.begin() | |
184 << "; hr=" << std::hex << hr; | |
185 } | |
186 } | |
187 return result; | |
188 } | |
189 | |
190 void FileLogger::DisableProviders() { | |
191 HRESULT hr = S_OK; | |
192 const std::vector<GUID>::const_iterator end(provider_names_.end()); | |
193 for (std::vector<GUID>::const_iterator scan(provider_names_.begin()); | |
194 scan != end; ++scan) { | |
195 hr = controller_.DisableProvider(*scan); | |
196 LOG_IF(ERROR, FAILED(hr)) << "Failed to disable event provider " | |
197 << scan - provider_names_.begin() | |
198 << "; hr=" << std::hex << hr; | |
199 } | |
200 } | |
201 | |
202 void FileLogger::Initialize() { | |
203 Initialize(&kDefaultProviders[0], arraysize(kDefaultProviders)); | |
204 } | |
205 | |
206 | |
207 void FileLogger::Initialize(const GUID* const* provider_names, | |
208 size_t provider_count) { | |
209 DCHECK(provider_names || provider_count == 0); | |
210 DCHECK(provider_names_.empty() && !added_chrome_etw_variable_); | |
211 CHECK(!g_is_initialized); | |
212 | |
213 provider_names_.resize(provider_count); | |
214 for (size_t i = 0; i < provider_count; ++i) { | |
215 provider_names_[i] = *provider_names[i]; | |
216 } | |
217 | |
218 ConfigureChromeEtwLogging(); | |
robertshield
2012/03/02 22:37:41
Make this return a value and log if it fails?
grt (UTC plus 2)
2012/03/03 02:12:43
I've added some logging to the implementation. WD
| |
219 | |
220 // Stop a previous session that wasn't shut down properly. | |
221 base::win::EtwTraceProperties ignore; | |
222 HRESULT hr = base::win::EtwTraceController::Stop(kChromeTestSession, | |
223 &ignore); | |
224 LOG_IF(ERROR, FAILED(hr) && | |
225 hr != HRESULT_FROM_WIN32(ERROR_WMI_INSTANCE_NOT_FOUND)) | |
226 << "Failed to stop a previous trace session; hr=" << std::hex << hr; | |
227 | |
228 // Generate ETW log events for this test binary. Log messages at and above | |
229 // logging::GetMinLogLevel() will continue to go to stderr as well. This | |
230 // leads to double logging in case of test failures: each LOG statement at | |
231 // or above the min level will go to stderr during test execution, and then | |
232 // all events logged to the file session will be dumped again. If this | |
233 // turns out to be an issue, one could call logging::SetMinLogLevel(INT_MAX) | |
234 // here (stashing away the previous min log level to be restored in | |
235 // Uninitialize) to suppress stderr logging during test execution. Then those | |
236 // events in the file that were logged at/above the old min level from the | |
237 // test binary could be dumped to stderr if there were no failures. | |
238 logging::LogEventProvider::Initialize(kChromeTestsProvider); | |
239 | |
240 g_is_initialized = true; | |
241 } | |
242 | |
243 void FileLogger::Uninitialize() { | |
244 if (is_logging()) { | |
245 LOG(ERROR) | |
246 << __FUNCTION__ << " don't forget to call FileLogger::StopLogging()"; | |
247 StopLogging(); | |
248 } | |
249 | |
250 logging::LogEventProvider::Uninitialize(); | |
251 | |
252 provider_names_.clear(); | |
253 | |
254 RevertChromeEtwLogging(); | |
255 | |
256 g_is_initialized = false; | |
257 } | |
258 | |
259 bool FileLogger::StartLogging(const FilePath& log_file) { | |
260 HRESULT hr = | |
261 controller_.StartFileSession(kChromeTestSession, | |
262 log_file.value().c_str(), false); | |
263 if (SUCCEEDED(hr)) { | |
264 EnableProviders(); | |
265 } else { | |
266 if (hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)) { | |
267 LOG(WARNING) << "Access denied while trying to start trace session. " | |
268 "This is expected when not running as an administrator."; | |
269 } else { | |
270 LOG(ERROR) << "Failed to start trace session to file " << log_file.value() | |
271 << "; hr=" << std::hex << hr; | |
272 } | |
273 return false; | |
274 } | |
275 return true; | |
276 } | |
277 | |
278 void FileLogger::StopLogging() { | |
279 HRESULT hr = S_OK; | |
280 | |
281 DisableProviders(); | |
282 | |
283 hr = controller_.Flush(NULL); | |
284 LOG_IF(ERROR, FAILED(hr)) | |
285 << "Failed to flush events; hr=" << std::hex << hr; | |
286 hr = controller_.Stop(NULL); | |
287 LOG_IF(ERROR, FAILED(hr)) | |
288 << "Failed to stop ETW session; hr=" << std::hex << hr; | |
289 } | |
290 | |
291 // static | |
292 void FileLogger::DumpLogFile(const FilePath& log_file, std::ostream& out) { | |
293 LogDumper::DumpLog(log_file, out); | |
294 } | |
OLD | NEW |