network_event_log.cc revision 3551c9c881056c480085172ff9840cab31610854
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
21namespace chromeos {
22namespace network_event_log {
23
24namespace {
25
26class NetworkEventLog;
27NetworkEventLog* g_network_event_log = NULL;
28size_t g_max_network_event_log_entries = 4000;
29const char* kLogLevelName[] = {"Error", "User", "Event", "Debug"};
30
31struct 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
61LogEntry::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
74std::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
91void 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
100std::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
111std::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
118std::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
140void 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
152bool 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
159void 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
186typedef std::list<LogEntry> LogEntryList;
187
188class 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
208void 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
241std::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
318const LogLevel kDefaultLogLevel = LOG_LEVEL_EVENT;
319
320void Initialize() {
321  if (g_network_event_log)
322    delete g_network_event_log;  // reset log
323  g_network_event_log = new NetworkEventLog();
324}
325
326void Shutdown() {
327  delete g_network_event_log;
328  g_network_event_log = NULL;
329}
330
331bool IsInitialized() { return g_network_event_log != NULL; }
332
333namespace internal {
334
335size_t GetMaxLogEntries() { return g_max_network_event_log_entries; }
336
337void 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
345void 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
363std::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
372std::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