public:
SystemLogHandler();
void Emit(llvm::StringRef message) override;
+
+ bool isA(const void *ClassID) const override { return ClassID == &ID; }
+ static bool classof(const LogHandler *obj) { return obj->isA(&ID); }
+
+private:
+ static char ID;
};
} // namespace lldb_private
public:
virtual ~LogHandler() = default;
virtual void Emit(llvm::StringRef message) = 0;
+
+ virtual bool isA(const void *ClassID) const { return ClassID == &ID; }
+ static bool classof(const LogHandler *obj) { return obj->isA(&ID); }
+
+private:
+ static char ID;
};
class StreamLogHandler : public LogHandler {
void Emit(llvm::StringRef message) override;
void Flush();
+ bool isA(const void *ClassID) const override { return ClassID == &ID; }
+ static bool classof(const LogHandler *obj) { return obj->isA(&ID); }
+
private:
std::mutex m_mutex;
llvm::raw_fd_ostream m_stream;
+ static char ID;
};
class CallbackLogHandler : public LogHandler {
void Emit(llvm::StringRef message) override;
+ bool isA(const void *ClassID) const override { return ClassID == &ID; }
+ static bool classof(const LogHandler *obj) { return obj->isA(&ID); }
+
private:
lldb::LogOutputCallback m_callback;
void *m_baton;
+ static char ID;
};
class RotatingLogHandler : public LogHandler {
void Emit(llvm::StringRef message) override;
void Dump(llvm::raw_ostream &stream) const;
+ bool isA(const void *ClassID) const override { return ClassID == &ID; }
+ static bool classof(const LogHandler *obj) { return obj->isA(&ID); }
+
private:
size_t NormalizeIndex(size_t i) const;
size_t GetNumMessages() const;
const size_t m_size = 0;
size_t m_next_index = 0;
size_t m_total_count = 0;
+ static char ID;
};
class Log final {
llvm::ArrayRef<const char *> categories,
llvm::raw_ostream &error_stream);
+ static bool DumpLogChannel(llvm::StringRef channel,
+ llvm::raw_ostream &output_stream,
+ llvm::raw_ostream &error_stream);
+
static bool ListChannelCategories(llvm::StringRef channel,
llvm::raw_ostream &stream);
void Disable(uint32_t flags);
+ bool Dump(llvm::raw_ostream &stream);
+
typedef llvm::StringMap<Log> ChannelMap;
static llvm::ManagedStatic<ChannelMap> g_channel_map;
#define LLDB_OPTIONS_log_enable
#include "CommandOptions.inc"
+#define LLDB_OPTIONS_log_dump
+#include "CommandOptions.inc"
+
/// Common completion logic for log enable/disable.
static void CompleteEnableDisable(CompletionRequest &request) {
size_t arg_index = request.GetCursorIndex();
return result.Succeeded();
}
};
+class CommandObjectLogDump : public CommandObjectParsed {
+public:
+ CommandObjectLogDump(CommandInterpreter &interpreter)
+ : CommandObjectParsed(interpreter, "log dump",
+ "dump circular buffer logs", nullptr) {
+ CommandArgumentEntry arg1;
+ CommandArgumentData channel_arg;
+
+ // Define the first (and only) variant of this arg.
+ channel_arg.arg_type = eArgTypeLogChannel;
+ channel_arg.arg_repetition = eArgRepeatPlain;
+
+ // There is only one variant this argument could be; put it into the
+ // argument entry.
+ arg1.push_back(channel_arg);
+
+ // Push the data for the first argument into the m_arguments vector.
+ m_arguments.push_back(arg1);
+ }
+
+ ~CommandObjectLogDump() override = default;
+
+ Options *GetOptions() override { return &m_options; }
+
+ class CommandOptions : public Options {
+ public:
+ CommandOptions() = default;
+
+ ~CommandOptions() override = default;
+
+ Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+ ExecutionContext *execution_context) override {
+ Status error;
+ const int short_option = m_getopt_table[option_idx].val;
+
+ switch (short_option) {
+ case 'f':
+ log_file.SetFile(option_arg, FileSpec::Style::native);
+ FileSystem::Instance().Resolve(log_file);
+ break;
+ default:
+ llvm_unreachable("Unimplemented option");
+ }
+
+ return error;
+ }
+
+ void OptionParsingStarting(ExecutionContext *execution_context) override {
+ log_file.Clear();
+ }
+
+ llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+ return llvm::makeArrayRef(g_log_dump_options);
+ }
+
+ FileSpec log_file;
+ };
+
+ void
+ HandleArgumentCompletion(CompletionRequest &request,
+ OptionElementVector &opt_element_vector) override {
+ CompleteEnableDisable(request);
+ }
+
+protected:
+ bool DoExecute(Args &args, CommandReturnObject &result) override {
+ if (args.empty()) {
+ result.AppendErrorWithFormat(
+ "%s takes a log channel and one or more log types.\n",
+ m_cmd_name.c_str());
+ return false;
+ }
+
+ std::unique_ptr<llvm::raw_ostream> stream_up;
+ if (m_options.log_file) {
+ const File::OpenOptions flags = File::eOpenOptionWriteOnly |
+ File::eOpenOptionCanCreate |
+ File::eOpenOptionTruncate;
+ llvm::Expected<FileUP> file = FileSystem::Instance().Open(
+ m_options.log_file, flags, lldb::eFilePermissionsFileDefault, false);
+ if (!file) {
+ result.AppendErrorWithFormat("Unable to open log file '%s': %s",
+ m_options.log_file.GetCString(),
+ llvm::toString(file.takeError()).c_str());
+ return false;
+ }
+ stream_up = std::make_unique<llvm::raw_fd_ostream>(
+ (*file)->GetDescriptor(), /*shouldClose=*/true);
+ } else {
+ stream_up = std::make_unique<llvm::raw_fd_ostream>(
+ GetDebugger().GetOutputFile().GetDescriptor(), /*shouldClose=*/false);
+ }
+
+ const std::string channel = std::string(args[0].ref());
+ std::string error;
+ llvm::raw_string_ostream error_stream(error);
+ if (Log::DumpLogChannel(channel, *stream_up, error_stream)) {
+ result.SetStatus(eReturnStatusSuccessFinishNoResult);
+ } else {
+ result.SetStatus(eReturnStatusFailed);
+ result.GetErrorStream() << error_stream.str();
+ }
+
+ return result.Succeeded();
+ }
+
+ CommandOptions m_options;
+};
class CommandObjectLogTimerEnable : public CommandObjectParsed {
public:
CommandObjectSP(new CommandObjectLogDisable(interpreter)));
LoadSubCommand("list",
CommandObjectSP(new CommandObjectLogList(interpreter)));
+ LoadSubCommand("dump",
+ CommandObjectSP(new CommandObjectLogDump(interpreter)));
LoadSubCommand("timers",
CommandObjectSP(new CommandObjectLogTimer(interpreter)));
}
Desc<"Prepend the names of files and function that generate the logs.">;
}
+let Command = "log dump" in {
+ def log_dump_file : Option<"file", "f">, Group<1>, Arg<"Filename">,
+ Desc<"Set the destination file to dump to.">;
+}
+
let Command = "reproducer dump" in {
def reproducer_provider : Option<"provider", "p">, Group<1>,
EnumArg<"None", "ReproducerProviderType()">,
return result;
}
+char SystemLogHandler::ID;
+
SystemLogHandler::SystemLogHandler() {}
void SystemLogHandler::Emit(llvm::StringRef message) {
#include "llvm/ADT/Twine.h"
#include "llvm/ADT/iterator.h"
+#include "llvm/Support/Casting.h"
#include "llvm/Support/Chrono.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/Path.h"
using namespace lldb_private;
+char LogHandler::ID;
+char StreamLogHandler::ID;
+char CallbackLogHandler::ID;
+char RotatingLogHandler::ID;
+
llvm::ManagedStatic<Log::ChannelMap> Log::g_channel_map;
void Log::ForEachCategory(
}
}
+bool Log::Dump(llvm::raw_ostream &output_stream) {
+ llvm::sys::ScopedReader lock(m_mutex);
+ if (RotatingLogHandler *handler =
+ llvm::dyn_cast_or_null<RotatingLogHandler>(m_handler.get())) {
+ handler->Dump(output_stream);
+ return true;
+ }
+ return false;
+}
+
const Flags Log::GetOptions() const {
return m_options.load(std::memory_order_relaxed);
}
return true;
}
+bool Log::DumpLogChannel(llvm::StringRef channel,
+ llvm::raw_ostream &output_stream,
+ llvm::raw_ostream &error_stream) {
+ auto iter = g_channel_map->find(channel);
+ if (iter == g_channel_map->end()) {
+ error_stream << llvm::formatv("Invalid log channel '{0}'.\n", channel);
+ return false;
+ }
+ if (!iter->second.Dump(output_stream)) {
+ error_stream << llvm::formatv(
+ "log channel '{0}' does not support dumping.\n", channel);
+ return false;
+ }
+ return true;
+}
+
bool Log::ListChannelCategories(llvm::StringRef channel,
llvm::raw_ostream &stream) {
auto ch = g_channel_map->find(channel);
--- /dev/null
+"""
+Test lldb log handlers.
+"""
+
+import os
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class LogHandlerTestCase(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def setUp(self):
+ TestBase.setUp(self)
+ self.log_file = self.getBuildArtifact("log-file.txt")
+ if (os.path.exists(self.log_file)):
+ os.remove(self.log_file)
+
+ def test_circular(self):
+ self.runCmd("log enable -b 5 -h circular lldb commands")
+ self.runCmd("bogus", check=False)
+ self.runCmd("log dump lldb -f {}".format(self.log_file))
+
+ with open(self.log_file, 'r') as f:
+ log_lines = f.readlines()
+
+ self.assertEqual(len(log_lines), 5)
+
+ found_command_log_dump = False
+ found_command_bogus = False
+
+ for line in log_lines:
+ if 'Processing command: log dump' in line:
+ found_command_log_dump = True
+ if 'Processing command: bogus' in line:
+ found_command_bogus = True
+
+ self.assertTrue(found_command_log_dump)
+ self.assertFalse(found_command_bogus)
+
+ def test_circular_no_buffer_size(self):
+ self.expect(
+ "log enable -h circular lldb commands",
+ error=True,
+ substrs=[
+ 'the circular buffer handler requires a non-zero buffer size'
+ ])
+
+ def test_dump_unsupported(self):
+ self.runCmd("log enable lldb commands -f {}".format(self.log_file))
+ self.expect("log dump lldb",
+ error=True,
+ substrs=["log channel 'lldb' does not support dumping"])