1 // Copyright 2014 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.
5 #include "components/device_event_log/device_event_log_impl.h"
11 #include "base/containers/adapters.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/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "net/base/escape.h"
23 namespace device_event_log
{
27 const char* kLogLevelName
[] = {"Error", "User", "Event", "Debug"};
29 const char* kLogTypeNetworkDesc
= "Network";
30 const char* kLogTypePowerDesc
= "Power";
31 const char* kLogTypeLoginDesc
= "Login";
32 const char* kLogTypeUsbDesc
= "USB";
33 const char* kLogTypeHidDesc
= "HID";
35 std::string
GetLogTypeString(LogType type
) {
37 case LOG_TYPE_NETWORK
:
38 return kLogTypeNetworkDesc
;
40 return kLogTypePowerDesc
;
42 return kLogTypeLoginDesc
;
44 return kLogTypeUsbDesc
;
46 return kLogTypeHidDesc
;
53 std::string
DateAndTimeWithMicroseconds(const base::Time
& time
) {
54 base::Time::Exploded exploded
;
55 time
.LocalExplode(&exploded
);
56 // base::Time::Exploded does not include microseconds, but sometimes we need
57 // microseconds, so append '.' + usecs to the end of the formatted string.
58 int usecs
= static_cast<int>(fmod(time
.ToDoubleT() * 1000000, 1000000));
59 return base::StringPrintf("%04d/%02d/%02d %02d:%02d:%02d.%06d", exploded
.year
,
60 exploded
.month
, exploded
.day_of_month
,
61 exploded
.hour
, exploded
.minute
, exploded
.second
,
65 std::string
TimeWithSeconds(const base::Time
& time
) {
66 base::Time::Exploded exploded
;
67 time
.LocalExplode(&exploded
);
68 return base::StringPrintf("%02d:%02d:%02d", exploded
.hour
, exploded
.minute
,
72 std::string
TimeWithMillieconds(const base::Time
& time
) {
73 base::Time::Exploded exploded
;
74 time
.LocalExplode(&exploded
);
75 return base::StringPrintf("%02d:%02d:%02d.%03d", exploded
.hour
,
76 exploded
.minute
, exploded
.second
,
77 exploded
.millisecond
);
80 // Defined below for easier review. TODO(stevenjb): Move implementation here.
81 std::string
GetHtmlText(LogLevel log_level
, const std::string
& event
);
83 std::string
LogEntryToString(const DeviceEventLogImpl::LogEntry
& log_entry
,
91 line
+= "[" + TimeWithMillieconds(log_entry
.time
) + "] ";
93 line
+= GetLogTypeString(log_entry
.log_type
) + ": ";
95 const char* kLevelDesc
[] = {"ERROR", "USER", "EVENT", "DEBUG"};
96 line
+= base::StringPrintf("%s: ", kLevelDesc
[log_entry
.log_level
]);
100 format_html
? net::EscapeForHTML(log_entry
.file
) : log_entry
.file
;
101 line
+= base::StringPrintf("%s:%d ", log_entry
.file
.c_str(),
102 log_entry
.file_line
);
104 line
+= format_html
? GetHtmlText(log_entry
.log_level
, log_entry
.event
)
106 if (log_entry
.count
> 1)
107 line
+= base::StringPrintf(" (%d)", log_entry
.count
);
111 void LogEntryToDictionary(const DeviceEventLogImpl::LogEntry
& log_entry
,
112 base::DictionaryValue
* output
) {
113 output
->SetString("timestamp", DateAndTimeWithMicroseconds(log_entry
.time
));
114 output
->SetString("timestampshort", TimeWithSeconds(log_entry
.time
));
115 output
->SetString("level", kLogLevelName
[log_entry
.log_level
]);
116 output
->SetString("type", GetLogTypeString(log_entry
.log_type
));
117 output
->SetString("file", base::StringPrintf("%s:%d ", log_entry
.file
.c_str(),
118 log_entry
.file_line
));
119 output
->SetString("event", log_entry
.event
);
122 std::string
LogEntryAsJSON(const DeviceEventLogImpl::LogEntry
& log_entry
) {
123 base::DictionaryValue entry_dict
;
124 LogEntryToDictionary(log_entry
, &entry_dict
);
126 JSONStringValueSerializer
serializer(&json
);
127 if (!serializer
.Serialize(entry_dict
)) {
128 LOG(ERROR
) << "Failed to serialize to JSON";
133 std::string
GetHtmlText(LogLevel log_level
, const std::string
& event
) {
135 if (log_level
== LOG_LEVEL_DEBUG
)
137 else if (log_level
== LOG_LEVEL_USER
)
139 else if (log_level
== LOG_LEVEL_ERROR
)
142 text
+= net::EscapeForHTML(event
);
144 if (log_level
== LOG_LEVEL_DEBUG
)
146 else if (log_level
== LOG_LEVEL_USER
)
148 else if (log_level
== LOG_LEVEL_ERROR
)
153 void SendLogEntryToVLogOrErrorLog(
154 const DeviceEventLogImpl::LogEntry
& log_entry
) {
155 if (log_entry
.log_level
!= LOG_LEVEL_ERROR
&& !VLOG_IS_ON(1))
157 const bool show_time
= true;
158 const bool show_file
= true;
159 const bool show_type
= true;
160 const bool show_level
= false;
161 const bool format_html
= false;
162 std::string output
= LogEntryToString(log_entry
, show_time
, show_file
,
163 show_type
, show_level
, format_html
);
164 if (log_entry
.log_level
== LOG_LEVEL_ERROR
)
165 LOG(ERROR
) << output
;
170 bool LogEntryMatches(const DeviceEventLogImpl::LogEntry
& first
,
171 const DeviceEventLogImpl::LogEntry
& second
) {
172 return first
.file
== second
.file
&& first
.file_line
== second
.file_line
&&
173 first
.log_level
== second
.log_level
&&
174 first
.log_type
== second
.log_type
&& first
.event
== second
.event
;
177 bool LogEntryMatchesTypes(const DeviceEventLogImpl::LogEntry
& entry
,
178 const std::set
<LogType
>& include_types
,
179 const std::set
<LogType
>& exclude_types
) {
180 if (include_types
.empty() && exclude_types
.empty())
182 if (!include_types
.empty() && include_types
.count(entry
.log_type
))
184 if (!exclude_types
.empty() && !exclude_types
.count(entry
.log_type
))
189 void GetFormat(const std::string
& format_string
,
196 base::StringTokenizer
tokens(format_string
, ",");
201 *format_html
= false;
202 *format_json
= false;
203 while (tokens
.GetNext()) {
204 std::string
tok(tokens
.token());
220 LogType
LogTypeFromString(const std::string
& desc
) {
221 std::string desc_lc
= base::StringToLowerASCII(desc
);
222 if (desc_lc
== "network")
223 return LOG_TYPE_NETWORK
;
224 if (desc_lc
== "power")
225 return LOG_TYPE_POWER
;
226 if (desc_lc
== "login")
227 return LOG_TYPE_LOGIN
;
228 NOTREACHED() << "Unrecogized LogType: " << desc
;
229 return LOG_TYPE_UNKNOWN
;
232 void GetLogTypes(const std::string
& types
,
233 std::set
<LogType
>* include_types
,
234 std::set
<LogType
>* exclude_types
) {
235 base::StringTokenizer
tokens(types
, ",");
236 while (tokens
.GetNext()) {
237 std::string
tok(tokens
.token());
238 if (tok
.substr(0, 4) == "non-") {
239 LogType type
= LogTypeFromString(tok
.substr(4));
240 if (type
!= LOG_TYPE_UNKNOWN
)
241 exclude_types
->insert(type
);
243 LogType type
= LogTypeFromString(tok
);
244 if (type
!= LOG_TYPE_UNKNOWN
)
245 include_types
->insert(type
);
253 void DeviceEventLogImpl::SendToVLogOrErrorLog(const char* file
,
257 const std::string
& event
) {
258 LogEntry
entry(file
, file_line
, log_type
, log_level
, event
);
259 SendLogEntryToVLogOrErrorLog(entry
);
262 DeviceEventLogImpl::DeviceEventLogImpl(size_t max_entries
)
263 : max_entries_(max_entries
) {
266 DeviceEventLogImpl::~DeviceEventLogImpl() {
269 void DeviceEventLogImpl::AddEntry(const char* file
,
273 const std::string
& event
) {
274 LogEntry
entry(file
, file_line
, log_type
, log_level
, event
);
278 void DeviceEventLogImpl::AddLogEntry(const LogEntry
& entry
) {
279 if (!entries_
.empty()) {
280 LogEntry
& last
= entries_
.back();
281 if (LogEntryMatches(last
, entry
)) {
282 // Update count and time for identical events to avoid log spam.
284 last
.log_level
= std::min(last
.log_level
, entry
.log_level
);
285 last
.time
= base::Time::Now();
289 if (entries_
.size() >= max_entries_
) {
290 const size_t max_error_entries
= max_entries_
/ 2;
291 // Remove the first (oldest) non-error entry, or the oldest entry if more
292 // than half the entries are errors.
293 size_t error_count
= 0;
294 for (LogEntryList::iterator iter
= entries_
.begin(); iter
!= entries_
.end();
296 if (iter
->log_level
!= LOG_LEVEL_ERROR
) {
297 entries_
.erase(iter
);
300 if (++error_count
> max_error_entries
) {
301 // Too many error entries, remove the oldest entry.
302 entries_
.pop_front();
307 entries_
.push_back(entry
);
308 SendLogEntryToVLogOrErrorLog(entry
);
311 std::string
DeviceEventLogImpl::GetAsString(StringOrder order
,
312 const std::string
& format
,
313 const std::string
& types
,
316 if (entries_
.empty())
317 return "No Log Entries.";
319 bool show_time
, show_file
, show_type
, show_level
, format_html
, format_json
;
320 GetFormat(format
, &show_time
, &show_file
, &show_type
, &show_level
,
321 &format_html
, &format_json
);
323 std::set
<LogType
> include_types
, exclude_types
;
324 GetLogTypes(types
, &include_types
, &exclude_types
);
327 base::ListValue log_entries
;
328 if (order
== OLDEST_FIRST
) {
330 if (max_events
> 0 && max_events
< entries_
.size()) {
331 // Iterate backwards through the list skipping uninteresting entries to
332 // determine the first entry to include.
333 size_t shown_events
= 0;
334 size_t num_entries
= 0;
335 for (const LogEntry
& entry
: base::Reversed(entries_
)) {
337 if (!LogEntryMatchesTypes(entry
, include_types
, exclude_types
))
339 if (entry
.log_level
> max_level
)
341 if (++shown_events
>= max_events
)
344 offset
= entries_
.size() - num_entries
;
346 for (const LogEntry
& entry
: entries_
) {
351 if (!LogEntryMatchesTypes(entry
, include_types
, exclude_types
))
353 if (entry
.log_level
> max_level
)
356 log_entries
.AppendString(LogEntryAsJSON(entry
));
358 result
+= LogEntryToString(entry
, show_time
, show_file
, show_type
,
359 show_level
, format_html
);
365 // Iterate backwards through the list to show the most recent entries first.
366 for (const LogEntry
& entry
: base::Reversed(entries_
)) {
367 if (!LogEntryMatchesTypes(entry
, include_types
, exclude_types
))
369 if (entry
.log_level
> max_level
)
372 log_entries
.AppendString(LogEntryAsJSON(entry
));
374 result
+= LogEntryToString(entry
, show_time
, show_file
, show_type
,
375 show_level
, format_html
);
378 if (max_events
> 0 && ++nlines
>= max_events
)
383 JSONStringValueSerializer
serializer(&result
);
384 serializer
.Serialize(log_entries
);
390 DeviceEventLogImpl::LogEntry::LogEntry(const char* filedesc
,
394 const std::string
& event
)
395 : file_line(file_line
),
397 log_level(log_level
),
399 time(base::Time::Now()),
403 size_t last_slash_pos
= file
.find_last_of("\\/");
404 if (last_slash_pos
!= std::string::npos
) {
405 file
.erase(0, last_slash_pos
+ 1);
410 } // namespace device_event_log