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