// // 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_sink_set.h" #ifndef ABSL_HAVE_THREAD_LOCAL #include #endif #ifdef __ANDROID__ #include #endif #ifdef _WIN32 #include #endif #include #include #include "absl/base/attributes.h" #include "absl/base/call_once.h" #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/log_severity.h" #include "absl/base/thread_annotations.h" #include "absl/cleanup/cleanup.h" #include "absl/log/globals.h" #include "absl/log/internal/config.h" #include "absl/log/internal/globals.h" #include "absl/log/log_entry.h" #include "absl/log/log_sink.h" #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" #include "absl/types/span.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace log_internal { namespace { // Returns a mutable reference to a thread-local variable that should be true if // a globally-registered `LogSink`'s `Send()` is currently being invoked on this // thread. bool& ThreadIsLoggingStatus() { #ifdef ABSL_HAVE_THREAD_LOCAL ABSL_CONST_INIT thread_local bool thread_is_logging = false; return thread_is_logging; #else ABSL_CONST_INIT static pthread_key_t thread_is_logging_key; static const bool unused = [] { if (pthread_key_create(&thread_is_logging_key, [](void* data) { delete reinterpret_cast(data); })) { perror("pthread_key_create failed!"); abort(); } return true; }(); (void)unused; // Fixes -wunused-variable warning bool* thread_is_logging_ptr = reinterpret_cast(pthread_getspecific(thread_is_logging_key)); if (ABSL_PREDICT_FALSE(!thread_is_logging_ptr)) { thread_is_logging_ptr = new bool{false}; if (pthread_setspecific(thread_is_logging_key, thread_is_logging_ptr)) { perror("pthread_setspecific failed"); abort(); } } return *thread_is_logging_ptr; #endif } class StderrLogSink final : public LogSink { public: ~StderrLogSink() override = default; void Send(const absl::LogEntry& entry) override { if (entry.log_severity() < absl::StderrThreshold() && absl::log_internal::IsInitialized()) { return; } ABSL_CONST_INIT static absl::once_flag warn_if_not_initialized; absl::call_once(warn_if_not_initialized, []() { if (absl::log_internal::IsInitialized()) return; const char w[] = "WARNING: All log messages before absl::InitializeLog() is called" " are written to STDERR\n"; absl::log_internal::WriteToStderr(w, absl::LogSeverity::kWarning); }); if (!entry.stacktrace().empty()) { absl::log_internal::WriteToStderr(entry.stacktrace(), entry.log_severity()); } else { // TODO(b/226937039): do this outside else condition once we avoid // ReprintFatalMessage absl::log_internal::WriteToStderr( entry.text_message_with_prefix_and_newline(), entry.log_severity()); } } }; #if defined(__ANDROID__) class AndroidLogSink final : public LogSink { public: ~AndroidLogSink() override = default; void Send(const absl::LogEntry& entry) override { const int level = AndroidLogLevel(entry); // TODO(b/37587197): make the tag ("native") configurable. __android_log_write(level, "native", entry.text_message_with_prefix_and_newline_c_str()); if (entry.log_severity() == absl::LogSeverity::kFatal) __android_log_write(ANDROID_LOG_FATAL, "native", "terminating.\n"); } private: static int AndroidLogLevel(const absl::LogEntry& entry) { switch (entry.log_severity()) { case absl::LogSeverity::kFatal: return ANDROID_LOG_FATAL; case absl::LogSeverity::kError: return ANDROID_LOG_ERROR; case absl::LogSeverity::kWarning: return ANDROID_LOG_WARN; default: if (entry.verbosity() >= 2) return ANDROID_LOG_VERBOSE; if (entry.verbosity() == 1) return ANDROID_LOG_DEBUG; return ANDROID_LOG_INFO; } } }; #endif // !defined(__ANDROID__) #if defined(_WIN32) class WindowsDebuggerLogSink final : public LogSink { public: ~WindowsDebuggerLogSink() override = default; void Send(const absl::LogEntry& entry) override { if (entry.log_severity() < absl::StderrThreshold() && absl::log_internal::IsInitialized()) { return; } ::OutputDebugStringA(entry.text_message_with_prefix_and_newline_c_str()); } }; #endif // !defined(_WIN32) class GlobalLogSinkSet final { public: GlobalLogSinkSet() { #if defined(__myriad2__) || defined(__Fuchsia__) // myriad2 and Fuchsia do not log to stderr by default. #else static StderrLogSink* stderr_log_sink = new StderrLogSink; AddLogSink(stderr_log_sink); #endif #ifdef __ANDROID__ static AndroidLogSink* android_log_sink = new AndroidLogSink; AddLogSink(android_log_sink); #endif #if defined(_WIN32) static WindowsDebuggerLogSink* debugger_log_sink = new WindowsDebuggerLogSink; AddLogSink(debugger_log_sink); #endif // !defined(_WIN32) } void LogToSinks(const absl::LogEntry& entry, absl::Span extra_sinks, bool extra_sinks_only) ABSL_LOCKS_EXCLUDED(guard_) { SendToSinks(entry, extra_sinks); if (!extra_sinks_only) { if (ThreadIsLoggingToLogSink()) { absl::log_internal::WriteToStderr( entry.text_message_with_prefix_and_newline(), entry.log_severity()); } else { absl::ReaderMutexLock global_sinks_lock(&guard_); ThreadIsLoggingStatus() = true; // Ensure the "thread is logging" status is reverted upon leaving the // scope even in case of exceptions. auto status_cleanup = absl::MakeCleanup([] { ThreadIsLoggingStatus() = false; }); SendToSinks(entry, absl::MakeSpan(sinks_)); } } } void AddLogSink(absl::LogSink* sink) ABSL_LOCKS_EXCLUDED(guard_) { { absl::WriterMutexLock global_sinks_lock(&guard_); auto pos = std::find(sinks_.begin(), sinks_.end(), sink); if (pos == sinks_.end()) { sinks_.push_back(sink); return; } } ABSL_INTERNAL_LOG(FATAL, "Duplicate log sinks are not supported"); } void RemoveLogSink(absl::LogSink* sink) ABSL_LOCKS_EXCLUDED(guard_) { { absl::WriterMutexLock global_sinks_lock(&guard_); auto pos = std::find(sinks_.begin(), sinks_.end(), sink); if (pos != sinks_.end()) { sinks_.erase(pos); return; } } ABSL_INTERNAL_LOG(FATAL, "Mismatched log sink being removed"); } void FlushLogSinks() ABSL_LOCKS_EXCLUDED(guard_) { if (ThreadIsLoggingToLogSink()) { // The thread_local condition demonstrates that we're already holding the // lock in order to iterate over `sinks_` for dispatch. The thread-safety // annotations don't know this, so we use `ABSL_NO_THREAD_SAFETY_ANALYSIS` guard_.AssertReaderHeld(); FlushLogSinksLocked(); } else { absl::ReaderMutexLock global_sinks_lock(&guard_); // In case if LogSink::Flush overload decides to log ThreadIsLoggingStatus() = true; // Ensure the "thread is logging" status is reverted upon leaving the // scope even in case of exceptions. auto status_cleanup = absl::MakeCleanup([] { ThreadIsLoggingStatus() = false; }); FlushLogSinksLocked(); } } private: void FlushLogSinksLocked() ABSL_SHARED_LOCKS_REQUIRED(guard_) { for (absl::LogSink* sink : sinks_) { sink->Flush(); } } // Helper routine for LogToSinks. static void SendToSinks(const absl::LogEntry& entry, absl::Span sinks) { for (absl::LogSink* sink : sinks) { sink->Send(entry); } } using LogSinksSet = std::vector; absl::Mutex guard_; LogSinksSet sinks_ ABSL_GUARDED_BY(guard_); }; // Returns reference to the global LogSinks set. GlobalLogSinkSet& GlobalSinks() { static GlobalLogSinkSet* global_sinks = new GlobalLogSinkSet; return *global_sinks; } } // namespace bool ThreadIsLoggingToLogSink() { return ThreadIsLoggingStatus(); } void LogToSinks(const absl::LogEntry& entry, absl::Span extra_sinks, bool extra_sinks_only) { log_internal::GlobalSinks().LogToSinks(entry, extra_sinks, extra_sinks_only); } void AddLogSink(absl::LogSink* sink) { log_internal::GlobalSinks().AddLogSink(sink); } void RemoveLogSink(absl::LogSink* sink) { log_internal::GlobalSinks().RemoveLogSink(sink); } void FlushLogSinks() { log_internal::GlobalSinks().FlushLogSinks(); } } // namespace log_internal ABSL_NAMESPACE_END } // namespace absl