Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iox-#1755 Reintroduce lazy evaluation for the logging #2037

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions .github/workflows/lint_pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ jobs:
id: changed_files
uses: Ana06/[email protected]
- name: diff scan, clang-tidy for all files which are in `.clang-tidy-diff-scans.txt`
run: |
for diff_file in ${{ steps.changed_files.outputs.added_modified }}; do
./tools/scripts/clang_tidy_check.sh scan_list .clang-tidy-diff-scans.txt ${diff_file}
done
run: ./tools/scripts/clang_tidy_check.sh scan_list .clang-tidy-diff-scans.txt "${{ steps.changed_files.outputs.added_modified }}"

check-for-todo:
runs-on: ubuntu-latest
Expand All @@ -43,14 +40,14 @@ jobs:
steps:
- uses: actions/checkout@v3

- name: Calculate lychee cache key
run: echo "LYCHEE_CACHE_KEY=cache-lychee-all-branches-$(date +'%G-%V')" >> "$GITHUB_ENV" # %G = year; %V = week number

- name: Restore lychee cache
uses: actions/cache@v3
if: always()
with:
path: .lycheecache
key: cache-lychee-all-branches
# the cache will not be updated and therefore needs to be recreated after 7 days
retention-days: 7
key: ${{ env.LYCHEE_CACHE_KEY }}

- name: Link Checker
uses: lycheeverse/[email protected]
Expand Down
87 changes: 55 additions & 32 deletions doc/design/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,59 @@ by the `BaseLogger` template parameter and the latter by deriving from the logge

![logging with logstream](../website/images/logging_with_logstream.svg)

#### Logging macro
#### Logging macro with lazy evaluation

The `IOX_LOG` macro is intended for general use. It has one parameter which
The `IOX_LOG` macro is intended for general use. It has two parameters. The first one
sets the log level and delegates `file`, `line` and `function` to the `IOX_LOG_INTERNAL`
macro. The latter can be used in places like a custom `ASSERT` macro which already
obtained the `file`, `line` and `function` parameter.
obtained the `file`, `line` and `function` parameter. The second parameter contains
the items to log and multiple items can be logged by using the '<<' operator.

The `IOX_LOG_INTERNAL` uses the `iox::log::internal::SelectedLogStream` struct which is
specialized for `LogLevel::OFF` to compile to a no-op when the logging data does not
cause side effects. `IOX_LOG(INFO) << 42;` won't cause side effects but
`IOX_LOG(INFO) << functionCall();` might have some.
The buildup of the log message is only done when the condition to log the message
is fulfilled. This is accomplished by a macro with an if-statement.

```cpp
#define LAZY(cond, expr) if (cond) { expr; }

LAZY(x == 42, expensiveFunctionCall());
```

In the example above `expensiveFunctionCall` is only executed if `cond` is `true`.
If `cond` is a compile time constant set to `false`, the whole statement is compiled
to a no-op and optimized away.

This is the log macro with lazy evaluation

```cpp
inline bool isLogLevelActive(LogLevel logLevel) noexcept
{
return ((logLevel) <= MINIMAL_LOG_LEVEL)
&& (IGNORE_ACTIVE_LOG_LEVEL || ((logLevel) <= iox::log::Logger::getLogLevel()));
}

#define IOX_LOG_INTERNAL(file, line, function, level, msg_stream) \
if (iox::log::internal::isLogLevelActive(level)) \
{ \
iox::log::LogStream(file, line, function, level).self() << msg_stream; \
} \
[] {} () // the empty lambda forces a semicolon on the caller side

#define IOX_LOG(level, msg_stream) \
IOX_LOG_INTERNAL(__FILE__, __LINE__, __FUNCTION__, iox::log::LogLevel::level, msg_stream)
```

With `MINIMAL_LOG_LEVEL` and `IGNORE_ACTIVE_LOG_LEVEL` being static `constexpr`
constants the compiler will optimize this either to
`if (false) { iox::log::LogStream(...) ... }` and finally completely away or
`if ((level) <= iox::log::Logger::getLogLevel()) { iox::log::LogStream(...) ... }`.
The minimal log level check is intended to fully optimize away a log statement
and the ignore active log level check to always forward the log message to the
logger, independent of the active log level.

The `IOX_LOG_INTERNAL` calls `self()` on the `LogStream` instance to create an lvalue
reference to the `LogStream` instance. This eases the implementation of logging
support for custom types since `IOX_LOG(INFO) << myType;` would require to implement
an overload with a rvalue `LogStream` reference but `IOX_LOG(INFO) << "#### " << myType;`
support for custom types since `IOX_LOG(INFO, myType);` would require to implement
an overload with a rvalue `LogStream` reference but `IOX_LOG(INFO, "#### " << myType);`
requires a lvalue reference.

#### Behavior before calling Logger::init
Expand Down Expand Up @@ -268,7 +305,7 @@ int main()
{
iox::log::Logger::init(iox::log::logLevelFromEnvOr(iox::log::LogLevel::DEBUG));

IOX_LOG(DEBUG) << "Hello World";
IOX_LOG(DEBUG, "Hello World");

return 0;
}
Expand Down Expand Up @@ -296,7 +333,7 @@ iox::log::LogStream& operator<<(iox::log::LogStream& stream, const MyType& m)
int main()
{
MyType m;
IOX_LOG(INFO) << m;
IOX_LOG(INFO, m);

return 0;
}
Expand Down Expand Up @@ -362,37 +399,23 @@ int main()
{
MyLogger::init();

IOX_LOG(FATAL) << "Whoops ... look, over there is a dead seagull flying!";
IOX_LOG(ERROR) << "Oh no!";
IOX_LOG(WARN) << "It didn't happen!";
IOX_LOG(INFO) << "All glory to the hypnotoad!";
IOX_LOG(DEBUG) << "I didn't do it!";
IOX_LOG(TRACE) << "Row row row your boat!";
IOX_LOG(FATAL, "Whoops ... look, over there is a dead seagull flying!");
IOX_LOG(ERROR, "Oh no!");
IOX_LOG(WARN, "It didn't happen!");
IOX_LOG(INFO, "All glory to the hypnotoad!");
IOX_LOG(DEBUG, "I didn't do it!");
IOX_LOG(TRACE, "Row row row your boat!");

return 0;
}
```

## Open issues

### Lazy evaluation

Lazy evaluation via the following macro clashes with static code analyzer which
will generate warnings due to missing braces of the if-statement.

```cpp
#define LAZY() if (cond)

LAZY() expensiveFunctionCall();
```

Lazy evaluation with stream based logging seems to be challenging. An alternative
might be a variadic macro like `IOX_LOG(INFO, "Hello World");`

### Miscellaneous

- do we need to change the log level after `Logger::init`
- do we want a `IOX_LOG_IF(cond, level)` macro
- do we want a `IOX_LOG_IF(cond, level, msg_stream)` macro
- shall the `TestingLogger` register signals to catch `SIGTERM`, etc. and print the
log messages when the signal is raised? It might be necessary to wait for the
error handling refactoring before this can be done
Expand Down
32 changes: 16 additions & 16 deletions doc/website/release-notes/iceoryx-unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -691,14 +691,14 @@

auto& logger = iox::log::createLogger("MyComponent", "MyContext", iox::log::LogLevel::kInfo);

logger.LogInfo() << "Hello World";
logger.LogInfo() << "Hello World " << 42;

// after
#include "iox/logging.hpp"

iox::log::Logger::init(iox::log::LogLevel::INFO);

IOX_LOG(INFO) << "Hello World";
IOX_LOG(INFO, "Hello World " << 42);
```

31. Setting the default log level changed
Expand Down Expand Up @@ -771,7 +771,7 @@
{
void myFunc()
{
LogInfo() << "Hello World";
LogInfo() << "Hello World " << 42;
}
}

Expand All @@ -784,21 +784,21 @@
{
void myFunc()
{
IOX_LOG(INFO) << "Hello World";
IOX_LOG(INFO, "Hello World " << 42);
}
}
```

34. Free function logger calls changed

| before | after |
|:--------------:|:----------------:|
| `LogFatal()` | `IOX_LOG(FATAL)` |
| `LogError()` | `IOX_LOG(ERROR)` |
| `LogWarn()` | `IOX_LOG(WARN)` |
| `LogInfo()` | `IOX_LOG(INFO)` |
| `LogDebug()` | `IOX_LOG(DEBUG)` |
| `LogVerbose()` | `IOX_LOG(TRACE)` |
| before | after |
|:---------------------:|:---------------------:|
| `LogFatal() << "x"` | `IOX_LOG(FATAL, "x")` |
| `LogError() << "x"` | `IOX_LOG(ERROR, "x")` |
| `LogWarn() << "x"` | `IOX_LOG(WARN, "x")` |
| `LogInfo() << "x"` | `IOX_LOG(INFO, "x")` |
| `LogDebug() << "x"` | `IOX_LOG(DEBUG, "x")` |
| `LogVerbose() << "x"` | `IOX_LOG(TRACE, "x")` |

35. Logger formatting changed

Expand All @@ -809,8 +809,8 @@
LogInfo() << iox::log::RawBuffer(buf); // currently not supported

// after
IOX_LOG(INFO) << iox::log::hex(42);
IOX_LOG(INFO) << iox::log::oct(42);
IOX_LOG(INFO, iox::log::hex(42));
IOX_LOG(INFO, iox::log::oct(42));
```

36. Creating an instance of `LogStream` does not work anymore
Expand All @@ -827,15 +827,15 @@
stream.Flush();

// after
IOX_LOG(INFO) << [] (auto& stream) -> auto& {
IOX_LOG(INFO, [] (auto& stream) -> auto& {
stream << "fibonacci: "
for(auto fib : {1, 1, 2, 3, 5, 8})
{
stream << fib << ", ";
}
stream << "...";
return stream;
};
});
```

37. Testing of `LogStream::operator<<` overload for custom types changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ namespace er
// Custom panic with location
[[noreturn]] inline void panic(const SourceLocation& location)
{
IOX_ERROR_INTERNAL_LOG_PANIC(location) << "Panic";
IOX_ERROR_INTERNAL_LOG_PANIC(location, "Panic");
panic();
}

Expand All @@ -63,7 +63,7 @@ namespace er
template <class Message>
[[noreturn]] inline void panic(const SourceLocation& location, Message&& msg)
{
IOX_ERROR_INTERNAL_LOG_PANIC(location) << "Panic " << msg;
IOX_ERROR_INTERNAL_LOG_PANIC(location, "Panic " << msg);
panic();
}

Expand All @@ -76,8 +76,9 @@ inline void report(const SourceLocation& location, Kind, const Error& error)
auto moduleName = toModuleName(error);
auto errorName = toErrorName(error);

IOX_ERROR_INTERNAL_LOG(location) << ": " << errorName << " (code " << code.value << ") in module " << moduleName
<< " (id " << module.value << ")";
IOX_ERROR_INTERNAL_LOG(location,
": " << errorName << " (code " << code.value << ") in module " << moduleName << " (id "
<< module.value << ")");
auto& h = ErrorHandler::get();
h.onReportError(ErrorDescriptor(location, code, module));
}
Expand All @@ -95,8 +96,9 @@ inline void report(const SourceLocation& location, iox::er::FatalKind kind, cons
auto moduleName = toModuleName(error);
auto errorName = toErrorName(error);

IOX_ERROR_INTERNAL_LOG_FATAL(location) << ": " << kind.name << " " << errorName << " (code " << code.value
<< ") in module " << moduleName << " (id " << module.value << ")";
IOX_ERROR_INTERNAL_LOG_FATAL(location,
": " << kind.name << " " << errorName << " (code " << code.value << ") in module "
<< moduleName << " (id " << module.value << ")");
auto& h = ErrorHandler::get();
h.onReportError(ErrorDescriptor(location, code, module));
}
Expand All @@ -106,7 +108,7 @@ inline void report(const SourceLocation& location, iox::er::PreconditionViolatio
{
auto code = toCode(error);
auto module = toModule(error);
IOX_ERROR_INTERNAL_LOG_FATAL(location) << kind.name;
IOX_ERROR_INTERNAL_LOG_FATAL(location, kind.name);
auto& h = ErrorHandler::get();
h.onReportViolation(ErrorDescriptor(location, code, module));
}
Expand All @@ -116,7 +118,7 @@ inline void report(const SourceLocation& location, iox::er::AssumptionViolationK
{
auto code = toCode(error);
auto module = toModule(error);
IOX_ERROR_INTERNAL_LOG_FATAL(location) << kind.name;
IOX_ERROR_INTERNAL_LOG_FATAL(location, kind.name);
auto& h = ErrorHandler::get();
h.onReportViolation(ErrorDescriptor(location, code, module));
}
Expand All @@ -127,7 +129,7 @@ report(const SourceLocation& location, iox::er::PreconditionViolationKind kind,
{
auto code = toCode(error);
auto module = toModule(error);
IOX_ERROR_INTERNAL_LOG_FATAL(location) << kind.name << " " << std::forward<Message>(msg);
IOX_ERROR_INTERNAL_LOG_FATAL(location, kind.name << " " << std::forward<Message>(msg));
auto& h = ErrorHandler::get();
h.onReportViolation(ErrorDescriptor(location, code, module));
}
Expand All @@ -138,7 +140,7 @@ report(const SourceLocation& location, iox::er::AssumptionViolationKind kind, co
{
auto code = toCode(error);
auto module = toModule(error);
IOX_ERROR_INTERNAL_LOG_FATAL(location) << kind.name << " " << std::forward<Message>(msg);
IOX_ERROR_INTERNAL_LOG_FATAL(location, kind.name << " " << std::forward<Message>(msg));
auto& h = ErrorHandler::get();
h.onReportViolation(ErrorDescriptor(location, code, module));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,28 @@

/// @brief Log the location of an error.
/// @param location the location of the error
#define IOX_ERROR_INTERNAL_LOG(location) \
IOX_LOG_INTERNAL(location.file, location.line, location.function, iox::log::LogLevel::ERROR) \
<< location.file << " line " << location.line
/// @param msg_stream is the log message stream; multiple items can be logged by using the '<<' operator
#define IOX_ERROR_INTERNAL_LOG(location, msg_stream) \
elBoberido marked this conversation as resolved.
Show resolved Hide resolved
IOX_LOG_INTERNAL(location.file, \
location.line, \
location.function, \
iox::log::LogLevel::ERROR, \
location.file << " line " << location.line << ": " << msg_stream)

/// @brief Log the location of a fatal error.
/// @param location the location of the error
#define IOX_ERROR_INTERNAL_LOG_FATAL(location) \
IOX_LOG_INTERNAL(location.file, location.line, location.function, iox::log::LogLevel::FATAL) \
<< location.file << " line " << location.line << ": "
/// @param msg_stream is the log message stream; multiple items can be logged by using the '<<' operator
#define IOX_ERROR_INTERNAL_LOG_FATAL(location, msg_stream) \
IOX_LOG_INTERNAL(location.file, \
location.line, \
location.function, \
iox::log::LogLevel::FATAL, \
location.file << " line " << location.line << ": " << msg_stream)

/// @brief Log a panic invocation.
/// @param location the location of the panic invocation.
#define IOX_ERROR_INTERNAL_LOG_PANIC(location) IOX_ERROR_INTERNAL_LOG_FATAL(location)
/// @param msg_stream is the log message stream; multiple items can be logged by using the '<<' operator
#define IOX_ERROR_INTERNAL_LOG_PANIC(location, msg_stream) IOX_ERROR_INTERNAL_LOG_FATAL(location, msg_stream)

// NOLINTEND(cppcoreguidelines-macro-usage, bugprone-macro-parentheses)

Expand Down
Loading