// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "blimp/common/logging.h" #include #include #include #include "base/format_macros.h" #include "base/json/string_escape.h" #include "base/lazy_instance.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "blimp/common/proto/blimp_message.pb.h" namespace blimp { namespace { static base::LazyInstance g_logger = LAZY_INSTANCE_INITIALIZER; // The AddField() suite of functions are used to convert KV pairs with // arbitrarily typed values into string/string KV pairs for logging. // Specialization for string values, surrounding them with quotes and escaping // characters as necessary. void AddField(const std::string& key, const std::string& value, LogFields* output) { std::string escaped_value; base::EscapeJSONString(value, true, &escaped_value); output->push_back(std::make_pair(key, escaped_value)); } // Specialization for string literal values. void AddField(const std::string& key, const char* value, LogFields* output) { output->push_back(std::make_pair(key, std::string(value))); } // Specialization for boolean values (serialized as "true" or "false"). void AddField(const std::string& key, bool value, LogFields* output) { output->push_back(std::make_pair(key, (value ? "true" : "false"))); } // Specialization for SizeMessage values, serializing them as // WIDTHxHEIGHT:RATIO. RATIO is rounded to two digits of precision // (e.g. 2.123 => 2.12). void AddField(const std::string& key, const SizeMessage& value, LogFields* output) { output->push_back(std::make_pair( key, base::StringPrintf("%" PRIu64 "x%" PRIu64 ":%.2lf", value.width(), value.height(), value.device_pixel_ratio()))); } // Conversion function for all other types. // Uses std::to_string() to serialize |value|. template void AddField(const std::string& key, const T& value, LogFields* output) { output->push_back(std::make_pair(key, std::to_string(value))); } // The following LogExtractor subclasses contain logic for extracting loggable // fields from BlimpMessages. // Logs fields from TAB_CONTROL messages. class TabControlLogExtractor : public LogExtractor { void ExtractFields(const BlimpMessage& message, LogFields* output) const override { switch (message.tab_control().type()) { case TabControlMessage::CREATE_TAB: AddField("subtype", "CREATE_TAB", output); break; case TabControlMessage::CLOSE_TAB: AddField("subtype", "CLOSE_TAB", output); break; case TabControlMessage::SIZE: AddField("subtype", "SIZE", output); AddField("size", message.tab_control().size(), output); break; default: // unknown break; } } }; // Logs fields from NAVIGATION messages. class NavigationLogExtractor : public LogExtractor { void ExtractFields(const BlimpMessage& message, LogFields* output) const override { switch (message.navigation().type()) { case NavigationMessage::NAVIGATION_STATE_CHANGED: AddField("subtype", "NAVIGATION_STATE_CHANGED", output); if (message.navigation().navigation_state_changed().has_url()) { AddField("url", message.navigation().navigation_state_changed().url(), output); } if (message.navigation().navigation_state_changed().has_favicon()) { AddField( "favicon_size", message.navigation().navigation_state_changed().favicon().size(), output); } if (message.navigation().navigation_state_changed().has_title()) { AddField("title", message.navigation().navigation_state_changed().title(), output); } if (message.navigation().navigation_state_changed().has_loading()) { AddField("loading", message.navigation().navigation_state_changed().loading(), output); } break; case NavigationMessage::LOAD_URL: AddField("subtype", "LOAD_URL", output); AddField("url", message.navigation().load_url().url(), output); break; case NavigationMessage::GO_BACK: AddField("subtype", "GO_BACK", output); break; case NavigationMessage::GO_FORWARD: AddField("subtype", "GO_FORWARD", output); break; case NavigationMessage::RELOAD: AddField("subtype", "RELOAD", output); break; default: break; } } }; // Logs fields from RENDER_WIDGET messages. class RenderWidgetLogExtractor : public LogExtractor { void ExtractFields(const BlimpMessage& message, LogFields* output) const override { switch (message.render_widget().type()) { case RenderWidgetMessage::INITIALIZE: AddField("subtype", "INITIALIZE", output); break; case RenderWidgetMessage::CREATED: AddField("subtype", "CREATED", output); break; case RenderWidgetMessage::DELETED: AddField("subtype", "DELETED", output); break; } AddField("render_widget_id", message.render_widget().render_widget_id(), output); } }; // Logs fields from PROTOCOL_CONTROL messages. class ProtocolControlLogExtractor : public LogExtractor { void ExtractFields(const BlimpMessage& message, LogFields* output) const override { switch (message.protocol_control().type()) { case ProtocolControlMessage::START_CONNECTION: AddField("subtype", "START_CONNECTION", output); AddField("client_token", message.protocol_control().start_connection().client_token(), output); AddField( "protocol_version", message.protocol_control().start_connection().protocol_version(), output); break; case ProtocolControlMessage::CHECKPOINT_ACK: AddField("subtype", "CHECKPOINT_ACK", output); AddField("checkpoint_id", message.protocol_control().checkpoint_ack().checkpoint_id(), output); break; default: break; } } }; // No fields are extracted from |message|. class NullLogExtractor : public LogExtractor { void ExtractFields(const BlimpMessage& message, LogFields* output) const override {} }; } // namespace BlimpMessageLogger::BlimpMessageLogger() { AddHandler("COMPOSITOR", BlimpMessage::COMPOSITOR, make_scoped_ptr(new NullLogExtractor)); AddHandler("INPUT", BlimpMessage::INPUT, make_scoped_ptr(new NullLogExtractor)); AddHandler("NAVIGATION", BlimpMessage::NAVIGATION, make_scoped_ptr(new NavigationLogExtractor)); AddHandler("PROTOCOL_CONTROL", BlimpMessage::PROTOCOL_CONTROL, make_scoped_ptr(new ProtocolControlLogExtractor)); AddHandler("RENDER_WIDGET", BlimpMessage::RENDER_WIDGET, make_scoped_ptr(new RenderWidgetLogExtractor)); AddHandler("TAB_CONTROL", BlimpMessage::TAB_CONTROL, make_scoped_ptr(new TabControlLogExtractor)); } BlimpMessageLogger::~BlimpMessageLogger() {} void BlimpMessageLogger::AddHandler(const std::string& type_name, BlimpMessage::Type type, scoped_ptr extractor) { DCHECK(extractors_.find(type) == extractors_.end()); DCHECK(!type_name.empty()); extractors_[type] = make_pair(type_name, std::move(extractor)); } void BlimpMessageLogger::LogMessageToStream(const BlimpMessage& message, std::ostream* out) const { LogFields fields; auto extractor = extractors_.find(message.type()); if (extractor != extractors_.end()) { // An extractor is registered for |message|. // Add the human-readable name of |message.type|. fields.push_back(make_pair("type", extractor->second.first)); extractor->second.second->ExtractFields(message, &fields); } else { // Don't know the human-readable name of |message.type|. // Just represent it using its numeric form instead. AddField("type", message.type(), &fields); } // Append "target_tab_id" (if present) and "byte_size" to the field set. if (message.has_target_tab_id()) { AddField("target_tab_id", message.target_tab_id(), &fields); } AddField("byte_size", message.ByteSize(), &fields); // Format message using the syntax: // *out << ""; } std::ostream& operator<<(std::ostream& out, const BlimpMessage& message) { g_logger.Get().LogMessageToStream(message, &out); return out; } } // namespace blimp