blob: 8465a7f9381239446d5999d3d6f00369e101b8b7 [file] [log] [blame] [edit]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/shared/logging/debug.h"
#include <lib/syslog/cpp/log_level.h>
#include <lib/syslog/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>
#include <chrono>
#include <mutex>
#include <set>
#include "src/developer/debug/shared/logging/logging.h"
#include "src/lib/files/path.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace debug {
namespace {
// This marks the moment our process starts.
std::chrono::steady_clock::time_point kStartTime = std::chrono::steady_clock::now();
std::set<LogCategory>& GetLogCategories() {
static std::set<LogCategory> categories = {LogCategory::kAll};
return categories;
}
bool IsLogCategoryActive(LogCategory category) {
// If TRACE-level logging is enabled, always log all debug log categories.
if (fuchsia_logging::IsSeverityEnabled(fuchsia_logging::LogSeverity::Trace)) {
return true;
}
if (!IsDebugLoggingActive())
return false;
if (category == LogCategory::kAll)
return true;
const auto& active_categories = GetLogCategories();
if (active_categories.count(LogCategory::kAll) > 0)
return true;
return active_categories.count(category) > 0;
}
const char* LogCategoryToString(LogCategory category) {
switch (category) {
case LogCategory::kAgent:
return "Agent";
case LogCategory::kArchArm64:
return "arm64";
case LogCategory::kArchx64:
return "x64";
case LogCategory::kBreakpoint:
return "Breakpoint";
case LogCategory::kJob:
return "Job";
case LogCategory::kMessageLoop:
return "Loop";
case LogCategory::kProcess:
return "Process";
case LogCategory::kRemoteAPI:
return "DebugAPI";
case LogCategory::kSession:
return "Session";
case LogCategory::kSetting:
return "Setting";
case LogCategory::kTest:
return "Test";
case LogCategory::kTiming:
return "Timing";
case LogCategory::kThread:
return "Thread";
case LogCategory::kWatchpoint:
return "Watchpoint";
case LogCategory::kWorkerPool:
return "WorkerPool";
case LogCategory::kDebugAdapter:
return "DebugAdapter";
case LogCategory::kBuildIDIndex:
return "BuildIDIndex";
case LogCategory::kProcessSymbols:
return "ProcessSymbols";
case LogCategory::kModuleSymbols:
return "ModuleSymbols";
case LogCategory::kSystemSymbols:
return "SystemSymbols";
case LogCategory::kDownloadManager:
return "DownloadManager";
case LogCategory::kAll:
return "All";
case LogCategory::kNone:
return "<none>";
}
FX_NOTREACHED();
return "<unknown>";
}
// Returns how many seconds have passed since the program started.
double SecondsSinceStart() {
return std::chrono::duration<double>(std::chrono::steady_clock::now() - kStartTime).count();
}
} // namespace
bool IsDebugLoggingActive() {
return fuchsia_logging::IsSeverityEnabled(fuchsia_logging::LogSeverity::Debug);
}
void SetDebugLogging(bool activate) {
fuchsia_logging::LogSettingsBuilder builder;
if (activate) {
builder.WithMinLogSeverity(fuchsia_logging::LogSeverity::Debug);
}
builder.BuildAndInitialize();
}
void SetLogCategories(std::initializer_list<LogCategory> categories) {
auto& active_categories = GetLogCategories();
active_categories.clear();
for (LogCategory category : categories) {
active_categories.insert(category);
}
}
// Log Tree ----------------------------------------------------------------------------------------
//
// To facilitate logging, messages are appended to a tree and actually filled on the logging
// instance destructor. This permits to correctly track under which block each message was logged
// and give better context on output.
//
// The pop gets the message information because the logging logic using ostreams. This means that
// the actual message is constructored *after* the logging object has been constructed, which is
// the obvious message to push an entry. Plus, this logic permits to do messages that have
// information that it's only present after the block is done (like timing information).
//
// IMPORTANT: Because this delays when the log output, any abnormal termination (eg. crash) might
// eat the latest batch of logs that is currently on the stack. Possible workaround is
// having an exception handler in fuchsia and/or a signal handler in linux to flush the
// rest of the output in the case of a crash.
namespace {
// Logs are aranged in a tree that permits going back to the parent.
struct LogEntry {
LogCategory category = LogCategory::kNone;
FileLineFunction location;
std::string msg;
double start_time = 0;
double end_time = 0;
std::vector<std::unique_ptr<LogEntry>> children;
LogEntry* parent = nullptr;
// If a statement is valid, it means that it has not been flushed out of the stack, thus the
// statement is still valid in memory. We can use this backpointer to flush the statement.
DebugLogStatement* statement = nullptr;
};
LogEntry gRootSlot;
LogEntry* gCurrentSlot = nullptr;
std::mutex gLogMutex;
// Output is dd:hh:mm:ss.<ms>. dd is only showen when non-zero.
std::string SecondsToTimeString(double ds) {
int total_secs = static_cast<int>(ds);
int s = total_secs % 60;
int total_min = total_secs / 60;
int m = total_min % 60;
int total_hours = total_min / 60;
int h = total_hours % 24;
int d = total_hours / 24;
int ms = static_cast<int>((ds - total_secs) * 1000);
// We don't want to add days if it's 0, as it adds noise and it will be rare to have them.
if (d == 0) {
return fxl::StringPrintf("%02d:%02d:%02d.%03d", h, m, s, ms);
} else {
return fxl::StringPrintf("%02d:%02d:%02d:%02d.%03d", d, h, m, s, ms);
}
}
// Output is <sec>.<ms> (eg. 32.453).
std::string DurationToString(double start, double end) {
double diff = end - start;
int s = static_cast<int>(diff);
int ms = static_cast<int>((diff - s) * 1000000);
return fxl::StringPrintf("%03d.%06ds", s, ms);
}
// Format is (depending on whether |entry.location| is valid or not:
// [<time>][<category>]<indent><log msg> (location invalid).
// [<time>][<category>]<indent>[<function>][<file:line>] <log msg>
std::string LogEntryToStr(const LogEntry& entry, int indent) {
auto start_time_str = SecondsToTimeString(entry.start_time);
const char* cat_str = LogCategoryToString(entry.category);
// No location is only timing information.
if (!entry.location.is_valid()) {
return fxl::StringPrintf("[%s][%10s]%*s%s", start_time_str.c_str(), cat_str, indent, "",
entry.msg.c_str());
}
auto duration_str = DurationToString(entry.start_time, entry.end_time);
auto file = files::GetBaseName(entry.location.file());
int line = entry.location.line();
const char* function = entry.location.function();
return fxl::StringPrintf("[%s][%s][%10s]%*s[%s:%d][%s] %s", start_time_str.c_str(),
duration_str.c_str(), cat_str, indent, "", file.c_str(), line, function,
entry.msg.c_str());
}
// Goes over the logging tree recursively and correctly indents the log messages into |logs|.
void UnwindLogTree(const LogEntry& entry, std::vector<std::string>* logs, int indent = 0) {
logs->emplace_back(LogEntryToStr(entry, indent));
for (auto& child : entry.children) {
UnwindLogTree(*child, logs, indent + 2);
}
}
void PushLogEntry(DebugLogStatement* statement) {
std::lock_guard<std::mutex> lock(gLogMutex);
// No slot means that it's a new message tree. Use the root.
if (!gCurrentSlot) {
gRootSlot = {};
gCurrentSlot = &gRootSlot;
} else {
// Push a child.
gCurrentSlot->children.push_back(std::make_unique<LogEntry>());
auto& child = gCurrentSlot->children.back();
child->parent = gCurrentSlot;
gCurrentSlot = child.get();
}
gCurrentSlot->statement = statement;
}
void PopLogEntry(LogCategory category, const FileLineFunction& location, const std::string& msg,
double start_time, double end_time) {
std::vector<std::string> logs;
{
std::lock_guard<std::mutex> lock(gLogMutex);
// Set the message that's going away to the child.
gCurrentSlot->category = category;
gCurrentSlot->location = location;
gCurrentSlot->msg = msg;
gCurrentSlot->start_time = start_time;
gCurrentSlot->end_time = end_time;
gCurrentSlot->statement = nullptr;
// While there is still a parent, we're not at the root.
if (gCurrentSlot->parent) {
gCurrentSlot = gCurrentSlot->parent;
return;
}
// We tried to pop the root, that means this logging tree is done and we're going to output it
// and reset everything.
UnwindLogTree(*gCurrentSlot, &logs);
gCurrentSlot = nullptr;
}
#if defined(__Fuchsia__)
for (auto& log : logs) {
FX_LOGS(DEBUG) << log;
}
#else
for (auto& log : logs) {
fprintf(stderr, "\r%s\r\n", log.c_str());
}
fflush(stderr);
#endif
}
} // namespace
DebugLogStatement::DebugLogStatement(FileLineFunction origin, LogCategory category)
: origin_(std::move(origin)), category_(category) {
if (!IsLogCategoryActive(category_))
return;
start_time_ = SecondsSinceStart();
should_log_ = true;
PushLogEntry(this);
}
DebugLogStatement::~DebugLogStatement() {
if (!should_log_)
return;
PopLogEntry(category_, origin_, stream_.str(), start_time_, SecondsSinceStart());
}
} // namespace debug