1 /* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
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
16 #include "tensorflow/core/platform/default/logging.h"
17
18 // TODO(b/142492876): Avoid depending on absl internal.
19 #ifdef TF_ANDROID_ENABLE_LOG_EVERY_N_SECONDS
20 #include "absl/base/internal/cycleclock.h"
21 #endif
22 #if !defined(PLATFORM_POSIX_ANDROID)
23 #include "absl/base/internal/sysinfo.h"
24 #endif
25 #include "tensorflow/core/platform/env_time.h"
26 #include "tensorflow/core/platform/macros.h"
27 #include "tensorflow/core/platform/mutex.h"
28
29 #if defined(PLATFORM_POSIX_ANDROID)
30 #include <android/log.h>
31
32 #include <iostream>
33 #include <sstream>
34 #endif
35
36 #include <stdlib.h>
37 #include <string.h>
38 #include <time.h>
39
40 #include <algorithm>
41 #include <queue>
42 #include <unordered_map>
43
44 #ifdef __ANDROID__
45 #include <unistd.h>
46 #endif
47
48 namespace tensorflow {
49
50 namespace internal {
51 namespace {
52
53 #ifdef TF_ANDROID_ENABLE_LOGSINK
54 // This is an internal singleton class that manages the log sinks. It allows
55 // adding and removing the log sinks, as well as handling sending log messages
56 // to all the registered log sinks.
57 class TFLogSinks {
58 public:
59 // Gets the TFLogSinks instance. This is the entry point for using this class.
60 static TFLogSinks& Instance();
61
62 // Adds a log sink. The sink argument must not be a nullptr. TFLogSinks
63 // takes ownership of the pointer, the user must not free the pointer.
64 // The pointer will remain valid until the application terminates or
65 // until TFLogSinks::Remove is called for the same pointer value.
66 void Add(TFLogSink* sink);
67
68 // Removes a log sink. This will also erase the sink object. The pointer
69 // to the sink becomes invalid after this call.
70 void Remove(TFLogSink* sink);
71
72 // Gets the currently registered log sinks.
73 std::vector<TFLogSink*> GetSinks() const;
74
75 // Sends a log message to all registered log sinks.
76 //
77 // If there are no log sinks are registered:
78 //
79 // NO_DEFAULT_LOGGER is defined:
80 // Up to 128 messages will be queued until a log sink is added.
81 // The queue will then be logged to the first added log sink.
82 //
83 // NO_DEFAULT_LOGGER is not defined:
84 // The messages will be logged using the default logger. The default logger
85 // will log to stdout on all platforms except for Android. On Androit the
86 // default Android logger will be used.
87 void Send(const TFLogEntry& entry);
88
89 private:
90 TFLogSinks();
91 void SendToSink(TFLogSink& sink, const TFLogEntry& entry);
92
93 std::queue<TFLogEntry> log_entry_queue_;
94 static const size_t kMaxLogEntryQueueSize = 128;
95
96 mutable tensorflow::mutex mutex_;
97 std::vector<TFLogSink*> sinks_;
98 };
99
TFLogSinks()100 TFLogSinks::TFLogSinks() {
101 #ifndef NO_DEFAULT_LOGGER
102 static TFDefaultLogSink* default_sink = new TFDefaultLogSink();
103 sinks_.emplace_back(default_sink);
104 #endif
105 }
106
Instance()107 TFLogSinks& TFLogSinks::Instance() {
108 static TFLogSinks* instance = new TFLogSinks();
109 return *instance;
110 }
111
Add(TFLogSink * sink)112 void TFLogSinks::Add(TFLogSink* sink) {
113 assert(sink != nullptr && "The sink must not be a nullptr");
114
115 tensorflow::mutex_lock lock(mutex_);
116 sinks_.emplace_back(sink);
117
118 // If this is the only sink log all the queued up messages to this sink
119 if (sinks_.size() == 1) {
120 while (!log_entry_queue_.empty()) {
121 for (const auto& sink : sinks_) {
122 SendToSink(*sink, log_entry_queue_.front());
123 }
124 log_entry_queue_.pop();
125 }
126 }
127 }
128
Remove(TFLogSink * sink)129 void TFLogSinks::Remove(TFLogSink* sink) {
130 assert(sink != nullptr && "The sink must not be a nullptr");
131
132 tensorflow::mutex_lock lock(mutex_);
133 auto it = std::find(sinks_.begin(), sinks_.end(), sink);
134 if (it != sinks_.end()) sinks_.erase(it);
135 }
136
GetSinks() const137 std::vector<TFLogSink*> TFLogSinks::GetSinks() const {
138 tensorflow::mutex_lock lock(mutex_);
139 return sinks_;
140 }
141
Send(const TFLogEntry & entry)142 void TFLogSinks::Send(const TFLogEntry& entry) {
143 tensorflow::mutex_lock lock(mutex_);
144
145 // If we don't have any sinks registered, queue them up
146 if (sinks_.empty()) {
147 // If we've exceeded the maximum queue size, drop the oldest entries
148 while (log_entry_queue_.size() >= kMaxLogEntryQueueSize) {
149 log_entry_queue_.pop();
150 }
151 log_entry_queue_.push(entry);
152 return;
153 }
154
155 // If we have items in the queue, push them out first
156 while (!log_entry_queue_.empty()) {
157 for (const auto& sink : sinks_) {
158 SendToSink(*sink, log_entry_queue_.front());
159 }
160 log_entry_queue_.pop();
161 }
162
163 // ... and now we can log the current log entry
164 for (const auto& sink : sinks_) {
165 SendToSink(*sink, entry);
166 }
167 }
168
SendToSink(TFLogSink & sink,const TFLogEntry & entry)169 void TFLogSinks::SendToSink(TFLogSink& sink, const TFLogEntry& entry) {
170 sink.Send(entry);
171 sink.WaitTillSent();
172 }
173 #endif // TF_ANDROID_ENABLE_LOGSINK
174
ParseInteger(const char * str,size_t size)175 int ParseInteger(const char* str, size_t size) {
176 // Ideally we would use env_var / safe_strto64, but it is
177 // hard to use here without pulling in a lot of dependencies,
178 // so we use std:istringstream instead
179 string integer_str(str, size);
180 std::istringstream ss(integer_str);
181 int level = 0;
182 ss >> level;
183 return level;
184 }
185
186 // Parse log level (int64) from environment variable (char*)
LogLevelStrToInt(const char * tf_env_var_val)187 int64 LogLevelStrToInt(const char* tf_env_var_val) {
188 if (tf_env_var_val == nullptr) {
189 return 0;
190 }
191 return ParseInteger(tf_env_var_val, strlen(tf_env_var_val));
192 }
193
194 // Using StringPiece breaks Windows build.
195 struct StringData {
196 struct Hasher {
operator ()tensorflow::internal::__anon2b7d55170111::StringData::Hasher197 size_t operator()(const StringData& sdata) const {
198 // For dependency reasons, we cannot use hash.h here. Use DBJHash instead.
199 size_t hash = 5381;
200 const char* data = sdata.data;
201 for (const char* top = data + sdata.size; data < top; ++data) {
202 hash = ((hash << 5) + hash) + (*data);
203 }
204 return hash;
205 }
206 };
207
208 StringData() = default;
StringDatatensorflow::internal::__anon2b7d55170111::StringData209 StringData(const char* data, size_t size) : data(data), size(size) {}
210
operator ==tensorflow::internal::__anon2b7d55170111::StringData211 bool operator==(const StringData& rhs) const {
212 return size == rhs.size && memcmp(data, rhs.data, size) == 0;
213 }
214
215 const char* data = nullptr;
216 size_t size = 0;
217 };
218
219 using VmoduleMap = std::unordered_map<StringData, int, StringData::Hasher>;
220
221 // Returns a mapping from module name to VLOG level, derived from the
222 // TF_CPP_VMODULE environment variable; ownership is transferred to the caller.
VmodulesMapFromEnv()223 VmoduleMap* VmodulesMapFromEnv() {
224 // The value of the env var is supposed to be of the form:
225 // "foo=1,bar=2,baz=3"
226 const char* env = getenv("TF_CPP_VMODULE");
227 if (env == nullptr) {
228 // If there is no TF_CPP_VMODULE configuration (most common case), return
229 // nullptr so that the ShouldVlogModule() API can fast bail out of it.
230 return nullptr;
231 }
232 // The memory returned by getenv() can be invalidated by following getenv() or
233 // setenv() calls. And since we keep references to it in the VmoduleMap in
234 // form of StringData objects, make a copy of it.
235 const char* env_data = strdup(env);
236 VmoduleMap* result = new VmoduleMap();
237 while (true) {
238 const char* eq = strchr(env_data, '=');
239 if (eq == nullptr) {
240 break;
241 }
242 const char* after_eq = eq + 1;
243
244 // Comma either points at the next comma delimiter, or at a null terminator.
245 // We check that the integer we parse ends at this delimiter.
246 const char* comma = strchr(after_eq, ',');
247 const char* new_env_data;
248 if (comma == nullptr) {
249 comma = strchr(after_eq, '\0');
250 new_env_data = comma;
251 } else {
252 new_env_data = comma + 1;
253 }
254 (*result)[StringData(env_data, eq - env_data)] =
255 ParseInteger(after_eq, comma - after_eq);
256 env_data = new_env_data;
257 }
258 return result;
259 }
260
EmitThreadIdFromEnv()261 bool EmitThreadIdFromEnv() {
262 const char* tf_env_var_val = getenv("TF_CPP_LOG_THREAD_ID");
263 return tf_env_var_val == nullptr
264 ? false
265 : ParseInteger(tf_env_var_val, strlen(tf_env_var_val)) != 0;
266 }
267
268 } // namespace
269
MinLogLevelFromEnv()270 int64 MinLogLevelFromEnv() {
271 // We don't want to print logs during fuzzing as that would slow fuzzing down
272 // by almost 2x. So, if we are in fuzzing mode (not just running a test), we
273 // return a value so that nothing is actually printed. Since LOG uses >=
274 // (see ~LogMessage in this file) to see if log messages need to be printed,
275 // the value we're interested on to disable printing is the maximum severity.
276 // See also http://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode
277 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
278 return tensorflow::NUM_SEVERITIES;
279 #else
280 const char* tf_env_var_val = getenv("TF_CPP_MIN_LOG_LEVEL");
281 return LogLevelStrToInt(tf_env_var_val);
282 #endif
283 }
284
MaxVLogLevelFromEnv()285 int64 MaxVLogLevelFromEnv() {
286 // We don't want to print logs during fuzzing as that would slow fuzzing down
287 // by almost 2x. So, if we are in fuzzing mode (not just running a test), we
288 // return a value so that nothing is actually printed. Since VLOG uses <=
289 // (see VLOG_IS_ON in logging.h) to see if log messages need to be printed,
290 // the value we're interested on to disable printing is 0.
291 // See also http://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode
292 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
293 return 0;
294 #else
295 const char* tf_env_var_val = getenv("TF_CPP_MAX_VLOG_LEVEL");
296 return LogLevelStrToInt(tf_env_var_val);
297 #endif
298 }
299
LogMessage(const char * fname,int line,int severity)300 LogMessage::LogMessage(const char* fname, int line, int severity)
301 : fname_(fname), line_(line), severity_(severity) {}
302
AtLocation(const char * fname,int line)303 LogMessage& LogMessage::AtLocation(const char* fname, int line) {
304 fname_ = fname;
305 line_ = line;
306 return *this;
307 }
308
~LogMessage()309 LogMessage::~LogMessage() {
310 // Read the min log level once during the first call to logging.
311 static int64 min_log_level = MinLogLevelFromEnv();
312 if (severity_ >= min_log_level) {
313 GenerateLogMessage();
314 }
315 }
316
GenerateLogMessage()317 void LogMessage::GenerateLogMessage() {
318 #ifdef TF_ANDROID_ENABLE_LOGSINK
319 TFLogSinks::Instance().Send(TFLogEntry(severity_, fname_, line_, str()));
320 #else
321 static bool log_thread_id = EmitThreadIdFromEnv();
322 uint64 now_micros = EnvTime::NowMicros();
323 time_t now_seconds = static_cast<time_t>(now_micros / 1000000);
324 int32 micros_remainder = static_cast<int32>(now_micros % 1000000);
325 const size_t time_buffer_size = 30;
326 char time_buffer[time_buffer_size];
327 strftime(time_buffer, time_buffer_size, "%Y-%m-%d %H:%M:%S",
328 localtime(&now_seconds));
329 const size_t tid_buffer_size = 10;
330 char tid_buffer[tid_buffer_size] = "";
331 if (log_thread_id) {
332 snprintf(tid_buffer, sizeof(tid_buffer), " %7u", gettid());
333 }
334 // TODO(jeff,sanjay): Replace this with something that logs through the env.
335 fprintf(stderr, "%s.%06d: %c%s %s:%d] %s\n", time_buffer, micros_remainder,
336 "IWEF"[severity_], tid_buffer, fname_, line_, str().c_str());
337 #endif // TF_ANDROID_ENABLE_LOGSINK
338 }
339
MaxVLogLevel()340 int64 LogMessage::MaxVLogLevel() {
341 static int64 max_vlog_level = MaxVLogLevelFromEnv();
342 return max_vlog_level;
343 }
344
VmoduleActivated(const char * fname,int level)345 bool LogMessage::VmoduleActivated(const char* fname, int level) {
346 if (level <= MaxVLogLevel()) {
347 return true;
348 }
349 static VmoduleMap* vmodules = VmodulesMapFromEnv();
350 if (TF_PREDICT_TRUE(vmodules == nullptr)) {
351 return false;
352 }
353 const char* last_slash = strrchr(fname, '/');
354 const char* module_start = last_slash == nullptr ? fname : last_slash + 1;
355 const char* dot_after = strchr(module_start, '.');
356 const char* module_limit =
357 dot_after == nullptr ? strchr(fname, '\0') : dot_after;
358 StringData module(module_start, module_limit - module_start);
359 auto it = vmodules->find(module);
360 return it != vmodules->end() && it->second >= level;
361 }
362
LogMessageFatal(const char * file,int line)363 LogMessageFatal::LogMessageFatal(const char* file, int line)
364 : LogMessage(file, line, FATAL) {}
~LogMessageFatal()365 LogMessageFatal::~LogMessageFatal() {
366 // abort() ensures we don't return (we promised we would not via
367 // ATTRIBUTE_NORETURN).
368 GenerateLogMessage();
369 abort();
370 }
371
LogString(const char * fname,int line,int severity,const string & message)372 void LogString(const char* fname, int line, int severity,
373 const string& message) {
374 LogMessage(fname, line, severity) << message;
375 }
376
377 template <>
MakeCheckOpValueString(std::ostream * os,const char & v)378 void MakeCheckOpValueString(std::ostream* os, const char& v) {
379 if (v >= 32 && v <= 126) {
380 (*os) << "'" << v << "'";
381 } else {
382 (*os) << "char value " << static_cast<int16>(v);
383 }
384 }
385
386 template <>
MakeCheckOpValueString(std::ostream * os,const signed char & v)387 void MakeCheckOpValueString(std::ostream* os, const signed char& v) {
388 if (v >= 32 && v <= 126) {
389 (*os) << "'" << v << "'";
390 } else {
391 (*os) << "signed char value " << static_cast<int16>(v);
392 }
393 }
394
395 template <>
MakeCheckOpValueString(std::ostream * os,const unsigned char & v)396 void MakeCheckOpValueString(std::ostream* os, const unsigned char& v) {
397 if (v >= 32 && v <= 126) {
398 (*os) << "'" << v << "'";
399 } else {
400 (*os) << "unsigned char value " << static_cast<uint16>(v);
401 }
402 }
403
404 #if LANG_CXX11
405 template <>
MakeCheckOpValueString(std::ostream * os,const std::nullptr_t & v)406 void MakeCheckOpValueString(std::ostream* os, const std::nullptr_t& v) {
407 (*os) << "nullptr";
408 }
409 #endif
410
CheckOpMessageBuilder(const char * exprtext)411 CheckOpMessageBuilder::CheckOpMessageBuilder(const char* exprtext)
412 : stream_(new std::ostringstream) {
413 *stream_ << "Check failed: " << exprtext << " (";
414 }
415
~CheckOpMessageBuilder()416 CheckOpMessageBuilder::~CheckOpMessageBuilder() { delete stream_; }
417
ForVar2()418 std::ostream* CheckOpMessageBuilder::ForVar2() {
419 *stream_ << " vs. ";
420 return stream_;
421 }
422
NewString()423 string* CheckOpMessageBuilder::NewString() {
424 *stream_ << ")";
425 return new string(stream_->str());
426 }
427
428 namespace {
429 // The following code behaves like AtomicStatsCounter::LossyAdd() for
430 // speed since it is fine to lose occasional updates.
431 // Returns old value of *counter.
LossyIncrement(std::atomic<uint32> * counter)432 uint32 LossyIncrement(std::atomic<uint32>* counter) {
433 const uint32 value = counter->load(std::memory_order_relaxed);
434 counter->store(value + 1, std::memory_order_relaxed);
435 return value;
436 }
437
438 } // namespace
439
ShouldLog(int n)440 bool LogEveryNState::ShouldLog(int n) {
441 return n != 0 && (LossyIncrement(&counter_) % n) == 0;
442 }
443
ShouldLog(int n)444 bool LogFirstNState::ShouldLog(int n) {
445 const int counter_value =
446 static_cast<int>(counter_.load(std::memory_order_relaxed));
447 if (counter_value < n) {
448 counter_.store(counter_value + 1, std::memory_order_relaxed);
449 return true;
450 }
451 return false;
452 }
453
ShouldLog(int ignored)454 bool LogEveryPow2State::ShouldLog(int ignored) {
455 const uint32 new_value = LossyIncrement(&counter_) + 1;
456 return (new_value & (new_value - 1)) == 0;
457 }
458
459 #ifdef TF_ANDROID_ENABLE_LOG_EVERY_N_SECONDS
ShouldLog(double seconds)460 bool LogEveryNSecState::ShouldLog(double seconds) {
461 LossyIncrement(&counter_);
462 const int64 now_cycles = absl::base_internal::CycleClock::Now();
463 int64 next_cycles = next_log_time_cycles_.load(std::memory_order_relaxed);
464 do {
465 if (now_cycles <= next_cycles) return false;
466 } while (!next_log_time_cycles_.compare_exchange_weak(
467 next_cycles,
468 now_cycles + seconds * absl::base_internal::CycleClock::Frequency(),
469 std::memory_order_relaxed, std::memory_order_relaxed));
470 return true;
471 }
472 #endif
473
474 } // namespace internal
475
476 #ifdef TF_ANDROID_ENABLE_LOGSINK
TFAddLogSink(TFLogSink * sink)477 void TFAddLogSink(TFLogSink* sink) {
478 internal::TFLogSinks::Instance().Add(sink);
479 }
480
TFRemoveLogSink(TFLogSink * sink)481 void TFRemoveLogSink(TFLogSink* sink) {
482 internal::TFLogSinks::Instance().Remove(sink);
483 }
484
TFGetLogSinks()485 std::vector<TFLogSink*> TFGetLogSinks() {
486 return internal::TFLogSinks::Instance().GetSinks();
487 }
488
Send(const TFLogEntry & entry)489 void TFDefaultLogSink::Send(const TFLogEntry& entry) {
490 #ifdef PLATFORM_POSIX_ANDROID
491 int android_log_level;
492 switch (entry.log_severity()) {
493 case absl::LogSeverity::kInfo:
494 android_log_level = ANDROID_LOG_INFO;
495 break;
496 case absl::LogSeverity::kWarning:
497 android_log_level = ANDROID_LOG_WARN;
498 break;
499 case absl::LogSeverity::kError:
500 android_log_level = ANDROID_LOG_ERROR;
501 break;
502 case absl::LogSeverity::kFatal:
503 android_log_level = ANDROID_LOG_FATAL;
504 break;
505 default:
506 if (entry.log_severity() < absl::LogSeverity::kInfo) {
507 android_log_level = ANDROID_LOG_VERBOSE;
508 } else {
509 android_log_level = ANDROID_LOG_ERROR;
510 }
511 break;
512 }
513
514 std::stringstream ss;
515 const auto& fname = entry.FName();
516 auto pos = fname.find("/");
517 ss << (pos != std::string::npos ? fname.substr(pos + 1) : fname) << ":"
518 << entry.Line() << " " << entry.ToString();
519 __android_log_write(android_log_level, "native", ss.str().c_str());
520
521 // Also log to stderr (for standalone Android apps).
522 // Don't use 'std::cerr' since it crashes on Android.
523 fprintf(stderr, "native : %s\n", ss.str().c_str());
524
525 // Android logging at level FATAL does not terminate execution, so abort()
526 // is still required to stop the program.
527 if (entry.log_severity() == absl::LogSeverity::kFatal) {
528 abort();
529 }
530 #else // PLATFORM_POSIX_ANDROID
531 static bool log_thread_id = internal::EmitThreadIdFromEnv();
532 uint64 now_micros = EnvTime::NowMicros();
533 time_t now_seconds = static_cast<time_t>(now_micros / 1000000);
534 int32 micros_remainder = static_cast<int32>(now_micros % 1000000);
535 const size_t time_buffer_size = 30;
536 char time_buffer[time_buffer_size];
537 strftime(time_buffer, time_buffer_size, "%Y-%m-%d %H:%M:%S",
538 localtime(&now_seconds));
539 const size_t tid_buffer_size = 10;
540 char tid_buffer[tid_buffer_size] = "";
541 if (log_thread_id) {
542 snprintf(tid_buffer, sizeof(tid_buffer), " %7u",
543 absl::base_internal::GetTID());
544 }
545
546 char sev;
547 switch (entry.log_severity()) {
548 case absl::LogSeverity::kInfo:
549 sev = 'I';
550 break;
551
552 case absl::LogSeverity::kWarning:
553 sev = 'W';
554 break;
555
556 case absl::LogSeverity::kError:
557 sev = 'E';
558 break;
559
560 case absl::LogSeverity::kFatal:
561 sev = 'F';
562 break;
563
564 default:
565 assert(false && "Unknown logging severity");
566 sev = '?';
567 break;
568 }
569
570 // TODO(jeff,sanjay): Replace this with something that logs through the env.
571 fprintf(stderr, "%s.%06d: %c%s %s:%d] %s\n", time_buffer, micros_remainder,
572 sev, tid_buffer, entry.FName().c_str(), entry.Line(),
573 entry.ToString().c_str());
574 #endif // PLATFORM_POSIX_ANDROID
575 }
576 #endif // TF_ANDROID_ENABLE_LOGSINK
577
578 } // namespace tensorflow
579