Commit d69e8653 authored by Adam Simpkins's avatar Adam Simpkins Committed by Facebook Github Bot

logging: improve objectToString() for objects with no toAppend()

Summary:
Update the `logging::objectToString()` and `appendToString)` functions when
converting objects that do not have a `toAppend(std::string*, Object)`
implementation.

Previously the code simply emitted `<no_string_conversion>` after the object
type name.  Now the code emits a hexdump of the object.  The emitted hexdump
size is based compile-type visible type of the object.  For subclass instances
the correct dynamic type name will be emitted (if RTTI is supported), but the
hexdump will only include the portion of object known from the compile-time
type.

Reviewed By: yfeldblum, chadaustin

Differential Revision: D14545305

fbshipit-source-id: d1e3df001b7f7c40c7bcb0888e76bf775512e5d3
parent 029911f0
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/logging/ObjectToString.h>
#include <folly/Demangle.h>
namespace {
void appendHexdump(std::string& str, const uint8_t* data, size_t length) {
static constexpr const char nibbleToChar[] = "0123456789abcdef";
for (size_t index = 0; index < length; ++index) {
str.push_back(' ');
str.push_back(nibbleToChar[(data[index] >> 4) & 0xf]);
str.push_back(nibbleToChar[data[index] & 0xf]);
}
}
} // namespace
namespace folly {
namespace logging {
namespace detail {
void appendRawObjectInfo(
std::string& result,
const std::type_info* type,
const uint8_t* data,
size_t length) {
if (type) {
result.push_back('[');
try {
toAppend(folly::demangle(*type), &result);
} catch (const std::exception&) {
result.append("unknown_type");
}
result.append(" of size ");
} else {
result.append("[object of size ");
}
toAppend(length, &result);
result.append(":");
appendHexdump(result, data, length);
result.push_back(']');
}
} // namespace detail
} // namespace logging
} // namespace folly
......@@ -16,8 +16,8 @@
#pragma once
#include <folly/Conv.h>
#include <folly/Demangle.h>
#include <folly/Portability.h>
#include <folly/lang/TypeInfo.h>
/*
* This file contains functions for converting arbitrary objects to strings for
......@@ -46,6 +46,36 @@
namespace folly {
namespace logging {
namespace detail {
void appendRawObjectInfo(
std::string& result,
const std::type_info* type,
const uint8_t* data,
size_t length);
} // namespace detail
/**
* Append raw information about an object to a string.
*
* This is used as a fallback for objects that we do not otherwise know how to
* print. This emits:
* - The object type name (if RTTI is supported)
* - The object size
* - A hexdump of the object contents.
*
* e.g.
* [MyStruct of size 4: 37 6f af 2a]
* [AnotherClass of size 8: f9 48 78 85 56 54 8f 54]
*/
template <typename Arg>
inline void appendRawObjectInfo(std::string& str, const Arg* arg) {
detail::appendRawObjectInfo(
str,
FOLLY_TYPE_INFO_OF(*arg),
reinterpret_cast<const uint8_t*>(arg),
sizeof(*arg));
}
/*
* Helper functions for object to string conversion.
* These are in a detail namespace so that we can include a using directive in
......@@ -77,16 +107,7 @@ auto appendObjectToString(std::string& str, const Arg* arg, int) -> decltype(
template <typename Arg>
inline void appendObjectToString(std::string& str, const Arg* arg, long) {
str.push_back('(');
#if FOLLY_HAS_RTTI
try {
toAppend(folly::demangle(typeid(*arg)), &str);
str.append(": ");
} catch (const std::exception&) {
// Ignore the error
}
#endif
str.append("<no_string_conversion>)");
::folly::logging::appendRawObjectInfo(str, arg);
}
} // namespace detail
......
......@@ -140,7 +140,11 @@ TEST_F(LoggerTest, toString) {
}
class ToStringFailure {};
class FormattableButNoToString {};
class FormattableButNoToString {
public:
explicit FormattableButNoToString(uint32_t v) : value(v) {}
uint32_t value = 0;
};
// clang-format off
[[noreturn]] void toAppend(
......@@ -224,18 +228,20 @@ TEST_F(LoggerTest, formatFallbackError) {
TEST_F(LoggerTest, formatFallbackUnsupported) {
// Check the behavior if logf() fails, and toAppend() also fails.
FormattableButNoToString obj;
FormattableButNoToString obj(0x1234cdef);
FB_LOGF(logger_, WARN, "param1: {}, param2: {}", 1234, obj);
std::string objectHex = kIsLittleEndian ? "ef cd 34 12" : "12 34 cd ef";
auto expectedRegex =
R"(error formatting log message: test; )"
R"(format string: "param1: \{\}, param2: \{\}", )"
R"(arguments: \((.*: )?1234\), )"
R"(\[(.*FormattableButNoToString.*|object) of size 4: )" +
objectHex + R"(\])";
auto& messages = handler_->getMessages();
ASSERT_EQ(1, messages.size());
EXPECT_THAT(
messages[0].first.getMessage(),
MatchesRegex(
R"(error formatting log message: test; )"
R"(format string: "param1: \{\}, param2: \{\}", )"
R"(arguments: \((.*: )?1234\), )"
R"(\((.*FormattableButNoToString.*: )?<no_string_conversion>\))"));
EXPECT_THAT(messages[0].first.getMessage(), MatchesRegex(expectedRegex));
EXPECT_EQ("LoggerTest.cpp", pathBasename(messages[0].first.getFileName()));
EXPECT_EQ(LogLevel::WARN, messages[0].first.getLevel());
EXPECT_FALSE(messages[0].first.containsNewlines());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment