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

logging: support stripping directory prefixes from file names

Summary:
The XLOG() macros compute the log category based on the source file name, as
defined by `__FILE__`.  Both gcc and clang set the `__FILE__` contents to the
file name passed in on the command line, so if they are invoked with absolute
path names `__FILE__` will contain an absolute path.  This is normally
undesirable for `XLOG()` behavior.  Furthermore, CMake always invokes the
compiler with absolute path names, so it is difficult to avoid this situation
in open source builds.

This adds support for a `FOLLY_XLOG_STRIP_PREFIXES` macro.  This can be defined
to a string containing a colon-separated list of directory prefixes to strip
from `__FILE__` before using it to compute the log category name.  If
`FOLLY_XLOG_STRIP_PREFIXES` is not defined then the code continues to use
`__FILE__` as-is.

Reviewed By: yfeldblum

Differential Revision: D6868773

fbshipit-source-id: a5c3276d8d22d3b27ae621700a75b1362d049292
parent 42ec19da
......@@ -4,6 +4,12 @@ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_COMMON} -O3")
list(APPEND CMAKE_REQUIRED_FLAGS -std=gnu++14)
function(apply_folly_compile_options_to_target THETARGET)
target_compile_definitions(${THETARGET}
PRIVATE
_REENTRANT
_GNU_SOURCE
"FOLLY_XLOG_STRIP_PREFIXES=\"${FOLLY_DIR_PREFIXES}\""
)
target_compile_options(${THETARGET}
PUBLIC
-g
......@@ -21,8 +27,5 @@ function(apply_folly_compile_options_to_target THETARGET)
-Wunused-result
-Wnon-virtual-dtor
${FOLLY_CXX_FLAGS}
PRIVATE
-D_REENTRANT
-D_GNU_SOURCE
)
endfunction()
......@@ -55,8 +55,12 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
endif()
endif()
set(FOLLY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/folly")
set(TOP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(FOLLY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/folly")
set(
FOLLY_DIR_PREFIXES
"${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_CURRENT_BINARY_DIR}"
)
include(folly-deps) # Find the required packages
......@@ -446,11 +450,7 @@ if (BUILD_TESTS)
TEST printf_test SOURCES PrintfTest.cpp
TEST rate_limiter_test SOURCES RateLimiterTest.cpp
TEST standard_log_handler_test SOURCES StandardLogHandlerTest.cpp
# This test is currently broken in CMake-based builds given
# that CMake passes absolute source paths to the compiler;
# the xlog code probably should be updated to support stripping off
# project directory prefixes.
TEST xlog_test BROKEN
TEST xlog_test
HEADERS
XlogHeader1.h
XlogHeader2.h
......
......@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/logging/xlog.h>
#include <folly/experimental/logging/LogCategory.h>
#include <folly/experimental/logging/LogHandler.h>
#include <folly/experimental/logging/LogMessage.h>
......@@ -20,7 +22,7 @@
#include <folly/experimental/logging/test/TestLogHandler.h>
#include <folly/experimental/logging/test/XlogHeader1.h>
#include <folly/experimental/logging/test/XlogHeader2.h>
#include <folly/experimental/logging/xlog.h>
#include <folly/portability/Constexpr.h>
#include <folly/portability/GTest.h>
#include <folly/test/TestUtils.h>
......@@ -204,3 +206,48 @@ TEST(Xlog, getXlogCategoryName) {
getXlogCategoryNameForFile(
"buck-out/gen/foo/bar#header-map,headers/foo/bar/test.h"));
}
TEST(Xlog, xlogStripFilename) {
EXPECT_STREQ("c/d.txt", xlogStripFilename("/a/b/c/d.txt", "/a/b"));
EXPECT_STREQ("c/d.txt", xlogStripFilename("/a/b/c/d.txt", "/a/b/"));
EXPECT_STREQ(
"ships/cruiser.cpp",
xlogStripFilename(
"/home/johndoe/src/spacesim/ships/cruiser.cpp",
"/home/johndoe/src/spacesim"));
EXPECT_STREQ(
"ships/cruiser.cpp",
xlogStripFilename("src/spacesim/ships/cruiser.cpp", "src/spacesim"));
// Test with multiple prefixes
EXPECT_STREQ("c/d.txt", xlogStripFilename("/a/b/c/d.txt", "/x/y:1/2:/a/b"));
EXPECT_STREQ("c/d.txt", xlogStripFilename("/a/b/c/d.txt", "/x/y:/a/b:/1/2"));
EXPECT_STREQ(
"/foobar/src/test.cpp", xlogStripFilename("/foobar/src/test.cpp", "/foo"))
<< "should only strip full directory name matches";
EXPECT_STREQ(
"src/test.cpp",
xlogStripFilename("/foobar/src/test.cpp", "/foo:/foobar"));
EXPECT_STREQ(
"/a/b/c/d.txt", xlogStripFilename("/a/b/c/d.txt", "/a/b/c/d.txt"))
<< "should not strip if the result will be empty";
EXPECT_STREQ("c/d.txt", xlogStripFilename("/a/b/c/d.txt", ":/x/y::/a/b:"))
<< "empty prefixes in the prefix list should be ignored";
EXPECT_STREQ("d.txt", xlogStripFilename("/a/b/c/d.txt", "/a/b/c:/a"))
<< "only the first prefix match should be honored";
EXPECT_STREQ("b/c/d.txt", xlogStripFilename("/a/b/c/d.txt", "/a:/a/b/c"))
<< "only the first prefix match should be honored";
// xlogStripFilename() should ideally be a purely compile-time evaluation.
// Use a static_assert() to ensure that it can be evaluated at compile time.
// We use EXPECT_STREQ() checks above for most of the testing since it
// produces nicer messages on failure.
static_assert(
constexpr_strcmp(
xlogStripFilename("/my/project/src/test.cpp", "/my/project"),
"src/test.cpp") == 0,
"incorrect xlogStripFilename() behavior");
}
......@@ -70,7 +70,7 @@ std::string getXlogCategoryNameForFile(StringPiece filename) {
// Translate slashes to dots, to turn the directory layout into
// a category hierarchy.
for (size_t n = 0; n < categoryName.size(); ++n) {
if (categoryName[n] == '/') {
if (xlogIsDirSeparator(categoryName[n])) {
categoryName[n] = '.';
}
}
......
......@@ -16,6 +16,7 @@
#pragma once
#include <folly/Likely.h>
#include <folly/Portability.h>
#include <folly/Range.h>
#include <folly/experimental/logging/LogStream.h>
#include <folly/experimental/logging/Logger.h>
......@@ -69,6 +70,24 @@
arg1, \
##__VA_ARGS__)
/**
* FOLLY_XLOG_STRIP_PREFIXES can be defined to a string containing a
* colon-separated list of directory prefixes to strip off from the filename
* before using it to compute the log category name.
*
* If this is defined, use xlogStripFilename() to strip off directory prefixes;
* otherwise just use __FILE__ literally. xlogStripFilename() is a constexpr
* expression so that this stripping can be performed fully at compile time.
* (There is no guarantee that the compiler will evaluate it at compile time,
* though.)
*/
#ifdef FOLLY_XLOG_STRIP_PREFIXES
#define XLOG_FILENAME \
folly::xlogStripFilename(__FILE__, FOLLY_XLOG_STRIP_PREFIXES)
#else
#define XLOG_FILENAME __FILE__
#endif
/**
* Helper macro used to implement XLOG() and XLOGF()
*
......@@ -126,9 +145,9 @@
&xlog_detail::xlogFileScopeInfo); \
}(), \
(level), \
xlog_detail::getXlogCategoryName(__FILE__, 0), \
xlog_detail::getXlogCategoryName(XLOG_FILENAME, 0), \
xlog_detail::isXlogCategoryOverridden(0), \
__FILE__, \
XLOG_FILENAME, \
__LINE__, \
(type), \
##__VA_ARGS__) \
......@@ -162,7 +181,7 @@
static ::folly::XlogLevelInfo<XLOG_IS_IN_HEADER_FILE> _xlogLevel_; \
return _xlogLevel_.check( \
(level), \
xlog_detail::getXlogCategoryName(__FILE__, 0), \
xlog_detail::getXlogCategoryName(XLOG_FILENAME, 0), \
xlog_detail::isXlogCategoryOverridden(0), \
&xlog_detail::xlogFileScopeInfo); \
}())
......@@ -171,10 +190,10 @@
* Get the name of the log category that will be used by XLOG() statements
* in this file.
*/
#define XLOG_GET_CATEGORY_NAME() \
(xlog_detail::isXlogCategoryOverridden(0) \
? xlog_detail::getXlogCategoryName(__FILE__, 0) \
: ::folly::getXlogCategoryNameForFile(__FILE__))
#define XLOG_GET_CATEGORY_NAME() \
(xlog_detail::isXlogCategoryOverridden(0) \
? xlog_detail::getXlogCategoryName(XLOG_FILENAME, 0) \
: ::folly::getXlogCategoryNameForFile(XLOG_FILENAME))
/**
* Get a pointer to the LogCategory that will be used by XLOG() statements in
......@@ -381,6 +400,79 @@ class XlogCategoryInfo<false> {
* XLOG_SET_CATEGORY_NAME() has not been used.
*/
std::string getXlogCategoryNameForFile(folly::StringPiece filename);
constexpr bool xlogIsDirSeparator(char c) {
return c == '/' || (kIsWindows && c == '\\');
}
namespace detail {
constexpr const char* xlogStripFilenameRecursive(
const char* filename,
const char* prefixes,
size_t prefixIdx,
size_t filenameIdx,
bool match);
constexpr const char* xlogStripFilenameMatchFound(
const char* filename,
const char* prefixes,
size_t prefixIdx,
size_t filenameIdx) {
return (filename[filenameIdx] == '\0')
? xlogStripFilenameRecursive(filename, prefixes, prefixIdx + 1, 0, true)
: (xlogIsDirSeparator(filename[filenameIdx])
? xlogStripFilenameMatchFound(
filename, prefixes, prefixIdx, filenameIdx + 1)
: (filename + filenameIdx));
}
constexpr const char* xlogStripFilenameRecursive(
const char* filename,
const char* prefixes,
size_t prefixIdx,
size_t filenameIdx,
bool match) {
// This would be much easier to understand if written as a while loop.
// However, in order to maintain compatibility with pre-C++14 compilers we
// have implemented it recursively to adhere to C++11 restrictions for
// constexpr functions.
return (prefixes[prefixIdx] == ':' || prefixes[prefixIdx] == '\0')
? ((match && filenameIdx > 0 &&
(xlogIsDirSeparator(prefixes[filenameIdx - 1]) ||
xlogIsDirSeparator(filename[filenameIdx])))
? (xlogStripFilenameMatchFound(
filename, prefixes, prefixIdx, filenameIdx))
: ((prefixes[prefixIdx] == '\0')
? filename
: xlogStripFilenameRecursive(
filename, prefixes, prefixIdx + 1, 0, true)))
: ((match && (prefixes[prefixIdx] == filename[filenameIdx]))
? xlogStripFilenameRecursive(
filename, prefixes, prefixIdx + 1, filenameIdx + 1, true)
: xlogStripFilenameRecursive(
filename, prefixes, prefixIdx + 1, 0, false));
}
} // namespace detail
/**
* Strip directory prefixes from a filename before using it in XLOG macros.
*
* This is primarily used to strip off the initial project directory path for
* projects that invoke the compiler with absolute path names.
*
* The filename argument is the filename to process. This is normally the
* contents of the __FILE__ macro from the invoking file.
*
* prefixes is a colon-separated list of directory prefixes to strip off if
* present at the beginning of the filename. The prefix list is searched in
* order, and only the first match found will be stripped.
*
* e.g., xlogStripFilename("/my/project/src/foo.cpp", "/tmp:/my/project")
* would return "src/foo.cpp"
*/
constexpr const char* xlogStripFilename(
const char* filename,
const char* prefixes) {
return detail::xlogStripFilenameRecursive(filename, prefixes, 0, 0, true);
}
} // namespace folly
/*
......
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