syslogs_provider.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/browser/chromeos/system/syslogs_provider.h" 6 7 8#include "base/bind.h" 9#include "base/bind_helpers.h" 10#include "base/command_line.h" 11#include "base/file_path.h" 12#include "base/file_util.h" 13#include "base/logging.h" 14#include "base/memory/scoped_ptr.h" 15#include "base/memory/singleton.h" 16#include "base/string_util.h" 17#include "chrome/browser/memory_details.h" 18#include "chrome/common/chrome_switches.h" 19#include "content/public/browser/browser_thread.h" 20 21using content::BrowserThread; 22 23namespace chromeos { 24namespace system { 25namespace { 26 27const char kSysLogsScript[] = 28 "/usr/share/userfeedback/scripts/sysinfo_script_runner"; 29const char kBzip2Command[] = 30 "/bin/bzip2"; 31const char kMultilineQuote[] = "\"\"\""; 32const char kNewLineChars[] = "\r\n"; 33const char kInvalidLogEntry[] = "<invalid characters in log entry>"; 34const char kEmptyLogEntry[] = "<no value>"; 35 36const char kContextFeedback[] = "feedback"; 37const char kContextSysInfo[] = "sysinfo"; 38const char kContextNetwork[] = "network"; 39 40// Reads a key from the input string erasing the read values + delimiters read 41// from the initial string 42std::string ReadKey(std::string* data) { 43 size_t equal_sign = data->find("="); 44 if (equal_sign == std::string::npos) 45 return std::string(""); 46 std::string key = data->substr(0, equal_sign); 47 data->erase(0, equal_sign); 48 if (data->size() > 0) { 49 // erase the equal to sign also 50 data->erase(0,1); 51 return key; 52 } 53 return std::string(); 54} 55 56// Reads a value from the input string; erasing the read values from 57// the initial string; detects if the value is multiline and reads 58// accordingly 59std::string ReadValue(std::string* data) { 60 // Trim the leading spaces and tabs. In order to use a multi-line 61 // value, you have to place the multi-line quote on the same line as 62 // the equal sign. 63 // 64 // Why not use TrimWhitespace? Consider the following input: 65 // 66 // KEY1= 67 // KEY2=VALUE 68 // 69 // If we use TrimWhitespace, we will incorrectly trim the new line 70 // and assume that KEY1's value is "KEY2=VALUE" rather than empty. 71 TrimString(*data, " \t", data); 72 73 // If multiline value 74 if (StartsWithASCII(*data, std::string(kMultilineQuote), false)) { 75 data->erase(0, strlen(kMultilineQuote)); 76 size_t next_multi = data->find(kMultilineQuote); 77 if (next_multi == std::string::npos) { 78 // Error condition, clear data to stop further processing 79 data->erase(); 80 return std::string(); 81 } 82 std::string value = data->substr(0, next_multi); 83 data->erase(0, next_multi + 3); 84 return value; 85 } else { // single line value 86 size_t endl_pos = data->find_first_of(kNewLineChars); 87 // if we don't find a new line, we just return the rest of the data 88 std::string value = data->substr(0, endl_pos); 89 data->erase(0, endl_pos); 90 return value; 91 } 92} 93 94// Returns a map of system log keys and values. 95// 96// Parameters: 97// temp_filename: This is an out parameter that holds the name of a file in 98// Reads a value from the input string; erasing the read values from 99// the initial string; detects if the value is multiline and reads 100// accordingly 101// /tmp that contains the system logs in a KEY=VALUE format. 102// If this parameter is NULL, system logs are not retained on 103// the filesystem after this call completes. 104// context: This is an in parameter specifying what context should be 105// passed to the syslog collection script; currently valid 106// values are "sysinfo" or "feedback"; in case of an invalid 107// value, the script will currently default to "sysinfo" 108 109LogDictionaryType* GetSystemLogs(FilePath* zip_file_name, 110 const std::string& context) { 111 // Create the temp file, logs will go here 112 FilePath temp_filename; 113 114 if (!file_util::CreateTemporaryFile(&temp_filename)) 115 return NULL; 116 117 std::string cmd = std::string(kSysLogsScript) + " " + context + " >> " + 118 temp_filename.value(); 119 120 // Ignore the return value - if the script execution didn't work 121 // stderr won't go into the output file anyway. 122 if (::system(cmd.c_str()) == -1) 123 LOG(WARNING) << "Command " << cmd << " failed to run"; 124 125 // Compress the logs file if requested. 126 if (zip_file_name) { 127 cmd = std::string(kBzip2Command) + " -c " + temp_filename.value() + " > " + 128 zip_file_name->value(); 129 if (::system(cmd.c_str()) == -1) 130 LOG(WARNING) << "Command " << cmd << " failed to run"; 131 } 132 // Read logs from the temp file 133 std::string data; 134 bool read_success = file_util::ReadFileToString(temp_filename, 135 &data); 136 // if we were using an internal temp file, the user does not need the 137 // logs to stay past the ReadFile call - delete the file 138 file_util::Delete(temp_filename, false); 139 140 if (!read_success) 141 return NULL; 142 143 // Parse the return data into a dictionary 144 LogDictionaryType* logs = new LogDictionaryType(); 145 while (data.length() > 0) { 146 std::string key = ReadKey(&data); 147 TrimWhitespaceASCII(key, TRIM_ALL, &key); 148 if (!key.empty()) { 149 std::string value = ReadValue(&data); 150 if (IsStringUTF8(value)) { 151 TrimWhitespaceASCII(value, TRIM_ALL, &value); 152 if (value.empty()) 153 (*logs)[key] = kEmptyLogEntry; 154 else 155 (*logs)[key] = value; 156 } else { 157 LOG(WARNING) << "Invalid characters in system log entry: " << key; 158 (*logs)[key] = kInvalidLogEntry; 159 } 160 } else { 161 // no more keys, we're done 162 break; 163 } 164 } 165 166 return logs; 167} 168 169} // namespace 170 171class SyslogsProviderImpl : public SyslogsProvider { 172 public: 173 // SyslogsProvider implementation: 174 virtual Handle RequestSyslogs( 175 bool compress_logs, 176 SyslogsContext context, 177 CancelableRequestConsumerBase* consumer, 178 const ReadCompleteCallback& callback); 179 180 // Reads system logs, compresses content if requested. 181 // Called from FILE thread. 182 void ReadSyslogs( 183 scoped_refptr<CancelableRequest<ReadCompleteCallback> > request, 184 bool compress_logs, 185 SyslogsContext context); 186 187 // Loads compressed logs and writes into |zip_content|. 188 void LoadCompressedLogs(const FilePath& zip_file, 189 std::string* zip_content); 190 191 static SyslogsProviderImpl* GetInstance(); 192 193 private: 194 friend struct DefaultSingletonTraits<SyslogsProviderImpl>; 195 196 SyslogsProviderImpl(); 197 198 // Gets syslogs context string from the enum value. 199 const char* GetSyslogsContextString(SyslogsContext context); 200 201 DISALLOW_COPY_AND_ASSIGN(SyslogsProviderImpl); 202}; 203 204SyslogsProviderImpl::SyslogsProviderImpl() { 205} 206 207CancelableRequestProvider::Handle SyslogsProviderImpl::RequestSyslogs( 208 bool compress_logs, 209 SyslogsContext context, 210 CancelableRequestConsumerBase* consumer, 211 const ReadCompleteCallback& callback) { 212 // Register the callback request. 213 scoped_refptr<CancelableRequest<ReadCompleteCallback> > request( 214 new CancelableRequest<ReadCompleteCallback>(callback)); 215 AddRequest(request, consumer); 216 217 // Schedule a task on the FILE thread which will then trigger a request 218 // callback on the calling thread (e.g. UI) when complete. 219 BrowserThread::PostTask( 220 BrowserThread::FILE, FROM_HERE, 221 base::Bind(&SyslogsProviderImpl::ReadSyslogs, base::Unretained(this), 222 request, compress_logs, context)); 223 224 return request->handle(); 225} 226 227// Derived class from memoryDetails converts the results into a single string 228// and adds a "mem_usage" entry to the logs, then forwards the result. 229// Format of entry is (one process per line, reverse-sorted by size): 230// Tab [Title1|Title2]: 50 MB 231// Browser: 30 MB 232// Tab [Title]: 20 MB 233// Extension [Title]: 10 MB 234// ... 235class SyslogsMemoryHandler : public MemoryDetails { 236 public: 237 typedef SyslogsProvider::ReadCompleteCallback ReadCompleteCallback; 238 239 // |logs| is modified (see comment above) and passed to |request|. 240 // |zip_content| is passed to |request|. 241 SyslogsMemoryHandler( 242 scoped_refptr<CancelableRequest<ReadCompleteCallback> > request, 243 LogDictionaryType* logs, 244 std::string* zip_content) 245 : request_(request), 246 logs_(logs), 247 zip_content_(zip_content) { 248 } 249 250 virtual void OnDetailsAvailable() OVERRIDE { 251 (*logs_)["mem_usage"] = ToLogString(); 252 // This will call the callback on the calling thread. 253 request_->ForwardResult(logs_, zip_content_); 254 } 255 256 private: 257 virtual ~SyslogsMemoryHandler() {} 258 259 scoped_refptr<CancelableRequest<ReadCompleteCallback> > request_; 260 LogDictionaryType* logs_; 261 std::string* zip_content_; 262 DISALLOW_COPY_AND_ASSIGN(SyslogsMemoryHandler); 263}; 264 265// Called from FILE thread. 266void SyslogsProviderImpl::ReadSyslogs( 267 scoped_refptr<CancelableRequest<ReadCompleteCallback> > request, 268 bool compress_logs, 269 SyslogsContext context) { 270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 271 272 if (request->canceled()) 273 return; 274 275 // Create temp file. 276 FilePath zip_file; 277 if (compress_logs && !file_util::CreateTemporaryFile(&zip_file)) { 278 LOG(ERROR) << "Cannot create temp file"; 279 compress_logs = false; 280 } 281 282 LogDictionaryType* logs = NULL; 283 logs = GetSystemLogs( 284 compress_logs ? &zip_file : NULL, 285 GetSyslogsContextString(context)); 286 287 std::string* zip_content = NULL; 288 if (compress_logs) { 289 // Load compressed logs. 290 zip_content = new std::string(); 291 LoadCompressedLogs(zip_file, zip_content); 292 file_util::Delete(zip_file, false); 293 } 294 295 // SyslogsMemoryHandler will clean itself up. 296 // SyslogsMemoryHandler::OnDetailsAvailable() will modify |logs| and call 297 // request->ForwardResult(logs, zip_content). 298 scoped_refptr<SyslogsMemoryHandler> 299 handler(new SyslogsMemoryHandler(request, logs, zip_content)); 300 // TODO(jamescook): Maybe we don't need to update histograms here? 301 handler->StartFetch(MemoryDetails::UPDATE_USER_METRICS); 302} 303 304void SyslogsProviderImpl::LoadCompressedLogs(const FilePath& zip_file, 305 std::string* zip_content) { 306 DCHECK(zip_content); 307 if (!file_util::ReadFileToString(zip_file, zip_content)) { 308 LOG(ERROR) << "Cannot read compressed logs file from " << 309 zip_file.value().c_str(); 310 } 311} 312 313const char* SyslogsProviderImpl::GetSyslogsContextString( 314 SyslogsContext context) { 315 switch (context) { 316 case(SYSLOGS_FEEDBACK): 317 return kContextFeedback; 318 case(SYSLOGS_SYSINFO): 319 return kContextSysInfo; 320 case(SYSLOGS_NETWORK): 321 return kContextNetwork; 322 case(SYSLOGS_DEFAULT): 323 return kContextSysInfo; 324 default: 325 NOTREACHED(); 326 return ""; 327 } 328} 329 330SyslogsProviderImpl* SyslogsProviderImpl::GetInstance() { 331 return Singleton<SyslogsProviderImpl, 332 DefaultSingletonTraits<SyslogsProviderImpl> >::get(); 333} 334 335SyslogsProvider* SyslogsProvider::GetInstance() { 336 return SyslogsProviderImpl::GetInstance(); 337} 338 339} // namespace system 340} // namespace chromeos 341