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/bind.h"
12 #include "base/containers/adapters.h"
13 #include "base/json/json_string_value_serializer.h"
14 #include "base/json/json_writer.h"
15 #include "base/location.h"
16 #include "base/logging.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/strings/string_tokenizer.h"
19 #include "base/strings/string_util.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/values.h"
23 #include "net/base/escape.h"
25 namespace device_event_log
{
29 const char* kLogLevelName
[] = {"Error", "User", "Event", "Debug"};
31 const char* kLogTypeNetworkDesc
= "Network";
32 const char* kLogTypePowerDesc
= "Power";
33 const char* kLogTypeLoginDesc
= "Login";
34 const char* kLogTypeUsbDesc
= "USB";
35 const char* kLogTypeHidDesc
= "HID";
37 std::string
GetLogTypeString(LogType type
) {
39 case LOG_TYPE_NETWORK
:
40 return kLogTypeNetworkDesc
;
42 return kLogTypePowerDesc
;
44 return kLogTypeLoginDesc
;
46 return kLogTypeUsbDesc
;
48 return kLogTypeHidDesc
;
55 std::string
DateAndTimeWithMicroseconds(const base::Time
& time
) {
56 base::Time::Exploded exploded
;
57 time
.LocalExplode(&exploded
);
58 // base::Time::Exploded does not include microseconds, but sometimes we need
59 // microseconds, so append '.' + usecs to the end of the formatted string.
60 int usecs
= static_cast<int>(fmod(time
.ToDoubleT() * 1000000, 1000000));
61 return base::StringPrintf("%04d/%02d/%02d %02d:%02d:%02d.%06d", exploded
.year
,
62 exploded
.month
, exploded
.day_of_month
,
63 exploded
.hour
, exploded
.minute
, exploded
.second
,
67 std::string
TimeWithSeconds(const base::Time
& time
) {
68 base::Time::Exploded exploded
;
69 time
.LocalExplode(&exploded
);
70 return base::StringPrintf("%02d:%02d:%02d", exploded
.hour
, exploded
.minute
,
74 std::string
TimeWithMillieconds(const base::Time
& time
) {
75 base::Time::Exploded exploded
;
76 time
.LocalExplode(&exploded
);
77 return base::StringPrintf("%02d:%02d:%02d.%03d", exploded
.hour
,
78 exploded
.minute
, exploded
.second
,
79 exploded
.millisecond
);
82 // Defined below for easier review. TODO(stevenjb): Move implementation here.
83 std::string
GetHtmlText(LogLevel log_level
, const std::string
& event
);
85 std::string
LogEntryToString(const DeviceEventLogImpl::LogEntry
& log_entry
,
93 line
+= "[" + TimeWithMillieconds(log_entry
.time
) + "] ";
95 line
+= GetLogTypeString(log_entry
.log_type
) + ": ";
97 const char* kLevelDesc
[] = {"ERROR", "USER", "EVENT", "DEBUG"};
98 line
+= base::StringPrintf("%s: ", kLevelDesc
[log_entry
.log_level
]);
101 std::string filestr
=
102 format_html
? net::EscapeForHTML(log_entry
.file
) : log_entry
.file
;
103 line
+= base::StringPrintf("%s:%d ", log_entry
.file
.c_str(),
104 log_entry
.file_line
);
106 line
+= format_html
? GetHtmlText(log_entry
.log_level
, log_entry
.event
)
108 if (log_entry
.count
> 1)
109 line
+= base::StringPrintf(" (%d)", log_entry
.count
);
113 void LogEntryToDictionary(const DeviceEventLogImpl::LogEntry
& log_entry
,
114 base::DictionaryValue
* output
) {
115 output
->SetString("timestamp", DateAndTimeWithMicroseconds(log_entry
.time
));
116 output
->SetString("timestampshort", TimeWithSeconds(log_entry
.time
));
117 output
->SetString("level", kLogLevelName
[log_entry
.log_level
]);
118 output
->SetString("type", GetLogTypeString(log_entry
.log_type
));
119 output
->SetString("file", base::StringPrintf("%s:%d ", log_entry
.file
.c_str(),
120 log_entry
.file_line
));
121 output
->SetString("event", log_entry
.event
);
124 std::string
LogEntryAsJSON(const DeviceEventLogImpl::LogEntry
& log_entry
) {
125 base::DictionaryValue entry_dict
;
126 LogEntryToDictionary(log_entry
, &entry_dict
);
128 JSONStringValueSerializer
serializer(&json
);
129 if (!serializer
.Serialize(entry_dict
)) {
130 LOG(ERROR
) << "Failed to serialize to JSON";
135 std::string
GetHtmlText(LogLevel log_level
, const std::string
& event
) {
137 if (log_level
== LOG_LEVEL_DEBUG
)
139 else if (log_level
== LOG_LEVEL_USER
)
141 else if (log_level
== LOG_LEVEL_ERROR
)
144 text
+= net::EscapeForHTML(event
);
146 if (log_level
== LOG_LEVEL_DEBUG
)
148 else if (log_level
== LOG_LEVEL_USER
)
150 else if (log_level
== LOG_LEVEL_ERROR
)
155 void SendLogEntryToVLogOrErrorLog(
156 const DeviceEventLogImpl::LogEntry
& log_entry
) {
157 if (log_entry
.log_level
!= LOG_LEVEL_ERROR
&& !VLOG_IS_ON(1))
159 const bool show_time
= true;
160 const bool show_file
= true;
161 const bool show_type
= true;
162 const bool show_level
= log_entry
.log_level
!= LOG_LEVEL_ERROR
;
163 const bool format_html
= false;
164 std::string output
= LogEntryToString(log_entry
, show_time
, show_file
,
165 show_type
, show_level
, format_html
);
166 if (log_entry
.log_level
== LOG_LEVEL_ERROR
)
167 LOG(ERROR
) << output
;
172 bool LogEntryMatches(const DeviceEventLogImpl::LogEntry
& first
,
173 const DeviceEventLogImpl::LogEntry
& second
) {
174 return first
.file
== second
.file
&& first
.file_line
== second
.file_line
&&
175 first
.log_level
== second
.log_level
&&
176 first
.log_type
== second
.log_type
&& first
.event
== second
.event
;
179 bool LogEntryMatchesTypes(const DeviceEventLogImpl::LogEntry
& entry
,
180 const std::set
<LogType
>& include_types
,
181 const std::set
<LogType
>& exclude_types
) {
182 if (include_types
.empty() && exclude_types
.empty())
184 if (!include_types
.empty() && include_types
.count(entry
.log_type
))
186 if (!exclude_types
.empty() && !exclude_types
.count(entry
.log_type
))
191 void GetFormat(const std::string
& format_string
,
198 base::StringTokenizer
tokens(format_string
, ",");
203 *format_html
= false;
204 *format_json
= false;
205 while (tokens
.GetNext()) {
206 std::string
tok(tokens
.token());
222 LogType
LogTypeFromString(const std::string
& desc
) {
223 std::string desc_lc
= base::ToLowerASCII(desc
);
224 if (desc_lc
== "network")
225 return LOG_TYPE_NETWORK
;
226 if (desc_lc
== "power")
227 return LOG_TYPE_POWER
;
228 if (desc_lc
== "login")
229 return LOG_TYPE_LOGIN
;
230 NOTREACHED() << "Unrecogized LogType: " << desc
;
231 return LOG_TYPE_UNKNOWN
;
234 void GetLogTypes(const std::string
& types
,
235 std::set
<LogType
>* include_types
,
236 std::set
<LogType
>* exclude_types
) {
237 base::StringTokenizer
tokens(types
, ",");
238 while (tokens
.GetNext()) {
239 std::string
tok(tokens
.token());
240 if (tok
.substr(0, 4) == "non-") {
241 LogType type
= LogTypeFromString(tok
.substr(4));
242 if (type
!= LOG_TYPE_UNKNOWN
)
243 exclude_types
->insert(type
);
245 LogType type
= LogTypeFromString(tok
);
246 if (type
!= LOG_TYPE_UNKNOWN
)
247 include_types
->insert(type
);
252 // Update count and time for identical events to avoid log spam.
253 void IncreaseLogEntryCount(const DeviceEventLogImpl::LogEntry
& new_entry
,
254 DeviceEventLogImpl::LogEntry
* cur_entry
) {
256 cur_entry
->log_level
= std::min(cur_entry
->log_level
, new_entry
.log_level
);
257 cur_entry
->time
= base::Time::Now();
263 void DeviceEventLogImpl::SendToVLogOrErrorLog(const char* file
,
267 const std::string
& event
) {
268 LogEntry
entry(file
, file_line
, log_type
, log_level
, event
);
269 SendLogEntryToVLogOrErrorLog(entry
);
272 DeviceEventLogImpl::DeviceEventLogImpl(
273 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
,
275 : task_runner_(task_runner
),
276 max_entries_(max_entries
),
277 weak_ptr_factory_(this) {
278 DCHECK(task_runner_
);
281 DeviceEventLogImpl::~DeviceEventLogImpl() {
284 void DeviceEventLogImpl::AddEntry(const char* file
,
288 const std::string
& event
) {
289 LogEntry
entry(file
, file_line
, log_type
, log_level
, event
);
290 if (!task_runner_
->RunsTasksOnCurrentThread()) {
291 task_runner_
->PostTask(FROM_HERE
,
292 base::Bind(&DeviceEventLogImpl::AddLogEntry
,
293 weak_ptr_factory_
.GetWeakPtr(), entry
));
299 void DeviceEventLogImpl::AddLogEntry(const LogEntry
& entry
) {
300 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
301 if (!entries_
.empty()) {
302 LogEntry
& last
= entries_
.back();
303 if (LogEntryMatches(last
, entry
)) {
304 IncreaseLogEntryCount(entry
, &last
);
308 if (entries_
.size() >= max_entries_
)
310 entries_
.push_back(entry
);
311 SendLogEntryToVLogOrErrorLog(entry
);
314 void DeviceEventLogImpl::RemoveEntry() {
315 const size_t max_error_entries
= max_entries_
/ 2;
316 DCHECK(max_error_entries
< entries_
.size());
317 // Remove the first (oldest) non-error entry, or the oldest entry if more
318 // than half the entries are errors.
319 size_t error_count
= 0;
320 for (LogEntryList::iterator iter
= entries_
.begin(); iter
!= entries_
.end();
322 if (iter
->log_level
!= LOG_LEVEL_ERROR
) {
323 entries_
.erase(iter
);
326 if (++error_count
> max_error_entries
)
329 // Too many error entries, remove the oldest entry.
330 entries_
.pop_front();
333 std::string
DeviceEventLogImpl::GetAsString(StringOrder order
,
334 const std::string
& format
,
335 const std::string
& types
,
338 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
339 if (entries_
.empty())
340 return "No Log Entries.";
342 bool show_time
, show_file
, show_type
, show_level
, format_html
, format_json
;
343 GetFormat(format
, &show_time
, &show_file
, &show_type
, &show_level
,
344 &format_html
, &format_json
);
346 std::set
<LogType
> include_types
, exclude_types
;
347 GetLogTypes(types
, &include_types
, &exclude_types
);
350 base::ListValue log_entries
;
351 if (order
== OLDEST_FIRST
) {
353 if (max_events
> 0 && max_events
< entries_
.size()) {
354 // Iterate backwards through the list skipping uninteresting entries to
355 // determine the first entry to include.
356 size_t shown_events
= 0;
357 size_t num_entries
= 0;
358 for (const LogEntry
& entry
: base::Reversed(entries_
)) {
360 if (!LogEntryMatchesTypes(entry
, include_types
, exclude_types
))
362 if (entry
.log_level
> max_level
)
364 if (++shown_events
>= max_events
)
367 offset
= entries_
.size() - num_entries
;
369 for (const LogEntry
& entry
: entries_
) {
374 if (!LogEntryMatchesTypes(entry
, include_types
, exclude_types
))
376 if (entry
.log_level
> max_level
)
379 log_entries
.AppendString(LogEntryAsJSON(entry
));
381 result
+= LogEntryToString(entry
, show_time
, show_file
, show_type
,
382 show_level
, format_html
);
388 // Iterate backwards through the list to show the most recent entries first.
389 for (const LogEntry
& entry
: base::Reversed(entries_
)) {
390 if (!LogEntryMatchesTypes(entry
, include_types
, exclude_types
))
392 if (entry
.log_level
> max_level
)
395 log_entries
.AppendString(LogEntryAsJSON(entry
));
397 result
+= LogEntryToString(entry
, show_time
, show_file
, show_type
,
398 show_level
, format_html
);
401 if (max_events
> 0 && ++nlines
>= max_events
)
406 JSONStringValueSerializer
serializer(&result
);
407 serializer
.Serialize(log_entries
);
413 DeviceEventLogImpl::LogEntry::LogEntry(const char* filedesc
,
417 const std::string
& event
)
418 : file_line(file_line
),
420 log_level(log_level
),
422 time(base::Time::Now()),
426 size_t last_slash_pos
= file
.find_last_of("\\/");
427 if (last_slash_pos
!= std::string::npos
) {
428 file
.erase(0, last_slash_pos
+ 1);
433 } // namespace device_event_log