| // 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 |