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