| // |
| // 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 <stdlib.h> |
| #include <inttypes.h> |
| |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| #include <android-base/threads.h> |
| |
| #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<SeverityTarget>& destinations) |
| : destinations_(destinations) { |
| } |
| |
| // Copied from system/libbase/logging_splitters.h |
| static std::pair<int, int> 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 <typename F, typename... Args> |
| 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<SeverityTarget> SeverityTargetsForFiles( |
| const std::vector<std::string>& files) { |
| std::vector<SeverityTarget> 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<std::string>& files) { |
| return TeeLogger(SeverityTargetsForFiles(files)); |
| } |
| |
| TeeLogger LogToStderrAndFiles(const std::vector<std::string>& files) { |
| std::vector<SeverityTarget> log_severities = SeverityTargetsForFiles(files); |
| log_severities.push_back(SeverityTarget{ConsoleSeverity(), |
| SharedFD::Dup(/* stderr */ 2), |
| MetadataLevel::ONLY_MESSAGE}); |
| return TeeLogger(log_severities); |
| } |
| |
| } // namespace cuttlefish |