// 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/memory/ptr_util.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 COMPOSITOR messages. class CompositorLogExtractor : public LogExtractor { void ExtractFields(const BlimpMessage& message, LogFields* output) const override { AddField("render_widget_id", message.compositor().render_widget_id(), output); } }; // Logs fields from INPUT messages. class InputLogExtractor : public LogExtractor { void ExtractFields(const BlimpMessage& message, LogFields* output) const override { AddField("render_widget_id", message.input().render_widget_id(), output); AddField("timestamp_seconds", message.input().timestamp_seconds(), output); switch (message.input().type()) { case InputMessage::Type_GestureScrollBegin: AddField("subtype", "GestureScrollBegin", output); break; case InputMessage::Type_GestureScrollEnd: AddField("subtype", "GestureScrollEnd", output); break; case InputMessage::Type_GestureScrollUpdate: AddField("subtype", "GestureScrollUpdate", output); break; case InputMessage::Type_GestureFlingStart: AddField("subtype", "GestureFlingStart", output); break; case InputMessage::Type_GestureFlingCancel: AddField("subtype", "GestureFlingCancel", output); AddField("prevent_boosting", message.input().gesture_fling_cancel().prevent_boosting(), output); break; case InputMessage::Type_GestureTap: AddField("subtype", "GestureTap", output); break; case InputMessage::Type_GesturePinchBegin: AddField("subtype", "GesturePinchBegin", output); break; case InputMessage::Type_GesturePinchEnd: AddField("subtype", "GesturePinchEnd", output); break; case InputMessage::Type_GesturePinchUpdate: AddField("subtype", "GesturePinchUpdate", output); break; default: // unknown 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, base::WrapUnique(new CompositorLogExtractor)); AddHandler("INPUT", BlimpMessage::INPUT, base::WrapUnique(new InputLogExtractor)); AddHandler("NAVIGATION", BlimpMessage::NAVIGATION, base::WrapUnique(new NavigationLogExtractor)); AddHandler("PROTOCOL_CONTROL", BlimpMessage::PROTOCOL_CONTROL, base::WrapUnique(new ProtocolControlLogExtractor)); AddHandler("RENDER_WIDGET", BlimpMessage::RENDER_WIDGET, base::WrapUnique(new RenderWidgetLogExtractor)); AddHandler("TAB_CONTROL", BlimpMessage::TAB_CONTROL, base::WrapUnique(new TabControlLogExtractor)); } BlimpMessageLogger::~BlimpMessageLogger() {} void BlimpMessageLogger::AddHandler(const std::string& type_name, BlimpMessage::Type type, std::unique_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