Skip to content

Commit f038ea0

Browse files
committed
MB-46216: Check log format strings at compile-time (ep-engine)
Make use of the FMT_STRING macro to check format strings at compile-time using fmtlib v7 in ep-engine. If a call to the logger omits one or more arguments, then a compile-time error will be seen - given the following (incorrect) call missing a argument for 'bar': EP_LOG_DEBUG("Foo:{} bar:{}", foo); The compiler fails with: fmt/format.h:2873:27: constexpr variable 'invalid_format' must be initialized by a constant expression (admittedly not the most obvious, but you get what you get with C++ compiler errors...) Note that this now requires that the EP_LOG_<LEVEL> macros always take a valid fmtlib format string as the first argument, a raw string literal is no longer supported - the following will no longer compile: EP_LOG_DEBUG("Something happened"); // compile-time error. Instead, the _RAW macros added in the previous patch should be used: EP_LOG_DEBUG_RAW("Something else happened"); OK Note: The issue here is that to perform compile-time format string checking, the format string must be wrapped in FMT_STRING() - before the format string is evaluated / passed into the actual logging functions / methods. However, one cannot pass a non-string literal to FMT_STRING - essentially by design it fails at compile-time if it doesn't have {} placeholders. To address this (and still allow both styles of parameters) we _could_ in theory do some complex preprocessor logic - count the number of variadic arguments at compile-time and only apply FMT_STRING() macro to first if there is 2 or more arguments in total. I got something working for GCC and clang and which did this, but it didn't work for MSVC and the GCC one triggered a load of warnings, hence just making original macros always fmt-style, and adding _RAW for plan unformatted values. Change-Id: I32c37dfc9672663e5741433885787f1e941fe795 Reviewed-on: http://review.couchbase.org/c/kv_engine/+/153272 Tested-by: Dave Rigby <daver@couchbase.com> Reviewed-by: Ben Huddleston <ben.huddleston@couchbase.com>
1 parent eb60548 commit f038ea0

13 files changed

Lines changed: 73 additions & 56 deletions

engines/ep/src/bucket_logger.cc

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ void BucketLogger::flush_() {
4242
spdLogger->flush();
4343
}
4444

45+
void BucketLogger::logInner(spdlog::level::level_enum lvl,
46+
fmt::string_view fmt,
47+
fmt::format_args args) {
48+
EventuallyPersistentEngine* engine = ObjectRegistry::getCurrentEngine();
49+
// Disable memory tracking for the formatting and logging of the message.
50+
// This is necessary because the message will be written to disk (and
51+
// subsequently freed) by the shared background thread (as part of
52+
// spdlog::async_logger) and hence we do not know which engine to associate
53+
// the deallocation to.
54+
// Instead account any log message memory to "NonBucket" (it is only
55+
// transient and typically small - of the order of the log message length).
56+
NonBucketAllocationGuard guard;
57+
const auto prefixedFmt = prefixStringWithBucketName(engine, fmt);
58+
spdlog::logger::log(lvl, fmt::vformat(prefixedFmt, args));
59+
}
60+
4561
void BucketLogger::setLoggerAPI(ServerLogIface* api) {
4662
BucketLogger::loggerAPI.store(api, std::memory_order_relaxed);
4763

@@ -78,7 +94,7 @@ void BucketLogger::unregister() {
7894
}
7995

8096
std::string BucketLogger::prefixStringWithBucketName(
81-
const EventuallyPersistentEngine* engine, const char* fmt) {
97+
const EventuallyPersistentEngine* engine, fmt::string_view fmt) {
8298
std::string fmtString;
8399

84100
// Append the id (if set)
@@ -99,7 +115,7 @@ std::string BucketLogger::prefixStringWithBucketName(
99115
}
100116

101117
// Append the original format string
102-
fmtString.append(fmt);
118+
fmtString.append(fmt.begin(), fmt.end());
103119
return fmtString;
104120
}
105121

engines/ep/src/bucket_logger.h

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
#include "spdlog/logger.h"
1515

1616
#include <memcached/server_log_iface.h>
17-
#include <spdlog/fmt/ostr.h>
1817

1918
class EventuallyPersistentEngine;
2019

@@ -95,10 +94,9 @@ class BucketLogger : public spdlog::logger {
9594
* @param fmt The format string to use (fmtlib style).
9695
* @param args Variable arguments to include in the format string.
9796
*/
98-
template <typename... Args>
99-
void log(spdlog::level::level_enum lvl,
100-
const char* fmt,
101-
const Args&... args);
97+
template <typename S, typename... Args>
98+
void log(spdlog::level::level_enum lvl, const S& fmt, Args&&... args);
99+
102100
template <typename... Args>
103101
void log(spdlog::level::level_enum lvl, const char* msg);
104102
template <typename T>
@@ -179,19 +177,16 @@ class BucketLogger : public spdlog::logger {
179177
/// Overriden flush_ method to flush via the ServerAPI logger.
180178
void flush_() override;
181179

182-
template <typename... Args>
183180
void logInner(spdlog::level::level_enum lvl,
184-
const char* fmt,
185-
const Args&... args);
186-
template <typename T>
187-
void logInner(spdlog::level::level_enum lvl, const T& msg);
181+
fmt::string_view fmt,
182+
fmt::format_args args);
188183

189184
/**
190185
* Helper function which prefixes the string with the name of the
191186
* specified engine, or "No Engine" if engine is null.
192187
*/
193188
std::string prefixStringWithBucketName(
194-
const EventuallyPersistentEngine* engine, const char* fmt);
189+
const EventuallyPersistentEngine* engine, fmt::string_view fmt);
195190

196191
/**
197192
* Connection ID prefix that is printed if set (printed before any other
@@ -244,11 +239,21 @@ std::shared_ptr<BucketLogger>& getGlobalBucketLogger();
244239
// Various implementation details for the EP_LOG_<level> macros below.
245240
// End-users shouldn't use these directly, instead use EP_LOG_<level>.
246241

247-
#define EP_LOG_FMT(severity, ...) \
248-
do { \
249-
if (getGlobalBucketLogger()->should_log(severity)) { \
250-
getGlobalBucketLogger()->log(severity, __VA_ARGS__); \
251-
} \
242+
// Visual Studio prior to 2019 doens't correctly handle the constexpr
243+
// format string checking - see https://github.com/fmtlib/fmt/issues/2328.
244+
// As such, only apply the compile-time check for non-MSVC or VS 2019+
245+
#if FMT_MSC_VER && FMT_MSC_VER < 1920
246+
#define CHECK_FMT_STRING(fmt) fmt
247+
#else
248+
#define CHECK_FMT_STRING(fmt) FMT_STRING(fmt)
249+
#endif
250+
251+
#define EP_LOG_FMT(severity, fmt, ...) \
252+
do { \
253+
auto& logger = getGlobalBucketLogger(); \
254+
if (logger->should_log(severity)) { \
255+
logger->log(severity, CHECK_FMT_STRING(fmt), __VA_ARGS__); \
256+
} \
252257
} while (false)
253258

254259
#define EP_LOG_RAW(severity, msg) \
@@ -259,6 +264,23 @@ std::shared_ptr<BucketLogger>& getGlobalBucketLogger();
259264
} \
260265
} while (false)
261266

267+
// Convenience macros which call globalBucketLogger->log() with the given level,
268+
// format string and variadic arguments.
269+
// @param fmt Format string in fmtlib style (https://fmt.dev)
270+
// @param args Variable-length arguments, matching the number of placeholders
271+
// ({}) specified in the format string.
272+
//
273+
// For example:
274+
//
275+
// EP_LOG_INFO("Starting flusher on bucket:{} at {}", bucketName, now);
276+
//
277+
// Due to the combination of compile-time checking of format string (which must
278+
// be a string literal), these macros require both a format string and at least
279+
// one argument - i.e. you can't just pass a single element such as:
280+
//
281+
// EP_LOG_INFO("Fixed message")
282+
//
283+
// Instead, see the EP_LOG_<LEVEL>_R (Raw) macros below.
262284
#define EP_LOG_TRACE(...) \
263285
EP_LOG_FMT(spdlog::level::level_enum::trace, __VA_ARGS__)
264286

engines/ep/src/bucket_logger_impl.h

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,36 @@
1010
*/
1111
#pragma once
1212

13-
#include "objectregistry.h"
13+
#include <spdlog/fmt/ostr.h>
1414

1515
/*
1616
* Definitions of BucketLogger code which must be inline.
1717
*/
1818

19-
template <typename... Args>
19+
template <typename S, typename... Args>
2020
void BucketLogger::log(spdlog::level::level_enum lvl,
21-
const char* fmt,
22-
const Args&... args) {
21+
const S& fmt,
22+
Args&&... args) {
2323
if (!should_log(lvl)) {
2424
return;
2525
}
26-
logInner(lvl, fmt, args...);
26+
logInner(lvl, fmt, fmt::make_args_checked<Args...>(fmt, args...));
2727
}
2828

2929
template <typename... Args>
3030
void BucketLogger::log(spdlog::level::level_enum lvl, const char* msg) {
3131
if (!should_log(lvl)) {
3232
return;
3333
}
34-
logInner(lvl, msg);
34+
logInner(lvl, msg, {});
3535
}
3636

3737
template <typename T>
3838
void BucketLogger::log(spdlog::level::level_enum lvl, const T& msg) {
3939
if (!should_log(lvl)) {
4040
return;
4141
}
42-
logInner(lvl, msg);
42+
logInner(lvl, "{}", fmt::make_args_checked<T>("{}", msg));
4343
}
4444

4545
template <typename... Args>
@@ -101,30 +101,3 @@ template <typename T>
101101
void BucketLogger::critical(const T& msg) {
102102
log(spdlog::level::critical, msg);
103103
}
104-
105-
template <typename... Args>
106-
void BucketLogger::logInner(spdlog::level::level_enum lvl,
107-
const char* fmt,
108-
const Args&... args) {
109-
EventuallyPersistentEngine* engine = ObjectRegistry::getCurrentEngine();
110-
// Disable memory tracking for the formatting and logging of the message.
111-
// This is necessary because the message will be written to disk (and
112-
// subsequently freed) by the shared background thread (as part of
113-
// spdlog::async_logger) and hence we do not know which engine to associate
114-
// the deallocation to.
115-
// Instead account any log message memory to "NonBucket" (it is only
116-
// transient and typically small - of the order of the log message length).
117-
NonBucketAllocationGuard guard;
118-
const auto prefixedFmt = prefixStringWithBucketName(engine, fmt);
119-
spdlog::logger::log(lvl, prefixedFmt.c_str(), args...);
120-
}
121-
122-
template <typename T>
123-
void BucketLogger::logInner(spdlog::level::level_enum lvl, const T& msg) {
124-
EventuallyPersistentEngine* engine = ObjectRegistry::getCurrentEngine();
125-
// See comment in above logInner overload for why NonBucketAllocationGuard
126-
// is required.
127-
NonBucketAllocationGuard guard;
128-
const auto prefixedMsg = prefixStringWithBucketName(engine, "");
129-
spdlog::logger::log(lvl, "{}{}", prefixedMsg.c_str(), msg);
130-
}

engines/ep/src/cb3_executorpool.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "cb3_executorthread.h"
1515
#include "ep_engine.h"
1616
#include "ep_time.h"
17+
#include "objectregistry.h"
1718
#include "taskqueue.h"
1819

1920
#include <nlohmann/json.hpp>

engines/ep/src/cb3_executorthread.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "bucket_logger.h"
1414
#include "cb3_executorpool.h"
1515
#include "globaltask.h"
16+
#include "objectregistry.h"
1617
#include "taskqueue.h"
1718

1819
#include <folly/Portability.h>

engines/ep/src/dcp/consumer.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "executorpool.h"
2424
#include "failover-table.h"
2525
#include "kv_bucket.h"
26+
#include "objectregistry.h"
2627
#include "replicationthrottle.h"
2728

2829
#include <memcached/server_cookie_iface.h>

engines/ep/src/dcp/dcpconnmap.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "dcp/consumer.h"
1919
#include "dcp/producer.h"
2020
#include "ep_engine.h"
21+
#include "objectregistry.h"
2122
#include <daemon/tracing.h>
2223
#include <memcached/server_cookie_iface.h>
2324
#include <memcached/vbucket.h>

engines/ep/src/dcp/producer.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "failover-table.h"
3030
#include "item_eviction.h"
3131
#include "kv_bucket.h"
32+
#include "objectregistry.h"
3233
#include "snappy-c.h"
3334

3435
#include <memcached/server_cookie_iface.h>

engines/ep/src/flusher.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "bucket_logger.h"
1515
#include "ep_bucket.h"
1616
#include "executorpool.h"
17+
#include "objectregistry.h"
1718
#include "tasks.h"
1819

1920
#include <platform/timeutils.h>

engines/ep/src/folly_executorpool.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "bucket_logger.h"
1515
#include "ep_time.h"
1616
#include "globaltask.h"
17+
#include "objectregistry.h"
1718
#include "taskable.h"
1819

1920
#include <folly/executors/CPUThreadPoolExecutor.h>

0 commit comments

Comments
 (0)