Skip to content

[lldb] Introduce an always-on system log category/channel #108495

New issue

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

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

Already on GitHub? # to your account

Merged
merged 1 commit into from
Oct 10, 2024

Conversation

JDevlieghere
Copy link
Member

Add an "always on" log category and channel. Unlike other, existing log channels, it is not exposed to users. The channel is meant to be used sparsely and deliberately for logging high-value information to the system log.

We have a similar concept in the downstream Swift fork and this has proven to be extremely valuable. This is especially true on macOS where system log messages are automatically captured as part of a sysdiagnose.

This patch revives https://reviews.llvm.org/D135621 although the purpose is slightly different (logging to the system log rather than to a ring buffer dumped as part of the diagnostics). This patch addresses all the remaining oustanding comments.

@llvmbot
Copy link
Member

llvmbot commented Sep 13, 2024

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

Changes

Add an "always on" log category and channel. Unlike other, existing log channels, it is not exposed to users. The channel is meant to be used sparsely and deliberately for logging high-value information to the system log.

We have a similar concept in the downstream Swift fork and this has proven to be extremely valuable. This is especially true on macOS where system log messages are automatically captured as part of a sysdiagnose.

This patch revives https://reviews.llvm.org/D135621 although the purpose is slightly different (logging to the system log rather than to a ring buffer dumped as part of the diagnostics). This patch addresses all the remaining oustanding comments.


Full diff: https://github.com/llvm/llvm-project/pull/108495.diff

7 Files Affected:

  • (modified) lldb/include/lldb/Host/Host.h (+16)
  • (modified) lldb/include/lldb/Utility/LLDBLog.h (+1)
  • (modified) lldb/include/lldb/Utility/Log.h (+3-2)
  • (modified) lldb/source/Host/common/Host.cpp (+19)
  • (modified) lldb/source/Host/common/HostInfoBase.cpp (+2)
  • (modified) lldb/source/Utility/Log.cpp (+13-4)
  • (added) lldb/test/Shell/Host/TestSytemLogChannel.test (+3)
diff --git a/lldb/include/lldb/Host/Host.h b/lldb/include/lldb/Host/Host.h
index 9d0994978402f7..1ca89fa056aa93 100644
--- a/lldb/include/lldb/Host/Host.h
+++ b/lldb/include/lldb/Host/Host.h
@@ -31,6 +31,22 @@ class ProcessInstanceInfo;
 class ProcessInstanceInfoMatch;
 typedef std::vector<ProcessInstanceInfo> ProcessInstanceInfoList;
 
+// Always on system log category and channel.
+enum class SystemLog : Log::MaskType {
+  System = Log::ChannelFlag<0>,
+  LLVM_MARK_AS_BITMASK_ENUM(System)
+};
+
+LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
+
+class LogChannelSystem {
+public:
+  static void Initialize();
+  static void Terminate();
+};
+
+template <> Log::Channel &LogChannelFor<SystemLog>();
+
 // Exit Type for inferior processes
 struct WaitStatus {
   enum Type : uint8_t {
diff --git a/lldb/include/lldb/Utility/LLDBLog.h b/lldb/include/lldb/Utility/LLDBLog.h
index c7de41e74e85bd..b3e0e77514b8be 100644
--- a/lldb/include/lldb/Utility/LLDBLog.h
+++ b/lldb/include/lldb/Utility/LLDBLog.h
@@ -49,6 +49,7 @@ enum class LLDBLog : Log::MaskType {
   Watchpoints = Log::ChannelFlag<30>,
   OnDemand = Log::ChannelFlag<31>,
   Source = Log::ChannelFlag<32>,
+  AlwaysOn = Log::ChannelFlag<32>,
   LLVM_MARK_AS_BITMASK_ENUM(OnDemand),
 };
 
diff --git a/lldb/include/lldb/Utility/Log.h b/lldb/include/lldb/Utility/Log.h
index 27707c17f9b824..bcb022093c9001 100644
--- a/lldb/include/lldb/Utility/Log.h
+++ b/lldb/include/lldb/Utility/Log.h
@@ -165,13 +165,14 @@ class Log final {
     friend class Log;
 
   public:
+    const bool internal = false;
     const llvm::ArrayRef<Category> categories;
     const MaskType default_flags;
 
     template <typename Cat>
     constexpr Channel(llvm::ArrayRef<Log::Category> categories,
-                      Cat default_flags)
-        : log_ptr(nullptr), categories(categories),
+                      Cat default_flags, bool internal = false)
+        : log_ptr(nullptr), internal(internal), categories(categories),
           default_flags(MaskType(default_flags)) {
       static_assert(
           std::is_same<Log::MaskType, std::underlying_type_t<Cat>>::value);
diff --git a/lldb/source/Host/common/Host.cpp b/lldb/source/Host/common/Host.cpp
index f08adea6546ae1..3baf40a4700ffb 100644
--- a/lldb/source/Host/common/Host.cpp
+++ b/lldb/source/Host/common/Host.cpp
@@ -125,6 +125,25 @@ void Host::SystemLog(Severity severity, llvm::StringRef message) {
 #endif
 #endif
 
+static constexpr Log::Category g_categories[] = {
+    {{"system"}, {"system log"}, SystemLog::System}};
+
+static Log::Channel g_channel(g_categories, SystemLog::System,
+                              /*internal=*/true);
+
+template <> Log::Channel &lldb_private::LogChannelFor<SystemLog>() {
+  return g_channel;
+}
+
+void LogChannelSystem::Initialize() {
+  Log::Register("system", g_channel);
+  const uint32_t log_options = LLDB_LOG_OPTION_PREPEND_THREAD_NAME;
+  Log::EnableLogChannel(std::make_shared<SystemLogHandler>(), log_options,
+                        "system", {"all"}, llvm::nulls());
+}
+
+void LogChannelSystem::Terminate() { Log::Unregister("system"); }
+
 #if !defined(__APPLE__) && !defined(_WIN32)
 static thread_result_t
 MonitorChildProcessThreadFunction(::pid_t pid,
diff --git a/lldb/source/Host/common/HostInfoBase.cpp b/lldb/source/Host/common/HostInfoBase.cpp
index 5c44c2f38b2879..89dfe4a9e9baa3 100644
--- a/lldb/source/Host/common/HostInfoBase.cpp
+++ b/lldb/source/Host/common/HostInfoBase.cpp
@@ -76,9 +76,11 @@ static HostInfoBase::SharedLibraryDirectoryHelper *g_shlib_dir_helper = nullptr;
 void HostInfoBase::Initialize(SharedLibraryDirectoryHelper *helper) {
   g_shlib_dir_helper = helper;
   g_fields = new HostInfoBaseFields();
+  LogChannelSystem::Initialize();
 }
 
 void HostInfoBase::Terminate() {
+  LogChannelSystem::Terminate();
   g_shlib_dir_helper = nullptr;
   delete g_fields;
   g_fields = nullptr;
diff --git a/lldb/source/Utility/Log.cpp b/lldb/source/Utility/Log.cpp
index 6713a5bd758204..040bd47606ce18 100644
--- a/lldb/source/Utility/Log.cpp
+++ b/lldb/source/Utility/Log.cpp
@@ -245,6 +245,11 @@ bool Log::DisableLogChannel(llvm::StringRef channel,
     error_stream << llvm::formatv("Invalid log channel '{0}'.\n", channel);
     return false;
   }
+  if (iter->second.m_channel.internal) {
+    error_stream << llvm::formatv(
+        "Cannot disable internal log channel '{0}'.\n", channel);
+    return false;
+  }
   MaskType flags = categories.empty()
                        ? std::numeric_limits<MaskType>::max()
                        : GetFlags(error_stream, *iter, categories);
@@ -296,8 +301,10 @@ void Log::ForEachChannelCategory(
 
 std::vector<llvm::StringRef> Log::ListChannels() {
   std::vector<llvm::StringRef> result;
-  for (const auto &channel : *g_channel_map)
-    result.push_back(channel.first());
+  for (const auto &entry : *g_channel_map) {
+    if (!entry.second.m_channel.internal)
+      result.push_back(entry.first());
+  }
   return result;
 }
 
@@ -307,8 +314,10 @@ void Log::ListAllLogChannels(llvm::raw_ostream &stream) {
     return;
   }
 
-  for (const auto &channel : *g_channel_map)
-    ListCategories(stream, channel);
+  for (const auto &entry : *g_channel_map) {
+    if (!entry.second.m_channel.internal)
+      ListCategories(stream, entry);
+  }
 }
 
 bool Log::GetVerbose() const {
diff --git a/lldb/test/Shell/Host/TestSytemLogChannel.test b/lldb/test/Shell/Host/TestSytemLogChannel.test
new file mode 100644
index 00000000000000..9ac4985da6164e
--- /dev/null
+++ b/lldb/test/Shell/Host/TestSytemLogChannel.test
@@ -0,0 +1,3 @@
+RUN: %lldb -o 'log list' -o 'log disable system' 2>&1 | FileCheck %s
+CHECK-NOT: Logging categories for 'system'
+CHECK: Cannot disable internal log channel 'system'.

@@ -0,0 +1,3 @@
RUN: %lldb -o 'log list' -o 'log disable system' 2>&1 | FileCheck %s
CHECK-NOT: Logging categories for 'system'
CHECK: Cannot disable internal log channel 'system'.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is bikeshedding but perhaps:

`system` is an internal log channel, internal channels can not be disabled.

Just to be extra clear that we're not just printing "internal log" for the sake of it, it's the reason why it cannot be disabled.

(...maybe I am too used to software lying to me and should relax 🤣 )

@@ -31,6 +31,22 @@ class ProcessInstanceInfo;
class ProcessInstanceInfoMatch;
typedef std::vector<ProcessInstanceInfo> ProcessInstanceInfoList;

// Always on system log category and channel.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is the log naming system system or for now is it just system and we might add sub-channels later?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have a comment that explains what this channel is supposed to be used for?

@DavidSpickett
Copy link
Collaborator

Unlike other, existing log channels, it is not exposed to users.

This is presumably because:

  1. It contains very low level messages they likely won't be interested in.
  2. If it weren't hidden, it would be printing into their sessions all the time.

Users can still list it, I see that from the test, but that's fine we don't need to lie about its existence.

If someone suspects that a relevant message might be in this channel how do they look at it? Do they crash lldb / use a command to dump an archive of logs?

The channel is meant to be used sparsely and deliberately for logging high-value information to the system log.

What is an example of that? So that folks (aka me) have an idea what might go here for other OSs. Is it stuff like this process was killed for this reason, this system resource had to be cleared, that sort of thing?

@DavidSpickett
Copy link
Collaborator

We have a similar concept in the downstream Swift fork and this has proven to be extremely valuable. This is especially true on macOS where system log messages are automatically captured as part of a sysdiagnose.

Does this mean someone has already been putting non-MacOS messages in there in the Swift fork? Would be interested to know what those are.

@labath
Copy link
Collaborator

labath commented Sep 13, 2024

My first thought when seeing (before opening the phabricator patch) this was "wouldn't this be better off as a separate API". After opening the link, I saw that this was the last comment on it as well. So, what has changed since then?

My think is that, our logging API is a fairly complex piece of machinery, and I think this is largely due to the fact that we're allowing the user to customize it (change the target, verbosity, etc.). For the this log channel, you basically take away all of that, so what's the point in going through all of that? Why not just have a single SystemLog function that does what you want?

@JDevlieghere
Copy link
Member Author

JDevlieghere commented Sep 13, 2024

Unlike other, existing log channels, it is not exposed to users.

This is presumably because:

  1. It contains very low level messages they likely won't be interested in.
  2. If it weren't hidden, it would be printing into their sessions all the time.

Users can still list it, I see that from the test, but that's fine we don't need to lie about its existence.

Actually the test is testing that it cannot (CHECK-NOT) be listed. The goal is to make the fact that it's a log channel and implementation detail, as if we had used a dedicated API as Pavel suggested on Phab and here.

If someone suspects that a relevant message might be in this channel how do they look at it? Do they crash lldb / use a command to dump an archive of logs?

That depends on the system they're on: on macOS that would be by looking at Console.app. On Linux it's a file in /var/log. I wouldn't expect someone that isn't an LLDB developer to ever have to look at these logs though.

What is an example of that? So that folks (aka me) have an idea what might go here for other OSs. Is it stuff like this process was killed for this reason, this system resource had to be cleared, that sort of thing?

A really trivial example is logging the lldb version in the system log, which is something we do already do on the Swift fork. At Apple we have different toolchains with different versions of LLDB and surprisingly often a bug report will state the wrong version of LLDB. At this point this has probably saved me hours of wasted time.

Does this mean someone has already been putting non-MacOS messages in there in the Swift fork? Would be interested to know what those are.

Not quite, the Swift fork uses its own category & channel. Unlike the always-on channel I'm proposing here, the Swift one is meant to be user-visible. Users can generate the log file with a command (swift-healthcheck) and are expected to be able to diagnose things such as project misconfigurations.

If you look at the implementation, you can see that it's using a T-loghandler that writes both to a buffer and to the system log.

My first thought when seeing (before opening the phabricator patch) this was "wouldn't this be better off as a separate API". After opening the link, I saw that this was the last comment on it as well. So, what has changed since then?

I wouldn't say anything has changed, but here's why I'm proposing the log channel approach again:

  • We've had a very positive experience with the Swift log category. I'm the screener for the LLDB component so that's something I've witnessed (and benefited from) first hand. I'm sure Dave and Adrian can share a bunch of success stories as they deal with the actual details. Of course that says more about the concept than the implementation.
  • Everyone (else) I talked to liked the idea of reusing the logging system because that's what we already use and are familiar with. Promoting a log message to be part of this log becomes as easy as adding a GetLog and using the same macro.
  • I expect that in a bunch of places, you would like these new (or existing) log message to go both to the system log and to a user-visible log. If something egregious goes wrong related to types, you probably want to log it to the types log as well. With a separate API, you'll end up doing something like create the log message, then pass it to LLDB_LOG and then calling Host::SystemLog. We have a few examples where we do the same dance for Progress events and Signposts (LLDB_SCOPED_TIMER) which I'm also trying to solve in [lldb] Emit signpost intervals for progress events (NFC) #108498.

Ultimately I care more about the functionality than the implementation so I'm not opposed to the separate API, but I also want to encourage adoption across LLDB.

@labath
Copy link
Collaborator

labath commented Sep 16, 2024

  • Everyone (else) I talked to liked the idea of reusing the logging system because that's what we already use and are familiar with. Promoting a log message to be part of this log becomes as easy as adding a GetLog and using the same macro.

I'm all for code reuse, and reusing some part of the logging machinery for this makes sense to me. The thing I'm not sure about is whether this is plugging into the machinery at the right level. The most irritating part is the internal flag -- like, we do all the motions to place the channel into the global log channel map (which is the source of truth for all user-facing log commands), but then set this flag which basically says "pretend this channel isn't here".

I think this would look better if the flag wasn't there. Like, what if we had a separate way to create/register one of these "system" channels -- one which does not involve putting it in a list with all the other channels. Maybe the Initialize function could be something like:

Log g_system(g_system_channel);
g_system->Enable(...);

(The Enable function is currently private, but I think it would make sense to make it public, especially since it's possible (like you're effectively doing now) to bypass this restriction by calling the static string-based EnableLogChannel function. Alternatively, we could make the existence of this channel a feature of the generic log machinery (I expect we'll never want to have more than one of these), and then we can do this work from within Log::Initialize)

@JDevlieghere
Copy link
Member Author

Rebased + addressed @labath and @adrian-prantl's feedback.

@@ -83,6 +85,9 @@ llvm::Error SystemInitializerFull::Initialize() {
// Use the Debugger's LLDBAssert callback.
SetLLDBAssertCallback(Debugger::AssertCallback);

// Log the LLDB version to the system log.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this tells us anything new.

@@ -272,6 +272,12 @@ class Log final {
void VAFormatf(llvm::StringRef file, llvm::StringRef function,
const char *format, va_list args);

void Enable(const std::shared_ptr<LogHandler> &handler_sp,
uint32_t options = 0,
MaskType flags = std::numeric_limits<Log::MaskType>::max());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log channels have a concept of a default set of categories, so if we're going to have a default here, it better be it. That could be achieved by making this an optional<MaskType> (and then you can also simplify the code in EnableLogChannel).

@@ -20,6 +20,8 @@
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/TargetSelect.h"

#include "lldb/Version/Version.h"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Group this with the other lldb includes?

Add an "always on" log category and channel. Unlike other, existing log
channels, it is not exposed to users. The channel is meant to be used
sparsely and deliberately for logging high-value information to the
system log.

We have a similar concept in the downstream Swift fork and this has
proven to be extremely valuable. This is especially true on macOS where
system log messages are automatically captured as part of a sysdiagnose.

This patch revives https://reviews.llvm.org/D135621 although the purpose
is slightly different (logging to the system log rather than to a ring
buffer dumped as part of the diagnostics). This patch addresses all the
remaining outstanding comments.
@JDevlieghere JDevlieghere merged commit c77b107 into llvm:main Oct 10, 2024
7 checks passed
@JDevlieghere JDevlieghere deleted the always-on-system-log branch October 10, 2024 15:47
@antmox
Copy link
Contributor

antmox commented Oct 11, 2024

Hi, could this patch be the cause of lldb-aarch64-windows bot failure ?
https://lab.llvm.org/buildbot/#/builders/141/builds/3100

DanielCChen pushed a commit to DanielCChen/llvm-project that referenced this pull request Oct 16, 2024
Add an "always on" log category and channel. Unlike other, existing log
channels, it is not exposed to users. The channel is meant to be used
sparsely and deliberately for logging high-value information to the
system log.

We have a similar concept in the downstream Swift fork and this has
proven to be extremely valuable. This is especially true on macOS where
system log messages are automatically captured as part of a sysdiagnose.
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants