forked from OSchip/llvm-project
[lldb] Add a log dump command
Add a log dump command to dump logs to a file. This only works for channels that have a log handler associated that supports dumping. For now that's limited to the circular log handler, but more could be added in the future. Differential revision: https://reviews.llvm.org/D128557
This commit is contained in:
parent
becbbb7e3c
commit
9bdb7e5734
|
@ -261,6 +261,12 @@ class SystemLogHandler : public LogHandler {
|
|||
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
|
||||
|
|
|
@ -49,6 +49,12 @@ class LogHandler {
|
|||
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 {
|
||||
|
@ -59,9 +65,13 @@ public:
|
|||
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 {
|
||||
|
@ -70,9 +80,13 @@ public:
|
|||
|
||||
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 {
|
||||
|
@ -82,6 +96,9 @@ public:
|
|||
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;
|
||||
|
@ -92,6 +109,7 @@ private:
|
|||
const size_t m_size = 0;
|
||||
size_t m_next_index = 0;
|
||||
size_t m_total_count = 0;
|
||||
static char ID;
|
||||
};
|
||||
|
||||
class Log final {
|
||||
|
@ -169,6 +187,10 @@ public:
|
|||
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);
|
||||
|
||||
|
@ -258,6 +280,8 @@ private:
|
|||
|
||||
void Disable(uint32_t flags);
|
||||
|
||||
bool Dump(llvm::raw_ostream &stream);
|
||||
|
||||
typedef llvm::StringMap<Log> ChannelMap;
|
||||
static llvm::ManagedStatic<ChannelMap> g_channel_map;
|
||||
|
||||
|
|
|
@ -56,6 +56,9 @@ static constexpr OptionEnumValues LogHandlerType() {
|
|||
#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();
|
||||
|
@ -345,6 +348,114 @@ protected:
|
|||
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:
|
||||
|
@ -554,6 +665,8 @@ CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter)
|
|||
CommandObjectSP(new CommandObjectLogDisable(interpreter)));
|
||||
LoadSubCommand("list",
|
||||
CommandObjectSP(new CommandObjectLogList(interpreter)));
|
||||
LoadSubCommand("dump",
|
||||
CommandObjectSP(new CommandObjectLogDump(interpreter)));
|
||||
LoadSubCommand("timers",
|
||||
CommandObjectSP(new CommandObjectLogTimer(interpreter)));
|
||||
}
|
||||
|
|
|
@ -456,6 +456,11 @@ let Command = "log enable" in {
|
|||
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()">,
|
||||
|
|
|
@ -632,6 +632,8 @@ uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info,
|
|||
return result;
|
||||
}
|
||||
|
||||
char SystemLogHandler::ID;
|
||||
|
||||
SystemLogHandler::SystemLogHandler() {}
|
||||
|
||||
void SystemLogHandler::Emit(llvm::StringRef message) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#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"
|
||||
|
@ -34,6 +35,11 @@
|
|||
|
||||
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(
|
||||
|
@ -106,6 +112,16 @@ void Log::Disable(uint32_t flags) {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -222,6 +238,22 @@ bool Log::DisableLogChannel(llvm::StringRef channel,
|
|||
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);
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
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"])
|
Loading…
Reference in New Issue