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 m_current_event_id; llvm::StringMap> m_stream_handlers; + llvm::StringMap> m_rotating_handlers; std::shared_ptr m_callback_handler_sp; ConstString m_instance_name; static LoadPluginCallbackType g_load_plugin_callback; 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 ", + "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 ", + "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 | dump >") { + 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/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 handler = entry.second.lock(); + if (!handler) + continue; + + std::string filename = (llvm::Twine(category) + ".log").str(); + FileSpec log_file = dir.CopyByAppendingPathComponent(filename); + + llvm::Expected 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 language) { 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 RotatingLogHandlerCreate(size_t size) { +std::shared_ptr RotatingLogHandler::Create(size_t size) { return std::make_shared(size); }