| // |
| // 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 <string.h> |
| |
| #ifdef _MSC_VER |
| #include <winsock2.h> // For timeval |
| #else |
| #include <sys/time.h> |
| #endif |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <limits> |
| #include <string> |
| #include <type_traits> |
| |
| #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 <typename T> |
| inline std::enable_if_t<!std::is_signed<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 <typename T> |
| inline std::enable_if_t<std::is_signed<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<char>& buf) { |
| constexpr size_t kBoundedFieldsMaxLen = |
| sizeof("SMMDD HH:MM:SS.NNNNNN ") + |
| (1 + std::numeric_limits<log_internal::Tid>::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<int>(tv.tv_sec), |
| static_cast<int>(tv.tv_usec), static_cast<int>(tid)); |
| if (snprintf_result >= 0) { |
| buf.remove_prefix(static_cast<size_t>(snprintf_result)); |
| return static_cast<size_t>(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<uint32_t>(ci.cs.month()), p); |
| p += 2; |
| absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.day()), p); |
| p += 2; |
| *p++ = ' '; |
| absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.hour()), p); |
| p += 2; |
| *p++ = ':'; |
| absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.minute()), |
| p); |
| p += 2; |
| *p++ = ':'; |
| absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.second()), |
| p); |
| p += 2; |
| *p++ = '.'; |
| const int64_t usecs = absl::ToInt64Microseconds(ci.subsecond); |
| absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(usecs / 10000), p); |
| p += 2; |
| absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(usecs / 100 % 100), |
| p); |
| p += 2; |
| absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(usecs % 100), p); |
| p += 2; |
| *p++ = ' '; |
| PutLeadingWhitespace(tid, p); |
| p = absl::numbers_internal::FastIntToBuffer(tid, p); |
| *p++ = ' '; |
| const size_t bytes_formatted = static_cast<size_t>(p - buf.data()); |
| buf.remove_prefix(bytes_formatted); |
| return bytes_formatted; |
| } |
| |
| size_t FormatLineNumber(int line, absl::Span<char>& buf) { |
| constexpr size_t kLineFieldMaxLen = |
| sizeof(":] ") + (1 + std::numeric_limits<int>::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<size_t>(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<char>& 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 |