/** * Copyright (c) 2021-2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "logger.h" #include "os/thread.h" #include "string_helpers.h" #include "generated/base_options.h" #include <cstdarg> #include <cstdlib> #include <cstring> #include <fstream> #include <iostream> #include <string_view> namespace panda { Logger *Logger::logger = nullptr; thread_local int Logger::nesting = 0; #include <logger_impl_gen.inc> void Logger::Initialize(const base_options::Options &options) { panda::Logger::ComponentMask component_mask; auto load_components = [&component_mask](auto components) { for (const auto &s : components) { component_mask |= Logger::ComponentMaskFromString(s); } }; Level level = Level::LAST; if (options.WasSetLogFatal()) { ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options"); load_components(options.GetLogFatal()); level = Level::FATAL; } else if (options.WasSetLogError()) { ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options"); load_components(options.GetLogError()); level = Level::ERROR; } else if (options.WasSetLogWarning()) { ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options"); load_components(options.GetLogWarning()); level = Level::WARNING; } else if (options.WasSetLogInfo()) { ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options"); load_components(options.GetLogInfo()); level = Level::INFO; } else if (options.WasSetLogDebug()) { ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options"); load_components(options.GetLogDebug()); level = Level::DEBUG; } else { ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options"); load_components(options.GetLogComponents()); level = Logger::LevelFromString(options.GetLogLevel()); } #ifdef ENABLE_HILOG Logger::InitializeHiLogging(level, component_mask); return; #endif if (options.GetLogStream() == "std") { Logger::InitializeStdLogging(level, component_mask); } else if (options.GetLogStream() == "file" || options.GetLogStream() == "fast-file") { const std::string &file_name = options.GetLogFile(); Logger::InitializeFileLogging(file_name, level, component_mask, options.GetLogStream() == "fast-file"); } else if (options.GetLogStream() == "dummy") { Logger::InitializeDummyLogging(level, component_mask); } else { UNREACHABLE(); } } #ifndef NDEBUG /** * In debug builds this function allowes or disallowes proceeding with actual logging (i.e. creating Message{}) */ /* static */ bool Logger::IsMessageSuppressed([[maybe_unused]] Level level, [[maybe_unused]] Component component) { // Allowing only to log if it's not a nested log, or it's nested and it's severity is suitable return level >= Logger::logger->nested_allowed_level_ && nesting > 0; } /** * Increases log nesting (i.e. depth, or how many instances of Message{} is active atm) in a given thread */ /* static */ void Logger::LogNestingInc() { nesting++; } /** * Decreases log nesting (i.e. depth, or how many instances of Message{} is active atm) in a given thread */ /* static */ void Logger::LogNestingDec() { nesting--; } #endif // NDEBUG auto Logger::Buffer::printf(const char *format, ...) -> Buffer & { va_list args; va_start(args, format); // NOLINT(cppcoreguidelines-pro-type-vararg) [[maybe_unused]] int put = vsnprintf_s(this->data(), this->size(), this->size() - 1, format, args); ASSERT(put >= 0 && static_cast<size_t>(put) < BUFFER_SIZE); va_end(args); return *this; } os::memory::Mutex Logger::mutex; // NOLINT(fuchsia-statically-constructed-objects) FUNC_MOBILE_LOG_PRINT mlog_buf_print = nullptr; Logger::Message::~Message() { if (print_system_error_) { stream_ << ": " << os::Error(errno).ToString(); } Logger::Log(level_, component_, stream_.str()); #ifndef NDEBUG panda::Logger::LogNestingDec(); #endif if (level_ == Level::FATAL) { std::cerr << "FATAL ERROR" << std::endl; std::cerr << "Backtrace [tid=" << os::thread::GetCurrentThreadId() << "]:\n"; PrintStack(std::cerr); std::abort(); } } /* static */ void Logger::Log(Level level, Component component, const std::string &str) { if (!IsLoggingOn(level, component)) { return; } os::memory::LockHolder<os::memory::Mutex> lock(mutex); if (!IsLoggingOn(level, component)) { return; } size_t nl = str.find('\n'); if (nl == std::string::npos) { logger->LogLineInternal(level, component, str); logger->WriteMobileLog(level, GetComponentTag(component), str.c_str()); } else { size_t i = 0; while (nl != std::string::npos) { std::string line = str.substr(i, nl - i); logger->LogLineInternal(level, component, line); logger->WriteMobileLog(level, GetComponentTag(component), line.c_str()); i = nl + 1; nl = str.find('\n', i); } logger->LogLineInternal(level, component, str.substr(i)); logger->WriteMobileLog(level, GetComponentTag(component), str.substr(i).c_str()); } } /* static */ std::string GetPrefix(Logger::Level level, Logger::Component component) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) return helpers::string::Format("[TID %06x] %s/%s: ", os::thread::GetCurrentThreadId(), GetLevelTag(level), GetComponentTag(component)); } /* static */ void Logger::InitializeFileLogging(const std::string &log_file, Level level, ComponentMask component_mask, bool is_fast_logging) { if (IsInitialized()) { return; } os::memory::LockHolder<os::memory::Mutex> lock(mutex); if (IsInitialized()) { return; } std::ofstream stream(log_file); if (stream) { if (is_fast_logging) { logger = new FastFileLogger(std::move(stream), level, component_mask); } else { logger = new FileLogger(std::move(stream), level, component_mask); } } else { logger = new StderrLogger(level, component_mask); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) std::string msg = helpers::string::Format("Fallback to stderr logging: cannot open log file '%s': %s", log_file.c_str(), os::Error(errno).ToString().c_str()); logger->LogLineInternal(Level::ERROR, Component::COMMON, msg); } } #ifdef ENABLE_HILOG /* static */ void Logger::InitializeHiLogging(Level level, ComponentMask component_mask) { if (IsInitialized()) { return; } { os::memory::LockHolder<os::memory::Mutex> lock(mutex); if (IsInitialized()) { return; } logger = new HiLogger(level, component_mask); } } #endif /* static */ void Logger::InitializeStdLogging(Level level, ComponentMask component_mask) { if (IsInitialized()) { return; } { os::memory::LockHolder<os::memory::Mutex> lock(mutex); if (IsInitialized()) { return; } logger = new StderrLogger(level, component_mask); } } /* static */ void Logger::InitializeDummyLogging(Level level, ComponentMask component_mask) { if (IsInitialized()) { return; } { os::memory::LockHolder<os::memory::Mutex> lock(mutex); if (IsInitialized()) { return; } logger = new DummyLogger(level, component_mask); } } /* static */ void Logger::Destroy() { if (!IsInitialized()) { return; } Logger *l = nullptr; { os::memory::LockHolder<os::memory::Mutex> lock(mutex); if (!IsInitialized()) { return; } l = logger; logger = nullptr; } delete l; } /* static */ void Logger::ProcessLogLevelFromString(std::string_view s) { if (Logger::IsInLevelList(s)) { Logger::SetLevel(Logger::LevelFromString(s)); } else { LOG(ERROR, RUNTIME) << "Unknown level " << s; } } /* static */ void Logger::ProcessLogComponentsFromString(std::string_view s) { Logger::ResetComponentMask(); size_t last_pos = s.find_first_not_of(',', 0); size_t pos = s.find(',', last_pos); while (last_pos != std::string_view::npos) { std::string_view component_str = s.substr(last_pos, pos - last_pos); last_pos = s.find_first_not_of(',', pos); pos = s.find(',', last_pos); if (Logger::IsInComponentList(component_str)) { Logger::EnableComponent(Logger::ComponentMaskFromString(component_str)); } else { LOG(ERROR, RUNTIME) << "Unknown component " << component_str; } } } void FileLogger::LogLineInternal(Level level, Component component, const std::string &str) { std::string prefix = GetPrefix(level, component); stream_ << prefix << str << std::endl << std::flush; } void FastFileLogger::LogLineInternal(Level level, Component component, const std::string &str) { std::string prefix = GetPrefix(level, component); stream_ << prefix << str << '\n'; } #ifdef ENABLE_HILOG void HiLogger::LogLineInternal(Level level, Component component, const std::string &str) { std::string prefix = GetPrefix(level, component); stream_ << prefix << str; switch (level) { case Level::DEBUG: OHOS::HiviewDFX::HiLog::Debug(LABEL, "%{public}s", stream_.str().c_str()); break; case Level::INFO: OHOS::HiviewDFX::HiLog::Info(LABEL, "%{public}s", stream_.str().c_str()); break; case Level::ERROR: OHOS::HiviewDFX::HiLog::Error(LABEL, "%{public}s", stream_.str().c_str()); break; case Level::FATAL: OHOS::HiviewDFX::HiLog::Fatal(LABEL, "%{public}s", stream_.str().c_str()); break; case Level::WARNING: OHOS::HiviewDFX::HiLog::Warn(LABEL, "%{public}s", stream_.str().c_str()); break; default: UNREACHABLE(); } } #endif void StderrLogger::LogLineInternal(Level level, Component component, const std::string &str) { std::string prefix = GetPrefix(level, component); std::cerr << prefix << str << std::endl << std::flush; } void FastFileLogger::SyncOutputResource() { stream_ << std::flush; } } // namespace panda