1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "google/cloud/log.h"
16 #include "google/cloud/internal/getenv.h"
17 #include "absl/time/time.h"
18 #include <array>
19
20 namespace google {
21 namespace cloud {
22 inline namespace GOOGLE_CLOUD_CPP_NS {
23
24 static_assert(sizeof(Severity) <= sizeof(int),
25 "Expected Severity to fit in an integer");
26
27 static_assert(static_cast<int>(Severity::GCP_LS_LOWEST) <
28 static_cast<int>(Severity::GCP_LS_HIGHEST),
29 "Expect LOWEST severity to be smaller than HIGHEST severity");
30
31 namespace {
32 struct Timestamp {
Timestampgoogle::cloud::GOOGLE_CLOUD_CPP_NS::__anon3b3e4ebc0111::Timestamp33 explicit Timestamp(std::chrono::system_clock::time_point const& tp)
34 : t(absl::FromChrono(tp)) {}
35 absl::Time t;
36 };
37
operator <<(std::ostream & os,Timestamp const & ts)38 std::ostream& operator<<(std::ostream& os, Timestamp const& ts) {
39 auto constexpr kFormat = "%E4Y-%m-%dT%H:%M:%E9SZ";
40 return os << absl::FormatTime(kFormat, ts.t, absl::UTCTimeZone());
41 }
42
43 auto constexpr kSeverityCount = static_cast<int>(Severity::GCP_LS_HIGHEST) + 1;
44
45 // Double braces needed to workaround a clang-3.8 bug.
46 std::array<char const*, kSeverityCount> constexpr kSeverityNames{{
47 "TRACE",
48 "DEBUG",
49 "INFO",
50 "NOTICE",
51 "WARNING",
52 "ERROR",
53 "CRITICAL",
54 "ALERT",
55 "FATAL",
56 }};
57 } // namespace
58
operator <<(std::ostream & os,Severity x)59 std::ostream& operator<<(std::ostream& os, Severity x) {
60 auto index = static_cast<int>(x);
61 return os << kSeverityNames[index];
62 }
63
operator <<(std::ostream & os,LogRecord const & rhs)64 std::ostream& operator<<(std::ostream& os, LogRecord const& rhs) {
65 return os << Timestamp{rhs.timestamp} << " [" << rhs.severity << "] "
66 << rhs.message << " (" << rhs.filename << ':' << rhs.lineno << ')';
67 }
68
LogSink()69 LogSink::LogSink()
70 : empty_(true),
71 minimum_severity_(static_cast<int>(Severity::GCP_LS_LOWEST_ENABLED)),
72 next_id_(0),
73 clog_backend_id_(0) {}
74
Instance()75 LogSink& LogSink::Instance() {
76 static auto* const kInstance = [] {
77 auto* p = new LogSink;
78 if (internal::GetEnv("GOOGLE_CLOUD_CPP_ENABLE_CLOG").has_value()) {
79 p->EnableStdClogImpl();
80 }
81 return p;
82 }();
83 return *kInstance;
84 }
85
86 // NOLINTNEXTLINE(google-runtime-int)
AddBackend(std::shared_ptr<LogBackend> backend)87 long LogSink::AddBackend(std::shared_ptr<LogBackend> backend) {
88 std::unique_lock<std::mutex> lk(mu_);
89 return AddBackendImpl(std::move(backend));
90 }
91
92 // NOLINTNEXTLINE(google-runtime-int)
RemoveBackend(long id)93 void LogSink::RemoveBackend(long id) {
94 std::unique_lock<std::mutex> lk(mu_);
95 RemoveBackendImpl(id);
96 }
97
ClearBackends()98 void LogSink::ClearBackends() {
99 std::unique_lock<std::mutex> lk(mu_);
100 backends_.clear();
101 clog_backend_id_ = 0;
102 empty_.store(backends_.empty());
103 }
104
BackendCount() const105 std::size_t LogSink::BackendCount() const {
106 std::unique_lock<std::mutex> lk(mu_);
107 return backends_.size();
108 }
109
Log(LogRecord log_record)110 void LogSink::Log(LogRecord log_record) {
111 // Make a copy of the backends because calling user-defined functions while
112 // holding a lock is a bad idea: the application may change the backends while
113 // we are holding this lock, and soon deadlock occurs.
114 auto copy = [this]() {
115 std::unique_lock<std::mutex> lk(mu_);
116 return backends_;
117 }();
118 if (copy.empty()) {
119 return;
120 }
121 // In general, we just give each backend a const-reference and the backends
122 // must make a copy if needed. But if there is only one backend we can give
123 // the backend an opportunity to optimize things by transferring ownership of
124 // the LogRecord to it.
125 if (copy.size() == 1) {
126 copy.begin()->second->ProcessWithOwnership(std::move(log_record));
127 return;
128 }
129 for (auto& kv : copy) {
130 kv.second->Process(log_record);
131 }
132 }
133
134 namespace {
135 class StdClogBackend : public LogBackend {
136 public:
137 StdClogBackend() = default;
138
Process(LogRecord const & lr)139 void Process(LogRecord const& lr) override {
140 std::clog << lr << "\n";
141 if (lr.severity >= Severity::GCP_LS_WARNING) {
142 std::clog << std::flush;
143 }
144 }
ProcessWithOwnership(LogRecord lr)145 void ProcessWithOwnership(LogRecord lr) override { Process(lr); }
146 };
147 } // namespace
148
EnableStdClogImpl()149 void LogSink::EnableStdClogImpl() {
150 std::unique_lock<std::mutex> lk(mu_);
151 if (clog_backend_id_ != 0) {
152 return;
153 }
154 clog_backend_id_ = AddBackendImpl(std::make_shared<StdClogBackend>());
155 }
156
DisableStdClogImpl()157 void LogSink::DisableStdClogImpl() {
158 std::unique_lock<std::mutex> lk(mu_);
159 if (clog_backend_id_ == 0) {
160 return;
161 }
162 RemoveBackendImpl(clog_backend_id_);
163 clog_backend_id_ = 0;
164 }
165
166 // NOLINTNEXTLINE(google-runtime-int)
AddBackendImpl(std::shared_ptr<LogBackend> backend)167 long LogSink::AddBackendImpl(std::shared_ptr<LogBackend> backend) {
168 auto const id = ++next_id_;
169 backends_.emplace(id, std::move(backend));
170 empty_.store(backends_.empty());
171 return id;
172 }
173
174 // NOLINTNEXTLINE(google-runtime-int)
RemoveBackendImpl(long id)175 void LogSink::RemoveBackendImpl(long id) {
176 auto it = backends_.find(id);
177 if (backends_.end() == it) {
178 return;
179 }
180 backends_.erase(it);
181 empty_.store(backends_.empty());
182 }
183
184 } // namespace GOOGLE_CLOUD_CPP_NS
185 } // namespace cloud
186 } // namespace google
187