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 "chromeos/network/network_event_log.h" 6 7 #include <list> 8 9 #include "base/files/file_path.h" 10 #include "base/i18n/time_formatting.h" 11 #include "base/json/json_string_value_serializer.h" 12 #include "base/json/json_writer.h" 13 #include "base/logging.h" 14 #include "base/memory/scoped_ptr.h" 15 #include "base/strings/string_tokenizer.h" 16 #include "base/strings/stringprintf.h" 17 #include "base/strings/utf_string_conversions.h" 18 #include "base/values.h" 19 #include "net/base/escape.h" 20 21 namespace chromeos { 22 namespace network_event_log { 23 24 namespace { 25 26 class NetworkEventLog; 27 NetworkEventLog* g_network_event_log = NULL; 28 size_t g_max_network_event_log_entries = 4000; 29 const char* kLogLevelName[] = {"Error", "User", "Event", "Debug"}; 30 31 struct LogEntry { 32 LogEntry(const std::string& file, 33 int file_line, 34 LogLevel log_level, 35 const std::string& event, 36 const std::string& description); 37 38 std::string ToString(bool show_time, 39 bool show_file, 40 bool show_desc, 41 bool format_html) const; 42 void ToDictionary(base::DictionaryValue*) const; 43 44 std::string GetNormalText(bool show_desc) const; 45 std::string GetHtmlText(bool show_desc) const; 46 std::string GetAsJSON() const; 47 48 void SendToVLogOrErrorLog() const; 49 50 bool ContentEquals(const LogEntry& other) const; 51 52 std::string file; 53 int file_line; 54 LogLevel log_level; 55 std::string event; 56 std::string description; 57 base::Time time; 58 int count; 59 }; 60 61 LogEntry::LogEntry(const std::string& file, 62 int file_line, 63 LogLevel log_level, 64 const std::string& event, 65 const std::string& description) 66 : file(file), 67 file_line(file_line), 68 log_level(log_level), 69 event(event), 70 description(description), 71 time(base::Time::Now()), 72 count(1) {} 73 74 std::string LogEntry::ToString(bool show_time, 75 bool show_file, 76 bool show_desc, 77 bool format_html) const { 78 std::string line; 79 if (show_time) 80 line += "[" + UTF16ToUTF8(base::TimeFormatTimeOfDay(time)) + "] "; 81 if (show_file) { 82 std::string filestr = format_html ? net::EscapeForHTML(file) : file; 83 line += base::StringPrintf("%s:%d ", file.c_str(), file_line); 84 } 85 line += format_html ? GetHtmlText(show_desc) : GetNormalText(show_desc); 86 if (count > 1) 87 line += base::StringPrintf(" (%d)", count); 88 return line; 89 } 90 91 void LogEntry::ToDictionary(base::DictionaryValue* output) const { 92 output->SetString("timestamp", base::TimeFormatShortDateAndTime(time)); 93 output->SetString("level", kLogLevelName[log_level]); 94 output->SetString("file", 95 base::StringPrintf("%s:%d ", file.c_str(), file_line)); 96 output->SetString("event", event); 97 output->SetString("description", description); 98 } 99 100 std::string LogEntry::GetAsJSON() const { 101 base::DictionaryValue entry; 102 ToDictionary(&entry); 103 std::string json; 104 JSONStringValueSerializer serializer(&json); 105 if (!serializer.Serialize(entry)) { 106 LOG(ERROR) << "Failed to serialize to JSON"; 107 } 108 return json; 109 } 110 111 std::string LogEntry::GetNormalText(bool show_desc) const { 112 std::string text = event; 113 if (show_desc && !description.empty()) 114 text += ": " + description; 115 return text; 116 } 117 118 std::string LogEntry::GetHtmlText(bool show_desc) const { 119 std::string text; 120 if (log_level == LOG_LEVEL_DEBUG) 121 text += "<i>"; 122 else if (log_level == LOG_LEVEL_USER) 123 text += "<b>"; 124 else if (log_level == LOG_LEVEL_ERROR) 125 text += "<b><i>"; 126 127 text += event; 128 if (show_desc && !description.empty()) 129 text += ": " + net::EscapeForHTML(description); 130 131 if (log_level == LOG_LEVEL_DEBUG) 132 text += "</i>"; 133 else if (log_level == LOG_LEVEL_USER) 134 text += "</b>"; 135 else if (log_level == LOG_LEVEL_ERROR) 136 text += "</i></b>"; 137 return text; 138 } 139 140 void LogEntry::SendToVLogOrErrorLog() const { 141 const bool show_time = true; 142 const bool show_file = true; 143 const bool show_desc = true; 144 const bool format_html = false; 145 std::string output = ToString(show_time, show_file, show_desc, format_html); 146 if (log_level == LOG_LEVEL_ERROR) 147 LOG(ERROR) << output; 148 else 149 VLOG(1) << output; 150 } 151 152 bool LogEntry::ContentEquals(const LogEntry& other) const { 153 return file == other.file && 154 file_line == other.file_line && 155 event == other.event && 156 description == other.description; 157 } 158 159 void GetFormat(const std::string& format_string, 160 bool* show_time, 161 bool* show_file, 162 bool* show_desc, 163 bool* format_html, 164 bool* format_json) { 165 base::StringTokenizer tokens(format_string, ","); 166 *show_time = false; 167 *show_file = false; 168 *show_desc = false; 169 *format_html = false; 170 *format_json = false; 171 while (tokens.GetNext()) { 172 std::string tok(tokens.token()); 173 if (tok == "time") 174 *show_time = true; 175 if (tok == "file") 176 *show_file = true; 177 if (tok == "desc") 178 *show_desc = true; 179 if (tok == "html") 180 *format_html = true; 181 if (tok == "json") 182 *format_json = true; 183 } 184 } 185 186 typedef std::list<LogEntry> LogEntryList; 187 188 class NetworkEventLog { 189 public: 190 NetworkEventLog() {} 191 ~NetworkEventLog() {} 192 193 void AddLogEntry(const LogEntry& entry); 194 195 std::string GetAsString(StringOrder order, 196 const std::string& format, 197 LogLevel max_level, 198 size_t max_events); 199 200 LogEntryList& entries() { return entries_; } 201 202 private: 203 LogEntryList entries_; 204 205 DISALLOW_COPY_AND_ASSIGN(NetworkEventLog); 206 }; 207 208 void NetworkEventLog::AddLogEntry(const LogEntry& entry) { 209 if (!entries_.empty()) { 210 LogEntry& last = entries_.back(); 211 if (last.ContentEquals(entry)) { 212 // Update count and time for identical events to avoid log spam. 213 ++last.count; 214 last.log_level = std::min(last.log_level, entry.log_level); 215 last.time = base::Time::Now(); 216 return; 217 } 218 } 219 if (entries_.size() >= g_max_network_event_log_entries) { 220 const size_t max_error_entries = g_max_network_event_log_entries / 2; 221 // Remove the first (oldest) non-error entry, or the oldest entry if more 222 // than half the entries are errors. 223 size_t error_count = 0; 224 for (LogEntryList::iterator iter = entries_.begin(); iter != entries_.end(); 225 ++iter) { 226 if (iter->log_level != LOG_LEVEL_ERROR) { 227 entries_.erase(iter); 228 break; 229 } 230 if (++error_count > max_error_entries) { 231 // Too many error entries, remove the oldest entry. 232 entries_.pop_front(); 233 break; 234 } 235 } 236 } 237 entries_.push_back(entry); 238 entry.SendToVLogOrErrorLog(); 239 } 240 241 std::string NetworkEventLog::GetAsString(StringOrder order, 242 const std::string& format, 243 LogLevel max_level, 244 size_t max_events) { 245 if (entries_.empty()) 246 return "No Log Entries."; 247 248 bool show_time, show_file, show_desc, format_html, format_json; 249 GetFormat( 250 format, &show_time, &show_file, &show_desc, &format_html, &format_json); 251 252 std::string result; 253 base::ListValue log_entries; 254 if (order == OLDEST_FIRST) { 255 size_t offset = 0; 256 if (max_events > 0 && max_events < entries_.size()) { 257 // Iterate backwards through the list skipping uninteresting entries to 258 // determine the first entry to include. 259 size_t shown_events = 0; 260 size_t num_entries = 0; 261 for (LogEntryList::const_reverse_iterator riter = entries_.rbegin(); 262 riter != entries_.rend(); 263 ++riter) { 264 ++num_entries; 265 if (riter->log_level > max_level) 266 continue; 267 if (++shown_events >= max_events) 268 break; 269 } 270 offset = entries_.size() - num_entries; 271 } 272 for (LogEntryList::const_iterator iter = entries_.begin(); 273 iter != entries_.end(); 274 ++iter) { 275 if (offset > 0) { 276 --offset; 277 continue; 278 } 279 if (iter->log_level > max_level) 280 continue; 281 if (format_json) { 282 log_entries.AppendString((*iter).GetAsJSON()); 283 } else { 284 result += 285 (*iter).ToString(show_time, show_file, show_desc, format_html); 286 result += "\n"; 287 } 288 } 289 } else { 290 size_t nlines = 0; 291 // Iterate backwards through the list to show the most recent entries first. 292 for (LogEntryList::const_reverse_iterator riter = entries_.rbegin(); 293 riter != entries_.rend(); 294 ++riter) { 295 if (riter->log_level > max_level) 296 continue; 297 if (format_json) { 298 log_entries.AppendString((*riter).GetAsJSON()); 299 } else { 300 result += 301 (*riter).ToString(show_time, show_file, show_desc, format_html); 302 result += "\n"; 303 } 304 if (max_events > 0 && ++nlines >= max_events) 305 break; 306 } 307 } 308 if (format_json) { 309 JSONStringValueSerializer serializer(&result); 310 serializer.Serialize(log_entries); 311 } 312 313 return result; 314 } 315 316 } // namespace 317 318 const LogLevel kDefaultLogLevel = LOG_LEVEL_EVENT; 319 320 void Initialize() { 321 if (g_network_event_log) 322 delete g_network_event_log; // reset log 323 g_network_event_log = new NetworkEventLog(); 324 } 325 326 void Shutdown() { 327 delete g_network_event_log; 328 g_network_event_log = NULL; 329 } 330 331 bool IsInitialized() { return g_network_event_log != NULL; } 332 333 namespace internal { 334 335 size_t GetMaxLogEntries() { return g_max_network_event_log_entries; } 336 337 void SetMaxLogEntries(size_t max_entries) { 338 g_max_network_event_log_entries = max_entries; 339 if (!g_network_event_log) 340 return; 341 while (g_network_event_log->entries().size() > max_entries) 342 g_network_event_log->entries().pop_front(); 343 } 344 345 void AddEntry(const char* file, 346 int file_line, 347 LogLevel log_level, 348 const std::string& event, 349 const std::string& description) { 350 std::string filestr; 351 if (file) 352 filestr = base::FilePath(std::string(file)).BaseName().value(); 353 LogEntry entry(filestr, file_line, log_level, event, description); 354 if (!g_network_event_log) { 355 entry.SendToVLogOrErrorLog(); 356 return; 357 } 358 g_network_event_log->AddLogEntry(entry); 359 } 360 361 } // namespace internal 362 363 std::string GetAsString(StringOrder order, 364 const std::string& format, 365 LogLevel max_level, 366 size_t max_events) { 367 if (!g_network_event_log) 368 return "NetworkEventLog not initialized."; 369 return g_network_event_log->GetAsString(order, format, max_level, max_events); 370 } 371 372 std::string ValueAsString(const base::Value& value) { 373 std::string vstr; 374 base::JSONWriter::WriteWithOptions( 375 &value, base::JSONWriter::OPTIONS_OMIT_BINARY_VALUES, &vstr); 376 return vstr.empty() ? "''" : vstr; 377 } 378 379 } // namespace network_event_log 380 } // namespace chromeos 381