Skip to content

Commit 0a3b66f

Browse files
authored
add QUILL_DETAILED_FUNCTION_NAME and process_function_name
1 parent 44bcb90 commit 0a3b66f

File tree

7 files changed

+119
-3
lines changed

7 files changed

+119
-3
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@
134134
are not needed in the `PatternFormatter`. This removes embedded source path strings from built binaries from the
135135
security viewpoint.
136136
137+
- Added the `QUILL_DETAILED_FUNCTION_NAME` CMake option. When enabled, this option uses compiler-specific detailed
138+
function signatures (such as `__PRETTY_FUNCTION__` on GCC/Clang or `__FUNCSIG__` on MSVC) instead of the standard
139+
`__FUNCTION__` in log macros. This provides more complete function information, including return types, namespaces,
140+
and parameter types. This option is only relevant when `%(caller_function)` is used in the pattern
141+
formatter. ([#785](https://github.com/odygrd/quill/issues/785))
142+
143+
- Added `process_function_name` customisation point in `PatternFormatterOptions`. This function allows custom processing
144+
of the function signature before it's displayed in logs. This makes more sense to use when
145+
`QUILL_DETAILED_FUNCTION_NAME` is used. This provides flexibility to trim, format, or otherwise modify function
146+
signatures to improve readability in log output when using the `%(caller_function)`
147+
pattern. ([#785](https://github.com/odygrd/quill/issues/785))
148+
137149
- Added helper macros for easy logging of user-defined types. Two new macros are available in `quill/HelperMacros.h`:
138150
- `QUILL_LOGGABLE_DIRECT_FORMAT(Type)`: For types that contain pointers or have lifetime dependencies
139151
- `QUILL_LOGGABLE_DEFERRED_FORMAT(Type)`: For types that only contain value types and are safe to copy

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ option(QUILL_DISABLE_NON_PREFIXED_MACROS "Enable this option to disable non-pref
1515

1616
option(QUILL_DISABLE_FUNCTION_NAME "Disable the use of __FUNCTION__ in `LOG_*` macros when the function name is not needed." OFF)
1717

18+
option(QUILL_DETAILED_FUNCTION_NAME "Use detailed function name (__PRETTY_FUNCTION__ or __FUNCSIG__) instead of __FUNCTION__ in LOG_* macros" OFF)
19+
1820
option(QUILL_DISABLE_FILE_INFO "Disable the use of __FILE__ and __LINE__ in `LOG_*` macros when the file name and the line number are not needed." OFF)
1921

2022
option(QUILL_BUILD_EXAMPLES "Enable this option to build and install the examples. Set this to ON to include example projects in the build process and have them installed after configuring with CMake." OFF)
@@ -121,6 +123,7 @@ message(STATUS "QUILL_NO_THREAD_NAME_SUPPORT: " ${QUILL_NO_THREAD_NAME_SUPPORT})
121123
message(STATUS "QUILL_X86ARCH: " ${QUILL_X86ARCH})
122124
message(STATUS "QUILL_DISABLE_NON_PREFIXED_MACROS: " ${QUILL_DISABLE_NON_PREFIXED_MACROS})
123125
message(STATUS "QUILL_DISABLE_FUNCTION_NAME: " ${QUILL_DISABLE_FUNCTION_NAME})
126+
message(STATUS "QUILL_DETAILED_FUNCTION_NAME: " ${QUILL_DETAILED_FUNCTION_NAME})
124127
message(STATUS "QUILL_DISABLE_FILE_INFO: " ${QUILL_DISABLE_FILE_INFO})
125128
message(STATUS "QUILL_ENABLE_INSTALL: " ${QUILL_ENABLE_INSTALL})
126129

@@ -292,6 +295,10 @@ if (QUILL_DISABLE_FUNCTION_NAME)
292295
target_compile_definitions(${TARGET_NAME} PUBLIC INTERFACE -DQUILL_DISABLE_FUNCTION_NAME)
293296
endif ()
294297

298+
if (QUILL_DETAILED_FUNCTION_NAME)
299+
target_compile_definitions(${TARGET_NAME} PUBLIC INTERFACE -DQUILL_DETAILED_FUNCTION_NAME)
300+
endif ()
301+
295302
if (QUILL_DISABLE_FILE_INFO)
296303
target_compile_definitions(${TARGET_NAME} PUBLIC INTERFACE -DQUILL_DISABLE_FILE_INFO)
297304
endif ()

docs/cheat_sheet.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ Disables ``__FUNCTION__`` information in log statements at compile time when the
4747
4848
Disables ``__FILE__`` and ``__LINE__`` information in log statements at compile time when location-related patterns (``%(file_name)``, ``%(line_number)``, ``%(short_source_location)``, ``%(source_location)``) are not needed in the ``PatternFormatter``. This removes embedded source path strings from built binaries from the security viewpoint.
4949

50+
.. code:: cmake
51+
52+
option(QUILL_DETAILED_FUNCTION_NAME "Use detailed function name (__PRETTY_FUNCTION__ or __FUNCSIG__) instead of __FUNCTION__ in LOG_* macros" OFF)
53+
54+
Enables the use of compiler-specific detailed function signatures (such as ``__PRETTY_FUNCTION__`` on GCC/Clang or ``__FUNCSIG__`` on MSVC) instead of the standard ``__FUNCTION__`` in log macros. This option is only relevant when ``%(caller_function)`` is used in the pattern formatter. When enabled, you can further customize the function name display by providing a processing function via ``PatternFormatterOptions::process_detailed_function_name``.
55+
5056
.. code:: cmake
5157
5258
add_compile_definitions(-DQUILL_IMMEDIATE_FLUSH=0)
@@ -67,6 +73,12 @@ For example, to keep only warning level and above:
6773
6874
add_compile_definitions(-DQUILL_COMPILE_ACTIVE_LOG_LEVEL=QUILL_COMPILE_ACTIVE_LOG_LEVEL_WARNING)
6975
76+
Hiding File Names and Functions From Build Binaries
77+
--------------------------------------------------
78+
From a security standpoint, embedded source file paths and function signatures in binaries can leak sensitive information about your codebase structure.
79+
To protect use both the `-DQUILL_DISABLE_FUNCTION_NAME` and `-DQUILL_DISABLE_FILE_INFO` compile definitions described above.
80+
When both options are enabled, neither function names nor file paths will be embedded in your binary, significantly reducing the information available to anyone examining the compiled code.
81+
7082
LOGV Macros
7183
-----------
7284
In addition to the ``LOG_`` macros, the ``LOGV_`` macros provide a convenient alternative.

include/quill/LogMacros.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,20 @@
4545
#endif
4646

4747
#if !defined(QUILL_FUNCTION_NAME)
48+
#if defined(QUILL_DISABLE_FUNCTION_NAME) && defined(QUILL_DETAILED_FUNCTION_NAME)
49+
#error "QUILL_DISABLE_FUNCTION_NAME and QUILL_DETAILED_FUNCTION_NAME are mutually exclusive"
50+
#endif
51+
4852
#if defined(QUILL_DISABLE_FUNCTION_NAME)
4953
#define QUILL_FUNCTION_NAME ""
54+
#elif defined(QUILL_DETAILED_FUNCTION_NAME)
55+
#if defined(_MSC_VER)
56+
#define QUILL_FUNCTION_NAME __FUNCSIG__
57+
#elif defined(__clang__) || defined(__GNUC__) || defined(__INTEL_COMPILER)
58+
#define QUILL_FUNCTION_NAME __PRETTY_FUNCTION__
59+
#else
60+
#define QUILL_FUNCTION_NAME __FUNCTION__
61+
#endif
5062
#else
5163
#define QUILL_FUNCTION_NAME __FUNCTION__
5264
#endif

include/quill/backend/PatternFormatter.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,11 @@ class PatternFormatter
124124

125125
if (_is_set_in_pattern[Attribute::CallerFunction])
126126
{
127-
_set_arg_val<Attribute::CallerFunction>(log_statement_metadata.caller_function());
127+
std::string_view const function_name = _options.process_function_name
128+
? _options.process_function_name(log_statement_metadata.caller_function())
129+
: std::string_view{log_statement_metadata.caller_function()};
130+
131+
_set_arg_val<Attribute::CallerFunction>(function_name);
128132
}
129133

130134
if (_is_set_in_pattern[Attribute::LogLevel])
@@ -291,7 +295,7 @@ class PatternFormatter
291295

292296
_set_arg<Attribute::Time>(std::string_view("time"));
293297
_set_arg<Attribute::FileName>(std::string_view("file_name"));
294-
_set_arg<Attribute::CallerFunction>("caller_function");
298+
_set_arg<Attribute::CallerFunction>(std::string_view("caller_function"));
295299
_set_arg<Attribute::LogLevel>(std::string_view("log_level"));
296300
_set_arg<Attribute::LogLevelShortCode>(std::string_view("log_level_short_code"));
297301
_set_arg<Attribute::LineNumber>("line_number");

include/quill/core/PatternFormatterOptions.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,25 @@ class PatternFormatterOptions
9090
*/
9191
std::string source_location_path_strip_prefix{};
9292

93+
/**
94+
* @brief Function pointer for custom processing of detailed function names for %(caller_function)
95+
*
96+
* This is most useful when QUILL_DETAILED_FUNCTION_NAME is enabled, as it allows
97+
* custom processing of the detailed function signature provided by the compiler.
98+
*
99+
* Since the format of __PRETTY_FUNCTION__ or equivalent is compiler-specific,
100+
* this function allows users to implement their own parsing/formatting logic.
101+
*
102+
* The function takes one parameter:
103+
* - The raw function signature string from the compiler (e.g., from __PRETTY_FUNCTION__)
104+
*
105+
* It should return a string_view representing the processed function name.
106+
*
107+
* If set to nullptr (default), the logger will use the unprocessed function name
108+
* as provided by the compiler.
109+
*/
110+
std::string_view (*process_function_name)(char const*){nullptr};
111+
93112
/**
94113
* @brief The timezone to use for timestamps.
95114
*
@@ -121,7 +140,7 @@ class PatternFormatterOptions
121140
{
122141
return format_pattern == other.format_pattern && timestamp_pattern == other.timestamp_pattern &&
123142
source_location_path_strip_prefix == other.source_location_path_strip_prefix &&
124-
timestamp_timezone == other.timestamp_timezone &&
143+
timestamp_timezone == other.timestamp_timezone && process_function_name == other.process_function_name &&
125144
add_metadata_to_multi_line_logs == other.add_metadata_to_multi_line_logs &&
126145
source_location_remove_relative_paths == other.source_location_remove_relative_paths;
127146
}

test/unit_tests/PatternFormatterTest.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,56 @@ TEST_CASE("pattern_formatter_source_location_prefix")
545545
}
546546
}
547547

548+
TEST_CASE("pattern_formatter_process_function_name")
549+
{
550+
std::vector<std::pair<std::string, std::string>> named_args;
551+
uint64_t const ts{1579815761000023021};
552+
char const* thread_id = "31341";
553+
std::string const logger_name = "test_logger";
554+
MacroMetadata macro_metadata{
555+
__FILE__ ":" QUILL_STRINGIFY(__LINE__), __func__, "{}}", nullptr, LogLevel::Info, MacroMetadata::Event::Log};
556+
557+
PatternFormatterOptions po;
558+
po.format_pattern = "%(caller_function)";
559+
po.process_function_name = [](char const* function_name)
560+
{
561+
std::string_view sv{function_name};
562+
// Check if the string ends with a number after an underscore
563+
size_t pos = sv.rfind('_');
564+
if (pos != std::string_view::npos && pos < sv.length() - 1)
565+
{
566+
// Check if the characters after the underscore are all digits
567+
bool all_digits = true;
568+
for (size_t i = pos + 1; i < sv.length(); ++i)
569+
{
570+
if (!std::isdigit(sv[i]))
571+
{
572+
all_digits = false;
573+
break;
574+
}
575+
}
576+
577+
// If they are all digits, remove the underscore and the digits
578+
if (all_digits)
579+
{
580+
return sv.substr(0, pos);
581+
}
582+
}
583+
return sv;
584+
};
585+
586+
PatternFormatter pattern_formatter{po};
587+
588+
auto const& formatted_buffer =
589+
pattern_formatter.format(ts, thread_id, thread_name, process_id, logger_name, "INFO", "I",
590+
macro_metadata, &named_args, std::string_view{});
591+
592+
// in this test we remove e.g. _31 from the function name with process_function_name
593+
std::string const formatted_string = fmtquill::to_string(formatted_buffer);
594+
std::string const expected_string = "DOCTEST_ANON_FUNC\n";
595+
REQUIRE_EQ(formatted_string, expected_string);
596+
}
597+
548598
struct PatternFormatterMock : public PatternFormatter
549599
{
550600
PatternFormatterMock() : PatternFormatter(PatternFormatterOptions{}) {};

0 commit comments

Comments
 (0)