// // Copyright (C) 2020 The Android Open Source Project // // 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 "tee_logging.h" #include #include #include #include #include #include "common/libs/fs/shared_buf.h" #include "common/libs/utils/environment.h" using android::base::GetThreadId; using android::base::FATAL; using android::base::LogSeverity; using android::base::StringPrintf; namespace cuttlefish { static LogSeverity GuessSeverity( const std::string& env_var, LogSeverity default_value) { using android::base::VERBOSE; using android::base::DEBUG; using android::base::INFO; using android::base::WARNING; using android::base::ERROR; using android::base::FATAL_WITHOUT_ABORT; using android::base::FATAL; std::string env_value = StringFromEnv(env_var, ""); using android::base::EqualsIgnoreCase; if (EqualsIgnoreCase(env_value, "VERBOSE") || env_value == std::to_string((int) VERBOSE)) { return VERBOSE; } else if (EqualsIgnoreCase(env_value, "DEBUG") || env_value == std::to_string((int) DEBUG)) { return DEBUG; } else if (EqualsIgnoreCase(env_value, "INFO") || env_value == std::to_string((int) INFO)) { return INFO; } else if (EqualsIgnoreCase(env_value, "WARNING") || env_value == std::to_string((int) WARNING)) { return WARNING; } else if (EqualsIgnoreCase(env_value, "ERROR") || env_value == std::to_string((int) ERROR)) { return ERROR; } else if (EqualsIgnoreCase(env_value, "FATAL_WITHOUT_ABORT") || env_value == std::to_string((int) FATAL_WITHOUT_ABORT)) { return FATAL_WITHOUT_ABORT; } else if (EqualsIgnoreCase(env_value, "FATAL") || env_value == std::to_string((int) FATAL)) { return FATAL; } else { return default_value; } } LogSeverity ConsoleSeverity() { return GuessSeverity("CF_CONSOLE_SEVERITY", android::base::INFO); } LogSeverity LogFileSeverity() { return GuessSeverity("CF_FILE_SEVERITY", android::base::DEBUG); } TeeLogger::TeeLogger(const std::vector& destinations) : destinations_(destinations) { } // Copied from system/libbase/logging_splitters.h static std::pair CountSizeAndNewLines(const char* message) { int size = 0; int new_lines = 0; while (*message != '\0') { size++; if (*message == '\n') { ++new_lines; } ++message; } return {size, new_lines}; } // Copied from system/libbase/logging_splitters.h // This splits the message up line by line, by calling log_function with a pointer to the start of // each line and the size up to the newline character. It sends size = -1 for the final line. template static void SplitByLines(const char* msg, const F& log_function, Args&&... args) { const char* newline = strchr(msg, '\n'); while (newline != nullptr) { log_function(msg, newline - msg, args...); msg = newline + 1; newline = strchr(msg, '\n'); } log_function(msg, -1, args...); } // Copied from system/libbase/logging_splitters.h // This adds the log header to each line of message and returns it as a string intended to be // written to stderr. static std::string StderrOutputGenerator(const struct tm& now, int pid, uint64_t tid, LogSeverity severity, const char* tag, const char* file, unsigned int line, const char* message) { char timestamp[32]; strftime(timestamp, sizeof(timestamp), "%m-%d %H:%M:%S", &now); static const char log_characters[] = "VDIWEFF"; static_assert(arraysize(log_characters) - 1 == FATAL + 1, "Mismatch in size of log_characters and values in LogSeverity"); char severity_char = log_characters[severity]; std::string line_prefix; if (file != nullptr) { line_prefix = StringPrintf("%s %c %s %5d %5" PRIu64 " %s:%u] ", tag ? tag : "nullptr", severity_char, timestamp, pid, tid, file, line); } else { line_prefix = StringPrintf("%s %c %s %5d %5" PRIu64 " ", tag ? tag : "nullptr", severity_char, timestamp, pid, tid); } auto [size, new_lines] = CountSizeAndNewLines(message); std::string output_string; output_string.reserve(size + new_lines * line_prefix.size() + 1); auto concat_lines = [&](const char* message, int size) { output_string.append(line_prefix); if (size == -1) { output_string.append(message); } else { output_string.append(message, size); } output_string.append("\n"); }; SplitByLines(message, concat_lines); return output_string; } // TODO(schuffelen): Do something less primitive. static std::string StripColorCodes(const std::string& str) { std::stringstream sstream; bool in_color_code = false; for (char c : str) { if (c == '\033') { in_color_code = true; } if (!in_color_code) { sstream << c; } if (c == 'm') { in_color_code = false; } } return sstream.str(); } void TeeLogger::operator()( android::base::LogId, android::base::LogSeverity severity, const char* tag, const char* file, unsigned int line, const char* message) { for (const auto& destination : destinations_) { std::string output_string; if (destination.metadata_level == MetadataLevel::ONLY_MESSAGE) { output_string = message + std::string("\n"); } else { struct tm now; time_t t = time(nullptr); localtime_r(&t, &now); output_string = StderrOutputGenerator(now, getpid(), GetThreadId(), severity, tag, file, line, message); } if (severity >= destination.severity) { if (destination.target->IsATTY()) { WriteAll(destination.target, output_string); } else { WriteAll(destination.target, StripColorCodes(output_string)); } } } } static std::vector SeverityTargetsForFiles( const std::vector& files) { std::vector log_severities; for (const auto& file : files) { auto log_file_fd = SharedFD::Open( file, O_CREAT | O_WRONLY | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); if (!log_file_fd->IsOpen()) { LOG(FATAL) << "Failed to create log file: " << log_file_fd->StrError(); } log_severities.push_back( SeverityTarget{LogFileSeverity(), log_file_fd, MetadataLevel::FULL}); } return log_severities; } TeeLogger LogToFiles(const std::vector& files) { return TeeLogger(SeverityTargetsForFiles(files)); } TeeLogger LogToStderrAndFiles(const std::vector& files) { std::vector log_severities = SeverityTargetsForFiles(files); log_severities.push_back(SeverityTarget{ConsoleSeverity(), SharedFD::Dup(/* stderr */ 2), MetadataLevel::ONLY_MESSAGE}); return TeeLogger(log_severities); } } // namespace cuttlefish