JDevlieghere created this revision. JDevlieghere added reviewers: labath, clayborg, jingham, kastiglione. Herald added a project: All. JDevlieghere requested review of this revision.
This patch adds a buffer logging mode to lldb. It can be enabled with `log buffered enable`, which enables logging for the default category for every log channel to an in-memory circular buffer. The buffered log messages can be written to disk with the `log buffered dump` command. The latter command takes a directory as it input argument and creates a log file per log channel. As of right now these two new commands are pretty bare bones. For example, we do not support specifying categories or logging options. This is intentional, because I don't see them as the primary way to interact with this feature. Instead, I imagine a world where we have a small but meaningful subset of logging always enabled in buffered mode. When something goes wrong, the user types a command (similar to `sysdiagnose` on macOS) and we'll dump the logs as part of a package that's meant to help us investigate the issue. https://reviews.llvm.org/D127986 Files: lldb/include/lldb/Core/Debugger.h lldb/source/Commands/CommandObjectLog.cpp lldb/source/Core/Debugger.cpp lldb/source/Utility/Log.cpp
Index: lldb/source/Utility/Log.cpp =================================================================== --- lldb/source/Utility/Log.cpp +++ lldb/source/Utility/Log.cpp @@ -403,6 +403,6 @@ stream.flush(); } -std::shared_ptr<RotatingLogHandler> RotatingLogHandlerCreate(size_t size) { +std::shared_ptr<RotatingLogHandler> RotatingLogHandler::Create(size_t size) { return std::make_shared<RotatingLogHandler>(size); } Index: lldb/source/Core/Debugger.cpp =================================================================== --- lldb/source/Core/Debugger.cpp +++ lldb/source/Core/Debugger.cpp @@ -1453,6 +1453,57 @@ error_stream); } +bool Debugger::EnableRotatingLog(unsigned size, uint32_t log_options, + llvm::raw_ostream &error_stream) { + if (!m_rotating_handlers.empty()) + return false; + + // Create a RotatingLogHandler for each channel's default category. + for (llvm::StringRef channel : Log::ListChannels()) { + auto log_handler_sp = RotatingLogHandler::Create(size); + m_rotating_handlers[channel] = log_handler_sp; + Log::EnableLogChannel(log_handler_sp, log_options, channel, {"default"}, + error_stream); + } + return true; +} + +bool Debugger::DumpRotatingLog(const FileSpec &dir, + llvm::raw_ostream &error_stream) { + if (m_rotating_handlers.empty()) + return false; + + assert(FileSystem::Instance().IsDirectory(dir) && + "Caller should have made sure the directory is valid"); + + const File::OpenOptions flags = File::eOpenOptionWriteOnly | + File::eOpenOptionCanCreate | + File::eOpenOptionTruncate; + + for (const auto &entry : m_rotating_handlers) { + llvm::StringRef category = entry.first(); + std::shared_ptr<RotatingLogHandler> handler = entry.second.lock(); + if (!handler) + continue; + + std::string filename = (llvm::Twine(category) + ".log").str(); + FileSpec log_file = dir.CopyByAppendingPathComponent(filename); + + llvm::Expected<FileUP> file = FileSystem::Instance().Open( + log_file, flags, lldb::eFilePermissionsFileDefault, false); + if (!file) { + error_stream << "Unable to open log file '" << log_file.GetPath() + << "': " << llvm::toString(file.takeError()) << "\n"; + continue; + } + + llvm::raw_fd_ostream stream((*file)->GetDescriptor(), /*shouldClose=*/true); + handler->Dump(stream); + } + + return true; +} + ScriptInterpreter * Debugger::GetScriptInterpreter(bool can_create, llvm::Optional<lldb::ScriptLanguage> language) { Index: lldb/source/Commands/CommandObjectLog.cpp =================================================================== --- lldb/source/Commands/CommandObjectLog.cpp +++ lldb/source/Commands/CommandObjectLog.cpp @@ -493,6 +493,120 @@ ~CommandObjectLogTimer() override = default; }; +class CommandObjectLogBufferedEnable : public CommandObjectParsed { +public: + CommandObjectLogBufferedEnable(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "log buffered enable <buffer size>", + "enable logging to an in-memory buffer for the default category of " + "every log channel. These log files can later be written to disk " + "with the log buffer dump command.", + nullptr) { + + CommandArgumentEntry arg; + CommandArgumentData size_arg; + + // Define the first (and only) variant of this arg. + size_arg.arg_type = eArgTypeUnsignedInteger; + size_arg.arg_repetition = eArgRepeatPlain; + + arg.push_back(size_arg); + m_arguments.push_back(arg); + } + + ~CommandObjectLogBufferedEnable() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + uint32_t log_options = 0; + size_t buffer_size = 100; + + if (args.GetArgumentCount() == 1) { + if (args[0].ref().consumeInteger(0, buffer_size)) { + result.AppendErrorWithFormat("invalid buffer size: %s", + args[0].ref().data()); + } + } + + std::string error; + llvm::raw_string_ostream error_stream(error); + bool success = + GetDebugger().EnableRotatingLog(buffer_size, log_options, error_stream); + + result.GetErrorStream() << error_stream.str(); + result.SetStatus(success ? eReturnStatusSuccessFinishNoResult + : eReturnStatusFailed); + return result.Succeeded(); + } +}; + +class CommandObjectLogBufferedDump : public CommandObjectParsed { +public: + CommandObjectLogBufferedDump(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "log buffered dump <directory>", + "dump the buffer logs to disk. This command will create a log file " + "for every log channel in the given directory containing the " + "contents of the in-memory buffer at the time of the dump.", + nullptr) { + CommandArgumentEntry arg; + CommandArgumentData dir_arg; + + // Define the first (and only) variant of this arg. + dir_arg.arg_type = eArgTypeDirectoryName; + dir_arg.arg_repetition = eArgRepeatPlain; + + arg.push_back(dir_arg); + m_arguments.push_back(arg); + } + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eDiskDirectoryCompletion, + request, nullptr); + } + + ~CommandObjectLogBufferedDump() override = default; + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + if (args.GetArgumentCount() != 1) { + result.AppendErrorWithFormat("%s takes a directory argument.\n", + m_cmd_name.c_str()); + return false; + } + + FileSpec dir(args[0].ref()); + + std::string error; + llvm::raw_string_ostream error_stream(error); + bool success = GetDebugger().DumpRotatingLog(dir, error_stream); + result.GetErrorStream() << error_stream.str(); + result.SetStatus(eReturnStatusSuccessFinishResult); + result.SetStatus(success ? eReturnStatusSuccessFinishNoResult + : eReturnStatusFailed); + return result.Succeeded(); + } +}; + +class CommandObjectLogBuffered : public CommandObjectMultiword { +public: + CommandObjectLogBuffered(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "log buffered", + "Enable and dump buffered in-memory logging.", + "log buffered < enable <buffer size> | dump <directory> >") { + LoadSubCommand("enable", CommandObjectSP(new CommandObjectLogBufferedEnable( + interpreter))); + LoadSubCommand( + "dump", CommandObjectSP(new CommandObjectLogBufferedDump(interpreter))); + } + + ~CommandObjectLogBuffered() override = default; +}; + CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter) : CommandObjectMultiword(interpreter, "log", "Commands controlling LLDB internal logging.", @@ -505,6 +619,8 @@ CommandObjectSP(new CommandObjectLogList(interpreter))); LoadSubCommand("timers", CommandObjectSP(new CommandObjectLogTimer(interpreter))); + LoadSubCommand("buffered", + CommandObjectSP(new CommandObjectLogBuffered(interpreter))); } CommandObjectLog::~CommandObjectLog() = default; Index: lldb/include/lldb/Core/Debugger.h =================================================================== --- lldb/include/lldb/Core/Debugger.h +++ lldb/include/lldb/Core/Debugger.h @@ -53,11 +53,12 @@ } namespace lldb_private { -class LogHandler; -class CallbackLogHandler; class Address; +class CallbackLogHandler; class CommandInterpreter; +class LogHandler; class Process; +class RotatingLogHandler; class Stream; class SymbolContext; class Target; @@ -247,6 +248,10 @@ llvm::StringRef log_file, uint32_t log_options, llvm::raw_ostream &error_stream); + bool EnableRotatingLog(unsigned size, uint32_t log_options, + llvm::raw_ostream &error_stream); + bool DumpRotatingLog(const FileSpec &dir, llvm::raw_ostream &error_stream); + void SetLoggingCallback(lldb::LogOutputCallback log_callback, void *baton); // Properties Functions @@ -556,6 +561,7 @@ llvm::Optional<uint64_t> m_current_event_id; llvm::StringMap<std::weak_ptr<LogHandler>> m_stream_handlers; + llvm::StringMap<std::weak_ptr<RotatingLogHandler>> m_rotating_handlers; std::shared_ptr<CallbackLogHandler> m_callback_handler_sp; ConstString m_instance_name; static LoadPluginCallbackType g_load_plugin_callback;
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits