Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/test/chromedriver/logging.h" | 5 #include "chrome/test/chromedriver/logging.h" |
| 6 | 6 |
| 7 #include <stdio.h> | |
| 8 | |
| 7 #include "base/basictypes.h" | 9 #include "base/basictypes.h" |
| 10 #include "base/command_line.h" | |
| 11 #include "base/json/json_reader.h" | |
| 8 #include "base/logging.h" | 12 #include "base/logging.h" |
| 13 #include "base/strings/stringprintf.h" | |
| 9 #include "base/time/time.h" | 14 #include "base/time/time.h" |
| 10 #include "chrome/test/chromedriver/capabilities.h" | 15 #include "chrome/test/chromedriver/capabilities.h" |
| 11 #include "chrome/test/chromedriver/chrome/console_logger.h" | 16 #include "chrome/test/chromedriver/chrome/console_logger.h" |
| 12 #include "chrome/test/chromedriver/chrome/performance_logger.h" | 17 #include "chrome/test/chromedriver/chrome/performance_logger.h" |
| 13 #include "chrome/test/chromedriver/chrome/status.h" | 18 #include "chrome/test/chromedriver/chrome/status.h" |
| 19 #include "chrome/test/chromedriver/session.h" | |
| 20 | |
| 21 #if defined(OS_POSIX) | |
| 22 #include <fcntl.h> | |
| 23 #include <unistd.h> | |
| 24 #endif | |
| 14 | 25 |
| 15 | 26 |
| 16 namespace { | 27 namespace { |
| 17 | 28 |
| 18 // Map between WebDriverLog::WebDriverLevel and its name in WD wire protocol. | 29 Log::Level g_log_level = Log::kWarning; |
| 19 // Array indices are the WebDriverLog::WebDriverLevel enum values. | 30 |
| 20 const char* const kWebDriverLevelNames[] = { | 31 int64 g_start_time = 0; |
| 21 "ALL", "DEBUG", "INFO", "WARNING", "SEVERE", "OFF" | 32 |
| 33 // Array indices are the Log::Level enum values. | |
| 34 const char* kLevelToName[] = { | |
| 35 "ALL", // kAll | |
| 36 "DEBUG", // kDebug | |
| 37 "INFO", // kInfo | |
| 38 "WARNING", // kWarning | |
| 39 "SEVERE", // kError | |
| 40 "OFF", // kOff | |
| 22 }; | 41 }; |
| 23 | 42 |
| 24 // Map between Log::Level and WebDriverLog::WebDriverLevel. | 43 const char* LevelToName(Log::Level level) { |
| 25 // Array indices are the Log::Level enum values. | 44 const int index = level - Log::kAll; |
| 26 WebDriverLog::WebDriverLevel kLogLevelToWebDriverLevels[] = { | 45 CHECK_GE(index, 0); |
| 27 WebDriverLog::kWdDebug, // kDebug | 46 CHECK_LT(static_cast<size_t>(index), arraysize(kLevelToName)); |
| 28 WebDriverLog::kWdInfo, // kLog | 47 return kLevelToName[index]; |
| 29 WebDriverLog::kWdWarning, // kWarning | 48 } |
| 30 WebDriverLog::kWdSevere // kError | 49 |
| 50 struct LevelPair { | |
| 51 const char* name; | |
| 52 Log::Level level; | |
| 31 }; | 53 }; |
| 32 | 54 |
| 33 // Translates Log::Level to WebDriverLog::WebDriverLevel. | 55 const LevelPair kNameToLevel[] = { |
| 34 WebDriverLog::WebDriverLevel LogLevelToWebDriverLevel(Log::Level level) { | 56 {"ALL", Log::kAll}, |
| 35 const int index = level - Log::kDebug; | 57 {"DEBUG", Log::kDebug}, |
| 36 CHECK_GE(index, 0); | 58 {"INFO", Log::kInfo}, |
| 37 CHECK_LT(static_cast<size_t>(index), arraysize(kLogLevelToWebDriverLevels)); | 59 {"WARNING", Log::kWarning}, |
| 38 return kLogLevelToWebDriverLevels[index]; | 60 {"SEVERE", Log::kError}, |
| 61 {"OFF", Log::kOff}, | |
| 62 }; | |
| 63 | |
| 64 Log::Level GetLevelFromSeverity(int severity) { | |
| 65 switch (severity) { | |
| 66 case logging::LOG_FATAL: | |
| 67 case logging::LOG_ERROR_REPORT: | |
| 68 case logging::LOG_ERROR: | |
| 69 return Log::kError; | |
| 70 case logging::LOG_WARNING: | |
| 71 return Log::kWarning; | |
| 72 case logging::LOG_INFO: | |
| 73 return Log::kInfo; | |
| 74 case logging::LOG_VERBOSE: | |
| 75 default: | |
| 76 return Log::kDebug; | |
| 77 } | |
| 39 } | 78 } |
| 40 | 79 |
| 41 // Returns WD wire protocol level name for a WebDriverLog::WebDriverLevel. | 80 bool InternalIsVLogOn(int vlog_level) { |
| 42 std::string GetWebDriverLevelName( | 81 return GetLevelFromSeverity(vlog_level * -1) >= g_log_level; |
| 43 const WebDriverLog::WebDriverLevel level) { | 82 } |
| 44 const int index = level - WebDriverLog::kWdAll; | 83 |
| 45 CHECK_GE(index, 0); | 84 std::string ConvertForDisplayInternal(const std::string& input) { |
| 46 CHECK_LT(static_cast<size_t>(index), arraysize(kWebDriverLevelNames)); | 85 size_t left = input.find("{"); |
| 47 return kWebDriverLevelNames[index]; | 86 size_t right = input.rfind("}"); |
| 87 if (left == std::string::npos || right == std::string::npos) | |
| 88 return input.substr(0, 10 << 10); | |
| 89 | |
| 90 scoped_ptr<base::Value> value( | |
| 91 base::JSONReader::Read(input.substr(left, right - left + 1))); | |
| 92 if (!value) | |
| 93 return input.substr(0, 10 << 10); | |
| 94 std::string display = input.substr(0, left) + | |
| 95 PrettyPrintValue(TruncateContainedStrings(value.get())); | |
| 96 if (input.length() > right) | |
| 97 display += input.substr(right + 1); | |
| 98 return display; | |
| 99 } | |
| 100 | |
| 101 // Pretty prints encapsulated JSON and truncates long strings for display. | |
| 102 std::string ConvertForDisplay(const std::string& input) { | |
| 103 std::string display = ConvertForDisplayInternal(input); | |
| 104 return display; | |
| 105 } | |
| 106 | |
| 107 bool HandleLogMessage(int severity, | |
| 108 const char* file, | |
| 109 int line, | |
| 110 size_t message_start, | |
| 111 const std::string& str) { | |
| 112 Log::Level level = GetLevelFromSeverity(severity); | |
| 113 if (level < g_log_level) | |
| 114 return true; | |
| 115 | |
| 116 std::string message = str.substr(message_start); | |
| 117 const char* level_name = "UNKNOWN"; | |
| 118 switch (level) { | |
| 119 case Log::kDebug: | |
| 120 level_name = "DEBUG"; | |
| 121 break; | |
| 122 case Log::kInfo: | |
| 123 level_name = "INFO"; | |
| 124 break; | |
| 125 case Log::kWarning: | |
| 126 level_name = "WARNING"; | |
| 127 break; | |
| 128 case Log::kError: | |
| 129 level_name = "ERROR"; | |
| 130 break; | |
| 131 default: | |
| 132 break; | |
| 133 } | |
| 134 std::string entry = base::StringPrintf( | |
| 135 "[%.3lf][%s]: %s", | |
| 136 base::TimeDelta(base::TimeTicks::Now() - | |
| 137 base::TimeTicks::FromInternalValue(g_start_time)) | |
| 138 .InSecondsF(), | |
| 139 level_name, | |
| 140 ConvertForDisplay(message).c_str()); | |
| 141 const char* format = "%s\n"; | |
| 142 if (entry[entry.length() - 1] == '\n') | |
| 143 format = "%s"; | |
| 144 | |
| 145 fprintf(stderr, format, entry.c_str()); | |
| 146 fflush(stderr); | |
| 147 return true; | |
| 48 } | 148 } |
| 49 | 149 |
| 50 } // namespace | 150 } // namespace |
| 51 | 151 |
| 52 bool WebDriverLog::NameToLevel( | 152 bool WebDriverLog::NameToLevel(const std::string& name, Log::Level* out_level) { |
| 53 const std::string& name, WebDriverLog::WebDriverLevel* out_level) { | 153 for (size_t i = 0; i < arraysize(kNameToLevel); ++i) { |
| 54 for (size_t i = 0; i < arraysize(kWebDriverLevelNames); ++i) { | 154 if (name == kNameToLevel[i].name) { |
|
chrisgao (Use stgao instead)
2013/09/05 01:20:46
Can it be case insensitive?
kkania
2013/09/05 15:15:11
the spec isn't clear; i'll leave as is.
| |
| 55 if (name == kWebDriverLevelNames[i]) { | 155 *out_level = kNameToLevel[i].level; |
| 56 CHECK_LE(WebDriverLog::kWdAll + i, | |
| 57 static_cast<size_t>(WebDriverLog::kWdOff)); | |
| 58 *out_level = | |
| 59 static_cast<WebDriverLog::WebDriverLevel>(WebDriverLog::kWdAll + i); | |
| 60 return true; | 156 return true; |
| 61 } | 157 } |
| 62 } | 158 } |
| 63 return false; | 159 return false; |
| 64 } | 160 } |
| 65 | 161 |
| 66 WebDriverLog::WebDriverLog( | 162 WebDriverLog::WebDriverLog(const std::string& type, Log::Level min_level) |
| 67 const std::string& type, WebDriverLog::WebDriverLevel min_wd_level) | 163 : type_(type), min_level_(min_level), entries_(new base::ListValue()) { |
| 68 : type_(type), | |
| 69 min_wd_level_(min_wd_level), | |
| 70 entries_(new base::ListValue()) { | |
| 71 VLOG(1) << "Log(" << type_ << ", " << min_wd_level_ << ")"; | |
| 72 } | 164 } |
| 73 | 165 |
| 74 WebDriverLog::~WebDriverLog() { | 166 WebDriverLog::~WebDriverLog() { |
| 75 VLOG(1) << "Log type '" << type_ << "' lost " | 167 VLOG(1) << "Log type '" << type_ << "' lost " |
| 76 << entries_->GetSize() << " entries on destruction"; | 168 << entries_->GetSize() << " entries on destruction"; |
| 77 } | 169 } |
| 78 | 170 |
| 79 const std::string& WebDriverLog::GetType() { | |
| 80 return type_; | |
| 81 } | |
| 82 | |
| 83 void WebDriverLog::AddEntryTimestamped(const base::Time& timestamp, | |
| 84 Log::Level level, | |
| 85 const std::string& message) { | |
| 86 const WebDriverLog::WebDriverLevel wd_level = LogLevelToWebDriverLevel(level); | |
| 87 if (wd_level < min_wd_level_) | |
| 88 return; | |
| 89 scoped_ptr<base::DictionaryValue> log_entry_dict(new base::DictionaryValue()); | |
| 90 log_entry_dict->SetDouble("timestamp", | |
| 91 static_cast<int64>(timestamp.ToJsTime())); | |
| 92 log_entry_dict->SetString("level", GetWebDriverLevelName(wd_level)); | |
| 93 log_entry_dict->SetString("message", message); | |
| 94 entries_->Append(log_entry_dict.release()); | |
| 95 } | |
| 96 | |
| 97 scoped_ptr<base::ListValue> WebDriverLog::GetAndClearEntries() { | 171 scoped_ptr<base::ListValue> WebDriverLog::GetAndClearEntries() { |
| 98 scoped_ptr<base::ListValue> ret(entries_.release()); | 172 scoped_ptr<base::ListValue> ret(entries_.release()); |
| 99 entries_.reset(new base::ListValue()); | 173 entries_.reset(new base::ListValue()); |
| 100 return ret.Pass(); | 174 return ret.Pass(); |
| 101 } | 175 } |
| 102 | 176 |
| 177 void WebDriverLog::AddEntryTimestamped(const base::Time& timestamp, | |
| 178 Log::Level level, | |
| 179 const std::string& message) { | |
| 180 if (level < min_level_) | |
| 181 return; | |
| 182 | |
| 183 scoped_ptr<base::DictionaryValue> log_entry_dict(new base::DictionaryValue()); | |
| 184 log_entry_dict->SetDouble("timestamp", | |
| 185 static_cast<int64>(timestamp.ToJsTime())); | |
| 186 log_entry_dict->SetString("level", LevelToName(level)); | |
| 187 log_entry_dict->SetString("message", message); | |
| 188 entries_->Append(log_entry_dict.release()); | |
| 189 } | |
| 190 | |
| 191 const std::string& WebDriverLog::type() const { | |
| 192 return type_; | |
| 193 } | |
| 194 | |
| 195 Log::Level WebDriverLog::min_level() const { | |
| 196 return min_level_; | |
| 197 } | |
| 198 | |
| 199 bool InitLogging() { | |
| 200 InitLogging(&InternalIsVLogOn); | |
| 201 g_start_time = base::TimeTicks::Now().ToInternalValue(); | |
| 202 | |
| 203 CommandLine* cmd_line = CommandLine::ForCurrentProcess(); | |
| 204 if (cmd_line->HasSwitch("log-path")) { | |
| 205 g_log_level = Log::kInfo; | |
| 206 base::FilePath log_path = cmd_line->GetSwitchValuePath("log-path"); | |
| 207 #if defined(OS_WIN) | |
| 208 FILE* redir_stderr = _wfreopen(log_path.value().c_str(), L"w", stderr); | |
| 209 #else | |
| 210 FILE* redir_stderr = freopen(log_path.value().c_str(), "w", stderr); | |
| 211 #endif | |
| 212 if (!redir_stderr) { | |
| 213 printf("Failed to redirect stderr to log file.\n"); | |
| 214 return false; | |
| 215 } | |
| 216 } | |
| 217 if (cmd_line->HasSwitch("silent")) | |
| 218 g_log_level = Log::kOff; | |
| 219 | |
| 220 if (cmd_line->HasSwitch("verbose")) { | |
| 221 g_log_level = Log::kAll; | |
| 222 } else { | |
| 223 #if defined(OS_POSIX) | |
| 224 // Close stderr on exec, so that Chrome log spew doesn't confuse users. | |
| 225 fcntl(STDERR_FILENO, F_SETFD, FD_CLOEXEC); | |
| 226 #endif | |
| 227 } | |
| 228 | |
| 229 cmd_line->AppendSwitchASCII("vmodule", "*/chrome/test/chromedriver/*=3"); | |
|
chrisgao (Use stgao instead)
2013/09/05 01:20:46
How about adding a comment for this one?
kkania
2013/09/05 15:15:11
Done.
| |
| 230 | |
| 231 logging::SetMinLogLevel(logging::LOG_WARNING); | |
| 232 logging::SetLogItems(false, // enable_process_id | |
| 233 false, // enable_thread_id | |
| 234 false, // enable_timestamp | |
| 235 false); // enable_tickcount | |
| 236 logging::SetLogMessageHandler(&HandleLogMessage); | |
| 237 | |
| 238 logging::LoggingSettings logging_settings; | |
| 239 logging_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; | |
| 240 return logging::InitLogging(logging_settings); | |
| 241 } | |
| 242 | |
| 103 Status CreateLogs(const Capabilities& capabilities, | 243 Status CreateLogs(const Capabilities& capabilities, |
| 104 ScopedVector<WebDriverLog>* out_devtools_logs, | 244 ScopedVector<WebDriverLog>* out_logs, |
| 245 scoped_ptr<WebDriverLog>* out_driver_log, | |
| 105 ScopedVector<DevToolsEventListener>* out_listeners) { | 246 ScopedVector<DevToolsEventListener>* out_listeners) { |
| 106 ScopedVector<WebDriverLog> devtools_logs; | 247 ScopedVector<WebDriverLog> logs; |
| 107 ScopedVector<DevToolsEventListener> listeners; | 248 ScopedVector<DevToolsEventListener> listeners; |
| 108 WebDriverLog::WebDriverLevel browser_log_level = WebDriverLog::kWdInfo; | 249 Log::Level browser_log_level = Log::kOff; |
| 250 Log::Level driver_log_level = Log::kWarning; | |
| 251 const LoggingPrefs& prefs = capabilities.logging_prefs; | |
| 109 | 252 |
| 110 if (capabilities.logging_prefs) { | 253 const char kBrowserLogType[] = "browser"; |
| 111 for (DictionaryValue::Iterator pref(*capabilities.logging_prefs); | 254 const char kDriverLogType[] = "driver"; |
| 112 !pref.IsAtEnd(); pref.Advance()) { | 255 for (LoggingPrefs::const_iterator iter = prefs.begin(); |
| 113 const std::string type = pref.key(); | 256 iter != prefs.end(); |
| 114 std::string level_name; | 257 ++iter) { |
| 115 if (!pref.value().GetAsString(&level_name)) { | 258 std::string type = iter->first; |
| 116 return Status(kUnknownError, | 259 Log::Level level = iter->second; |
| 117 "logging level must be a string for log type: " + type); | 260 if (type == "performance") { |
| 261 if (level != Log::kOff) { | |
| 262 WebDriverLog* log = new WebDriverLog(type, Log::kDebug); | |
|
chrisgao (Use stgao instead)
2013/09/05 01:20:46
Why not using |level| but Log::kDebug?
kkania
2013/09/05 15:15:11
the thinking was that perf logging doesn't really
| |
| 263 logs.push_back(log); | |
| 264 listeners.push_back(new PerformanceLogger(log)); | |
| 118 } | 265 } |
| 119 WebDriverLog::WebDriverLevel level = WebDriverLog::kWdOff; | 266 } else if (type == kBrowserLogType) { |
| 120 if (!WebDriverLog::NameToLevel(level_name, &level)) { | 267 browser_log_level = level; |
| 121 return Status(kUnknownError, | 268 } else if (type == kDriverLogType) { |
| 122 "invalid log level \"" + level_name + | 269 driver_log_level = level; |
| 123 "\" for type: " + type); | 270 } else { |
| 124 } | 271 // Driver "should" ignore unrecognized log types, per Selenium tests. |
| 125 if ("performance" == type) { | 272 // For example the Java client passes the "client" log type in the caps, |
| 126 if (WebDriverLog::kWdOff != level) { | 273 // which the server should never provide. |
| 127 WebDriverLog* log = new WebDriverLog(type, WebDriverLog::kWdAll); | 274 LOG(WARNING) << "Ignoring unrecognized log type: " << type; |
| 128 devtools_logs.push_back(log); | |
| 129 listeners.push_back(new PerformanceLogger(log)); | |
| 130 } | |
| 131 } else if ("browser" == type) { | |
| 132 browser_log_level = level; | |
| 133 } else { | |
| 134 // Driver "should" ignore unrecognized log types, per Selenium tests. | |
| 135 // For example the Java client passes the "client" log type in the caps, | |
| 136 // which the server should never provide. | |
| 137 LOG(WARNING) << "Ignoring unrecognized log type: LoggingPrefs." << type; | |
| 138 } | |
| 139 } | 275 } |
| 140 } | 276 } |
| 141 // Create "browser" log -- should always exist. | 277 // Create "browser" log -- should always exist. |
| 142 WebDriverLog* browser_log = new WebDriverLog("browser", browser_log_level); | 278 WebDriverLog* browser_log = |
| 143 devtools_logs.push_back(browser_log); | 279 new WebDriverLog(kBrowserLogType, browser_log_level); |
| 280 logs.push_back(browser_log); | |
| 144 // If the level is OFF, don't even bother listening for DevTools events. | 281 // If the level is OFF, don't even bother listening for DevTools events. |
| 145 if (browser_log_level != WebDriverLog::kWdOff) | 282 if (browser_log_level != Log::kOff) |
| 146 listeners.push_back(new ConsoleLogger(browser_log)); | 283 listeners.push_back(new ConsoleLogger(browser_log)); |
| 147 | 284 |
| 148 out_devtools_logs->swap(devtools_logs); | 285 // Create "driver" log -- should always exist. |
| 286 scoped_ptr<WebDriverLog> driver_log( | |
| 287 new WebDriverLog(kDriverLogType, driver_log_level)); | |
| 288 | |
| 289 out_logs->swap(logs); | |
| 290 *out_driver_log = driver_log.Pass(); | |
|
chrisgao (Use stgao instead)
2013/09/05 01:20:46
|out_driver_log| is not contained in/owned by |out
kkania
2013/09/05 15:15:11
Updated comment.
| |
| 149 out_listeners->swap(listeners); | 291 out_listeners->swap(listeners); |
| 150 return Status(kOk); | 292 return Status(kOk); |
| 151 } | 293 } |
| OLD | NEW |