blob: 32505a8b99aff56eee8d0ad98a9bab0dc6520b69 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LIB_SYSLOG_CPP_MACROS_H_
#define LIB_SYSLOG_CPP_MACROS_H_
#include <lib/stdcompat/optional.h>
#include <lib/stdcompat/string_view.h>
#include <lib/syslog/cpp/log_level.h>
#include <zircon/types.h>
#include <atomic>
#include <functional>
#include <limits>
#include <sstream>
#include <vector>
namespace syslog_runtime {
struct LogBuffer;
// A null-safe wrapper around cpp17::optional<cpp17::string_view>
//
// This class is used to represent a string that may be nullptr. It is used
// to avoid the need to check for nullptr before passing a string to the
// syslog macros.
//
// This class is implicitly convertible to cpp17::optional<cpp17::string_view>.
// NOLINT is used as implicit conversions are intentional here.
class NullSafeStringView final {
public:
// Constructs a NullSafeStringView from a cpp17::string_view.
constexpr NullSafeStringView(cpp17::string_view string_view)
: string_view_(string_view) {} // NOLINT
// Constructs a NullSafeStringView from a nullptr.
constexpr NullSafeStringView(std::nullptr_t) : string_view_(cpp17::nullopt) {} // NOLINT
constexpr NullSafeStringView(const NullSafeStringView&) = default;
// Constructs a NullSafeStringView from a const char* which may be nullptr.
// string Nullable string to construct from.
constexpr NullSafeStringView(const char* input) { // NOLINT
if (!input) {
string_view_ = cpp17::nullopt;
} else {
string_view_ = cpp17::string_view(input);
}
}
// Constructs a NullSafeStringView from an std::string.
constexpr NullSafeStringView(const std::string& input) : string_view_(input) {} // NOLINT
// Converts this NullSafeStringView to a cpp17::optional<cpp17::string_view>.
constexpr operator cpp17::optional<cpp17::string_view>() const { return string_view_; } // NOLINT
private:
cpp17::optional<cpp17::string_view> string_view_;
};
#ifdef __Fuchsia__
void BeginRecordWithSocket(LogBuffer* buffer, fuchsia_logging::LogSeverity severity,
NullSafeStringView file_name, unsigned int line, NullSafeStringView msg,
NullSafeStringView condition, zx_handle_t socket);
void SetInterestChangedListener(void (*callback)(void* context,
fuchsia_logging::LogSeverity severity),
void* context);
#endif
void BeginRecord(LogBuffer* buffer, fuchsia_logging::LogSeverity severity, NullSafeStringView file,
unsigned int line, NullSafeStringView msg, NullSafeStringView condition);
void WriteKeyValue(LogBuffer* buffer, cpp17::string_view key, cpp17::string_view value);
void WriteKeyValue(LogBuffer* buffer, cpp17::string_view key, int64_t value);
void WriteKeyValue(LogBuffer* buffer, cpp17::string_view key, uint64_t value);
void WriteKeyValue(LogBuffer* buffer, cpp17::string_view key, double value);
void WriteKeyValue(LogBuffer* buffer, cpp17::string_view key, bool value);
static void WriteKeyValue(LogBuffer* buffer, cpp17::string_view key, const char* value) {
WriteKeyValue(buffer, key, cpp17::string_view(value));
}
bool FlushRecord(LogBuffer* buffer);
template <typename... Args>
constexpr size_t ArgsSize(Args... args) {
return sizeof...(args);
}
template <typename... Args>
struct Tuplet final {
std::tuple<Args...> tuple;
size_t size;
constexpr Tuplet(std::tuple<Args...> tuple, size_t size) : tuple(tuple), size(size) {}
};
template <typename Key, typename Value>
struct KeyValue final {
Key key;
Value value;
constexpr KeyValue(Key key, Value value) : key(key), value(value) {}
};
template <size_t i, size_t size>
constexpr bool ILessThanSize() {
return i < size;
}
template <bool expr>
constexpr bool Not() {
return !expr;
}
// Opaque structure representing the backend encode state.
// This structure only has meaning to the backend and application code shouldn't
// touch these values.
struct LogBuffer final {
// Max size of log buffer. This number may change as additional fields
// are added to the internal encoding state. It is based on trial-and-error
// and is adjusted when compilation fails due to it not being large enough.
static constexpr auto kBufferSize = (1 << 15) / 8;
// Additional storage for internal log state.
static constexpr auto kStateSize = 18;
// Record state (for keeping track of backend-specific details)
uint64_t record_state[kStateSize];
// Log data (used by the backend to encode the log into). The format
// for this is backend-specific.
uint64_t data[kBufferSize];
// Does nothing (enabled when we hit the last parameter that the user passed into us)
template <size_t i, size_t size, typename... T,
typename std::enable_if<Not<ILessThanSize<i, size>()>(), int>::type = 0>
void Encode(Tuplet<T...> value) {}
// Encodes an int8
void Encode(KeyValue<const char*, int8_t> value) {
Encode(KeyValue<const char*, int64_t>(value.key, value.value));
}
// Encodes an int16
void Encode(KeyValue<const char*, int16_t> value) {
Encode(KeyValue<const char*, int64_t>(value.key, value.value));
}
// Encodes an int32
void Encode(KeyValue<const char*, int32_t> value) {
Encode(KeyValue<const char*, int64_t>(value.key, value.value));
}
// Encodes an int64
void Encode(KeyValue<const char*, int64_t> value) {
syslog_runtime::WriteKeyValue(this, value.key, value.value);
}
#ifdef __APPLE__
// Encodes a size_t. On Apple Clang, size_t is a special type.
void Encode(KeyValue<const char*, size_t> value) {
syslog_runtime::WriteKeyValue(this, value.key, static_cast<int64_t>(value.value));
}
#endif
// Encodes an uint8_t
void Encode(KeyValue<const char*, uint8_t> value) {
Encode(KeyValue<const char*, uint64_t>(value.key, value.value));
}
// Encodes an uint16_t
void Encode(KeyValue<const char*, uint16_t> value) {
Encode(KeyValue<const char*, uint64_t>(value.key, value.value));
}
// Encodes a uint32_t
void Encode(KeyValue<const char*, uint32_t> value) {
Encode(KeyValue<const char*, uint64_t>(value.key, value.value));
}
// Encodes an uint64
void Encode(KeyValue<const char*, uint64_t> value) {
syslog_runtime::WriteKeyValue(this, value.key, value.value);
}
// Encodes a NULL-terminated C-string.
void Encode(KeyValue<const char*, const char*> value) {
syslog_runtime::WriteKeyValue(this, value.key, value.value);
}
// Encodes a NULL-terminated C-string.
void Encode(KeyValue<const char*, char*> value) {
syslog_runtime::WriteKeyValue(this, value.key, value.value);
}
// Encodes a C++ std::string.
void Encode(KeyValue<const char*, std::string> value) {
syslog_runtime::WriteKeyValue(this, value.key, value.value);
}
// Encodes a C++ std::string_view.
void Encode(KeyValue<const char*, std::string_view> value) {
syslog_runtime::WriteKeyValue(this, value.key, value.value);
}
// Encodes a double floating point value
void Encode(KeyValue<const char*, double> value) {
syslog_runtime::WriteKeyValue(this, value.key, value.value);
}
// Encodes a floating point value
void Encode(KeyValue<const char*, float> value) {
syslog_runtime::WriteKeyValue(this, value.key, value.value);
}
// Encodes a boolean value
void Encode(KeyValue<const char*, bool> value) {
syslog_runtime::WriteKeyValue(this, value.key, value.value);
}
// Encodes an arbitrary list of values recursively.
template <size_t i, size_t size, typename... T,
typename std::enable_if<ILessThanSize<i, size>(), int>::type = 0>
void Encode(Tuplet<T...> value) {
auto val = std::get<i>(value.tuple);
Encode(val);
Encode<i + 1, size>(value);
}
};
} // namespace syslog_runtime
namespace fuchsia_logging {
template <typename... LogArgs>
constexpr syslog_runtime::Tuplet<LogArgs...> Args(LogArgs... values) {
return syslog_runtime::Tuplet<LogArgs...>(std::make_tuple(values...), sizeof...(values));
}
template <typename Key, typename Value>
constexpr syslog_runtime::KeyValue<Key, Value> KeyValueInternal(Key key, Value value) {
return syslog_runtime::KeyValue<Key, Value>(key, value);
}
// Used to denote a key-value pair for use in structured logging API calls.
// This macro exists solely to improve readability of calls to FX_SLOG
#define FX_KV(a, b) a, b
template <typename Msg, typename... KeyValuePairs>
struct LogValue final {
constexpr LogValue(Msg msg, syslog_runtime::Tuplet<KeyValuePairs...> kvps)
: msg(msg), kvps(kvps) {}
// FIXME(https://fxbug.dev/42057899): With hwasan, or asan without stack-to-heap promotion for
// detecting use-after-returns, we can encounter a stack overflow in blobfs when bringing
// up one of the drivers needed for networking. This largely has to do with the LogBuffer
// which is 32kB and inlining can cause 4 of them to be allocated in a single frame. Without
// hwasan/asan, stack coloring can merge these to use the same stack space, but hwasan/asan
// both prevent this. It's still desirable to not have such a large object on the stack, so
// until we come up with a better API for using this or figure out sanitizers and stack coloring,
// we can temporarily work around the stack overflow by disabling inlining for this function.
__attribute__((__noinline__)) void LogNew(::fuchsia_logging::LogSeverity severity,
const char* file, unsigned int line,
const char* condition) const {
syslog_runtime::LogBuffer buffer;
syslog_runtime::BeginRecord(&buffer, severity, file, line, msg, condition);
// https://bugs.llvm.org/show_bug.cgi?id=41093 -- Clang loses constexpr
// even though this should be constexpr here.
buffer.Encode<0, sizeof...(KeyValuePairs)>(kvps);
syslog_runtime::FlushRecord(&buffer);
}
Msg msg;
syslog_runtime::Tuplet<KeyValuePairs...> kvps;
};
class LogMessageVoidify final {
public:
void operator&(std::ostream&) {}
};
class LogMessage final {
public:
LogMessage(LogSeverity severity, const char* file, int line, const char* condition,
const char* tag
#if defined(__Fuchsia__)
,
zx_status_t status = std::numeric_limits<zx_status_t>::max()
#endif
);
~LogMessage();
std::ostream& stream() { return stream_; }
private:
std::ostringstream stream_;
const LogSeverity severity_;
const char* file_;
const int line_;
const char* condition_;
const char* tag_;
#if defined(__Fuchsia__)
const zx_status_t status_;
#endif
};
// LogFirstNState is used by the macro FX_SLOG_FIRST_N_SECONDS below.
class LogEveryNSecondsState final {
public:
bool ShouldLog(uint32_t n);
uint32_t GetCounter();
private:
std::chrono::high_resolution_clock::time_point GetCurrentTime();
bool ShouldLogInternal(uint32_t n);
std::atomic<uint32_t> counter_{0};
std::chrono::high_resolution_clock::time_point last_;
};
// LogFirstNState is used by the macro FX_LOGS_FIRST_N below.
class LogFirstNState final {
public:
bool ShouldLog(uint32_t n);
private:
std::atomic<uint32_t> counter_{0};
};
// Gets the FX_VLOGS default verbosity level.
uint8_t GetVlogVerbosity();
// Returns true if |severity| is at or above the current minimum log level.
// LOG_FATAL and above is always true.
bool ShouldCreateLogMessage(LogSeverity severity);
} // namespace fuchsia_logging
#define FX_LOG_STREAM(severity, tag) \
::fuchsia_logging::LogMessage(::fuchsia_logging::LOG_##severity, __FILE__, __LINE__, nullptr, \
tag) \
.stream()
#define FX_LOG_STREAM_STATUS(severity, status, tag) \
::fuchsia_logging::LogMessage(::fuchsia_logging::LOG_##severity, __FILE__, __LINE__, nullptr, \
tag, status) \
.stream()
#define FX_LAZY_STREAM(stream, condition) \
!(condition) ? (void)0 : ::fuchsia_logging::LogMessageVoidify() & (stream)
#define FX_EAT_STREAM_PARAMETERS(ignored) \
true || (ignored) \
? (void)0 \
: ::fuchsia_logging::LogMessageVoidify() & \
::fuchsia_logging::LogMessage(::fuchsia_logging::LOG_FATAL, 0, 0, nullptr, nullptr) \
.stream()
#define FX_LOG_IS_ON(severity) \
(::fuchsia_logging::ShouldCreateLogMessage(::fuchsia_logging::LOG_##severity))
#define FX_LOGS(severity) FX_LOGST(severity, nullptr)
#define FX_LOGST(severity, tag) FX_LAZY_STREAM(FX_LOG_STREAM(severity, tag), FX_LOG_IS_ON(severity))
#if defined(__Fuchsia__)
#define FX_PLOGST(severity, tag, status) \
FX_LAZY_STREAM(FX_LOG_STREAM_STATUS(severity, status, tag), FX_LOG_IS_ON(severity))
#define FX_PLOGS(severity, status) FX_PLOGST(severity, nullptr, status)
#endif
// Writes a message to the global logger, the first |n| times that any callsite
// of this macro is invoked. |n| should be a positive integer literal.
// |severity| is one of INFO, WARNING, ERROR, FATAL
//
// Implementation notes:
// The outer for loop is a trick to allow us to introduce a new scope and
// introduce the variable |do_log| into that scope. It executes exactly once.
//
// The inner for loop is a trick to allow us to introduce a new scope and
// introduce the static variable |internal_state| into that new scope. It
// executes either zero or one times.
//
// C++ does not allow us to introduce two new variables into a single for loop
// scope and we need |do_log| so that the inner for loop doesn't execute twice.
#define FX_FIRST_N(n, log_statement) \
for (bool do_log = true; do_log; do_log = false) \
for (static ::fuchsia_logging::LogFirstNState internal_state; \
do_log && internal_state.ShouldLog(n); do_log = false) \
log_statement
#define FX_LOGS_FIRST_N(severity, n) FX_FIRST_N(n, FX_LOGS(severity))
#define FX_LOGST_FIRST_N(severity, n, tag) FX_FIRST_N(n, FX_LOGST(severity, tag))
#define FX_FIRST_N_SECS(n, log_statement) \
for (bool do_log = true; do_log; do_log = false) \
for (static ::fuchsia_logging::LogEveryNSecondsState internal_state; \
do_log && internal_state.ShouldLog(n); do_log = false) \
for ([[maybe_unused]] const uint32_t COUNTER = internal_state.GetCounter(); do_log; \
do_log = false) \
log_statement
#define FX_SLOG_EVERY_N_SECONDS(severity, n, msg...) FX_FIRST_N_SECS(n, FX_SLOG(severity, msg))
#define FX_CHECK(condition) FX_CHECKT(condition, nullptr)
#define FX_CHECKT(condition, tag) \
FX_LAZY_STREAM(::fuchsia_logging::LogMessage(::fuchsia_logging::LOG_FATAL, __FILE__, __LINE__, \
#condition, tag) \
.stream(), \
!(condition))
// The VLOG macros log with translated verbosities
// Get the severity corresponding to the given verbosity. Note that
// verbosity relative to the default severity and can be thought of
// as incrementally "more vebose than" the baseline.
fuchsia_logging::LogSeverity GetSeverityFromVerbosity(uint8_t verbosity);
#ifndef NDEBUG
#define FX_DLOGS(severity) FX_LOGS(severity)
#define FX_DCHECK(condition) FX_CHECK(condition)
#else
#define FX_DLOGS(severity) FX_EAT_STREAM_PARAMETERS(true)
#define FX_DCHECK(condition) FX_EAT_STREAM_PARAMETERS(condition)
#endif
#define FX_NOTREACHED() FX_DCHECK(false)
#define FX_NOTIMPLEMENTED() FX_LOGS(ERROR) << "Not implemented in: " << __PRETTY_FUNCTION__
template <typename Msg, typename... Args>
static auto MakeValue(Msg msg, syslog_runtime::Tuplet<Args...> args) {
return fuchsia_logging::LogValue<Msg, Args...>(msg, args);
}
template <size_t i, size_t size, typename... Values, typename... Tuple,
typename std::enable_if<syslog_runtime::Not<syslog_runtime::ILessThanSize<i, size>()>(),
int>::type = 0>
static auto MakeKV(std::tuple<Values...> value, std::tuple<Tuple...> tuple) {
return syslog_runtime::Tuplet<Tuple...>(tuple, size);
}
template <size_t i, size_t size, typename... Values, typename... Tuple,
typename std::enable_if<syslog_runtime::ILessThanSize<i, size>(), int>::type = 0>
static auto MakeKV(std::tuple<Values...> value, std::tuple<Tuple...> tuple) {
// Key at index i, value at index i+1
auto k = std::get<i>(value);
auto v = std::get<i + 1>(value);
auto new_tuple = std::tuple_cat(tuple, std::make_tuple(fuchsia_logging::KeyValueInternal(k, v)));
return MakeKV<i + 2, size, Values...>(value, new_tuple);
}
template <typename... Args, typename... EmptyTuple>
static auto MakeKV(std::tuple<Args...> args, std::tuple<EmptyTuple...> start_tuple) {
return MakeKV<0, sizeof...(Args), Args..., EmptyTuple...>(args, start_tuple);
}
template <typename... Args>
static auto MakeKV(std::tuple<Args...> args) {
return MakeKV(args, std::make_tuple());
}
template <typename Msg, typename... Args>
static void fx_slog_internal(fuchsia_logging::LogSeverity flag, const char* file, int line, Msg msg,
Args... args) {
MakeValue(msg, MakeKV<Args...>(std::make_tuple(args...))).LogNew(flag, file, line, nullptr);
}
#define FX_SLOG_ETC(flag, args...) \
do { \
if (::fuchsia_logging::ShouldCreateLogMessage(flag)) { \
fx_slog_internal(flag, __FILE__, __LINE__, args); \
} \
} while (0)
#define FX_SLOG(flag, msg...) FX_SLOG_ETC(::fuchsia_logging::LOG_##flag, msg)
#endif // LIB_SYSLOG_CPP_MACROS_H_