// // Copyright 2022 The Abseil Authors. // // 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 // // https://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 "absl/log/internal/log_format.h" #include #ifdef _MSC_VER #include // For timeval #else #include #endif #include #include #include #include #include #include "absl/base/config.h" #include "absl/base/log_severity.h" #include "absl/base/optimization.h" #include "absl/log/internal/append_truncated.h" #include "absl/log/internal/config.h" #include "absl/log/internal/globals.h" #include "absl/strings/numbers.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/time/civil_time.h" #include "absl/time/time.h" #include "absl/types/span.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace log_internal { namespace { // This templated function avoids compiler warnings about tautological // comparisons when log_internal::Tid is unsigned. It can be replaced with a // constexpr if once the minimum C++ version Abseil supports is C++17. template inline std::enable_if_t::value> PutLeadingWhitespace(T tid, char*& p) { if (tid < 10) *p++ = ' '; if (tid < 100) *p++ = ' '; if (tid < 1000) *p++ = ' '; if (tid < 10000) *p++ = ' '; if (tid < 100000) *p++ = ' '; if (tid < 1000000) *p++ = ' '; } template inline std::enable_if_t::value> PutLeadingWhitespace(T tid, char*& p) { if (tid >= 0 && tid < 10) *p++ = ' '; if (tid > -10 && tid < 100) *p++ = ' '; if (tid > -100 && tid < 1000) *p++ = ' '; if (tid > -1000 && tid < 10000) *p++ = ' '; if (tid > -10000 && tid < 100000) *p++ = ' '; if (tid > -100000 && tid < 1000000) *p++ = ' '; } // The fields before the filename are all fixed-width except for the thread ID, // which is of bounded width. size_t FormatBoundedFields(absl::LogSeverity severity, absl::Time timestamp, log_internal::Tid tid, absl::Span& buf) { constexpr size_t kBoundedFieldsMaxLen = sizeof("SMMDD HH:MM:SS.NNNNNN ") + (1 + std::numeric_limits::digits10 + 1) - sizeof(""); if (ABSL_PREDICT_FALSE(buf.size() < kBoundedFieldsMaxLen)) { // We don't bother trying to truncate these fields if the buffer is too // short (or almost too short) because it would require doing a lot more // length checking (slow) and it should never happen. A 15kB buffer should // be enough for anyone. Instead we mark `buf` full without writing // anything. buf.remove_suffix(buf.size()); return 0; } // We can't call absl::LocalTime(), localtime_r(), or anything else here that // isn't async-signal-safe. We can only use the time zone if it has already // been loaded. const absl::TimeZone* tz = absl::log_internal::TimeZone(); if (ABSL_PREDICT_FALSE(tz == nullptr)) { // If a time zone hasn't been set yet because we are logging before the // logging library has been initialized, we fallback to a simpler, slower // method. Just report the raw Unix time in seconds. We cram this into the // normal time format for the benefit of parsers. auto tv = absl::ToTimeval(timestamp); int snprintf_result = absl::SNPrintF( buf.data(), buf.size(), "%c0000 00:00:%02d.%06d %7d ", absl::LogSeverityName(severity)[0], static_cast(tv.tv_sec), static_cast(tv.tv_usec), static_cast(tid)); if (snprintf_result >= 0) { buf.remove_prefix(static_cast(snprintf_result)); return static_cast(snprintf_result); } return 0; } char* p = buf.data(); *p++ = absl::LogSeverityName(severity)[0]; const absl::TimeZone::CivilInfo ci = tz->At(timestamp); absl::numbers_internal::PutTwoDigits(static_cast(ci.cs.month()), p); p += 2; absl::numbers_internal::PutTwoDigits(static_cast(ci.cs.day()), p); p += 2; *p++ = ' '; absl::numbers_internal::PutTwoDigits(static_cast(ci.cs.hour()), p); p += 2; *p++ = ':'; absl::numbers_internal::PutTwoDigits(static_cast(ci.cs.minute()), p); p += 2; *p++ = ':'; absl::numbers_internal::PutTwoDigits(static_cast(ci.cs.second()), p); p += 2; *p++ = '.'; const int64_t usecs = absl::ToInt64Microseconds(ci.subsecond); absl::numbers_internal::PutTwoDigits(static_cast(usecs / 10000), p); p += 2; absl::numbers_internal::PutTwoDigits(static_cast(usecs / 100 % 100), p); p += 2; absl::numbers_internal::PutTwoDigits(static_cast(usecs % 100), p); p += 2; *p++ = ' '; PutLeadingWhitespace(tid, p); p = absl::numbers_internal::FastIntToBuffer(tid, p); *p++ = ' '; const size_t bytes_formatted = static_cast(p - buf.data()); buf.remove_prefix(bytes_formatted); return bytes_formatted; } size_t FormatLineNumber(int line, absl::Span& buf) { constexpr size_t kLineFieldMaxLen = sizeof(":] ") + (1 + std::numeric_limits::digits10 + 1) - sizeof(""); if (ABSL_PREDICT_FALSE(buf.size() < kLineFieldMaxLen)) { // As above, we don't bother trying to truncate this if the buffer is too // short and it should never happen. buf.remove_suffix(buf.size()); return 0; } char* p = buf.data(); *p++ = ':'; p = absl::numbers_internal::FastIntToBuffer(line, p); *p++ = ']'; *p++ = ' '; const size_t bytes_formatted = static_cast(p - buf.data()); buf.remove_prefix(bytes_formatted); return bytes_formatted; } } // namespace std::string FormatLogMessage(absl::LogSeverity severity, absl::CivilSecond civil_second, absl::Duration subsecond, log_internal::Tid tid, absl::string_view basename, int line, PrefixFormat format, absl::string_view message) { return absl::StrFormat( "%c%02d%02d %02d:%02d:%02d.%06d %7d %s:%d] %s%s", absl::LogSeverityName(severity)[0], civil_second.month(), civil_second.day(), civil_second.hour(), civil_second.minute(), civil_second.second(), absl::ToInt64Microseconds(subsecond), tid, basename, line, format == PrefixFormat::kRaw ? "RAW: " : "", message); } // This method is fairly hot, and the library always passes a huge `buf`, so we // save some bounds-checking cycles by not trying to do precise truncation. // Truncating at a field boundary is probably a better UX anyway. // // The prefix is written in three parts, each of which does a single // bounds-check and truncation: // 1. severity, timestamp, and thread ID // 2. filename // 3. line number and bracket size_t FormatLogPrefix(absl::LogSeverity severity, absl::Time timestamp, log_internal::Tid tid, absl::string_view basename, int line, PrefixFormat format, absl::Span& buf) { auto prefix_size = FormatBoundedFields(severity, timestamp, tid, buf); prefix_size += log_internal::AppendTruncated(basename, buf); prefix_size += FormatLineNumber(line, buf); if (format == PrefixFormat::kRaw) prefix_size += log_internal::AppendTruncated("RAW: ", buf); return prefix_size; } } // namespace log_internal ABSL_NAMESPACE_END } // namespace absl