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
175 // A class for managing the text file to which VLOG output is written.
176 // If the environment variable TF_CPP_VLOG_FILENAME is set, all VLOG
177 // calls are redirected from stderr to a file with corresponding name.
178 class VlogFileMgr {
179 public:
180 // Determines if the env variable is set and if necessary
181 // opens the file for write access.
182 VlogFileMgr();
183 // Closes the file.
184 ~VlogFileMgr();
185 // Returns either a pointer to the file or stderr.
186 FILE* FilePtr() const;
187
188 private:
189 FILE* vlog_file_ptr;
190 char* vlog_file_name;
191 };
192
VlogFileMgr()193 VlogFileMgr::VlogFileMgr() {
194 vlog_file_name = getenv("TF_CPP_VLOG_FILENAME");
195 vlog_file_ptr =
196 vlog_file_name == nullptr ? nullptr : fopen(vlog_file_name, "w");
197
198 if (vlog_file_ptr == nullptr) {
199 vlog_file_ptr = stderr;
200 }
201 }
202
~VlogFileMgr()203 VlogFileMgr::~VlogFileMgr() {
204 if (vlog_file_ptr != stderr) {
205 fclose(vlog_file_ptr);
206 }
207 }
208
FilePtr() const209 FILE* VlogFileMgr::FilePtr() const { return vlog_file_ptr; }
210
ParseInteger(const char * str,size_t size)211 int ParseInteger(const char* str, size_t size) {
212 // Ideally we would use env_var / safe_strto64, but it is
213 // hard to use here without pulling in a lot of dependencies,
214 // so we use std:istringstream instead
215 string integer_str(str, size);
216 std::istringstream ss(integer_str);
217 int level = 0;
218 ss >> level;
219 return level;
220 }
221
222 // Parse log level (int64) from environment variable (char*)
LogLevelStrToInt(const char * tf_env_var_val)223 int64 LogLevelStrToInt(const char* tf_env_var_val) {
224 if (tf_env_var_val == nullptr) {
225 return 0;
226 }
227 return ParseInteger(tf_env_var_val, strlen(tf_env_var_val));
228 }
229
230 // Using StringPiece breaks Windows build.
231 struct StringData {
232 struct Hasher {
operator ()tensorflow::internal::__anoncd346ebc0111::StringData::Hasher233 size_t operator()(const StringData& sdata) const {
234 // For dependency reasons, we cannot use hash.h here. Use DBJHash instead.
235 size_t hash = 5381;
236 const char* data = sdata.data;
237 for (const char* top = data + sdata.size; data < top; ++data) {
238 hash = ((hash << 5) + hash) + (*data);
239 }
240 return hash;
241 }
242 };
243
244 StringData() = default;
StringDatatensorflow::internal::__anoncd346ebc0111::StringData245 StringData(const char* data, size_t size) : data(data), size(size) {}
246
operator ==tensorflow::internal::__anoncd346ebc0111::StringData247 bool operator==(const StringData& rhs) const {
248 return size == rhs.size && memcmp(data, rhs.data, size) == 0;
249 }
250
251 const char* data = nullptr;
252 size_t size = 0;
253 };
254
255 using VmoduleMap = std::unordered_map<StringData, int, StringData::Hasher>;
256
257 // Returns a mapping from module name to VLOG level, derived from the
258 // TF_CPP_VMODULE environment variable; ownership is transferred to the caller.
VmodulesMapFromEnv()259 VmoduleMap* VmodulesMapFromEnv() {
260 // The value of the env var is supposed to be of the form:
261 // "foo=1,bar=2,baz=3"
262 const char* env = getenv("TF_CPP_VMODULE");
263 if (env == nullptr) {
264 // If there is no TF_CPP_VMODULE configuration (most common case), return
265 // nullptr so that the ShouldVlogModule() API can fast bail out of it.
266 return nullptr;
267 }
268 // The memory returned by getenv() can be invalidated by following getenv() or
269 // setenv() calls. And since we keep references to it in the VmoduleMap in
270 // form of StringData objects, make a copy of it.
271 const char* env_data = strdup(env);
272 VmoduleMap* result = new VmoduleMap();
273 while (true) {
274 const char* eq = strchr(env_data, '=');
275 if (eq == nullptr) {
276 break;
277 }
278 const char* after_eq = eq + 1;
279
280 // Comma either points at the next comma delimiter, or at a null terminator.
281 // We check that the integer we parse ends at this delimiter.
282 const char* comma = strchr(after_eq, ',');
283 const char* new_env_data;
284 if (comma == nullptr) {
285 comma = strchr(after_eq, '\0');
286 new_env_data = comma;
287 } else {
288 new_env_data = comma + 1;
289 }
290 (*result)[StringData(env_data, eq - env_data)] =
291 ParseInteger(after_eq, comma - after_eq);
292 env_data = new_env_data;
293 }
294 return result;
295 }
296
EmitThreadIdFromEnv()297 bool EmitThreadIdFromEnv() {
298 const char* tf_env_var_val = getenv("TF_CPP_LOG_THREAD_ID");
299 return tf_env_var_val == nullptr
300 ? false
301 : ParseInteger(tf_env_var_val, strlen(tf_env_var_val)) != 0;
302 }
303
304 } // namespace
305
MinLogLevelFromEnv()306 int64 MinLogLevelFromEnv() {
307 // We don't want to print logs during fuzzing as that would slow fuzzing down
308 // by almost 2x. So, if we are in fuzzing mode (not just running a test), we
309 // return a value so that nothing is actually printed. Since LOG uses >=
310 // (see ~LogMessage in this file) to see if log messages need to be printed,
311 // the value we're interested on to disable printing is the maximum severity.
312 // See also http://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode
313 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
314 return tensorflow::NUM_SEVERITIES;
315 #else
316 const char* tf_env_var_val = getenv("TF_CPP_MIN_LOG_LEVEL");
317 return LogLevelStrToInt(tf_env_var_val);
318 #endif
319 }
320
MaxVLogLevelFromEnv()321 int64 MaxVLogLevelFromEnv() {
322 // We don't want to print logs during fuzzing as that would slow fuzzing down
323 // by almost 2x. So, if we are in fuzzing mode (not just running a test), we
324 // return a value so that nothing is actually printed. Since VLOG uses <=
325 // (see VLOG_IS_ON in logging.h) to see if log messages need to be printed,
326 // the value we're interested on to disable printing is 0.
327 // See also http://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode
328 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
329 return 0;
330 #else
331 const char* tf_env_var_val = getenv("TF_CPP_MAX_VLOG_LEVEL");
332 return LogLevelStrToInt(tf_env_var_val);
333 #endif
334 }
335
LogMessage(const char * fname,int line,int severity)336 LogMessage::LogMessage(const char* fname, int line, int severity)
337 : fname_(fname), line_(line), severity_(severity) {}
338
AtLocation(const char * fname,int line)339 LogMessage& LogMessage::AtLocation(const char* fname, int line) {
340 fname_ = fname;
341 line_ = line;
342 return *this;
343 }
344
~LogMessage()345 LogMessage::~LogMessage() {
346 // Read the min log level once during the first call to logging.
347 static int64_t min_log_level = MinLogLevelFromEnv();
348 if (severity_ >= min_log_level) {
349 GenerateLogMessage();
350 }
351 }
352
GenerateLogMessage()353 void LogMessage::GenerateLogMessage() {
354 #ifdef TF_ANDROID_ENABLE_LOGSINK
355 TFLogSinks::Instance().Send(TFLogEntry(severity_, fname_, line_, str()));
356 #else
357 static bool log_thread_id = EmitThreadIdFromEnv();
358 uint64 now_micros = EnvTime::NowMicros();
359 time_t now_seconds = static_cast<time_t>(now_micros / 1000000);
360 int32 micros_remainder = static_cast<int32>(now_micros % 1000000);
361 const size_t time_buffer_size = 30;
362 char time_buffer[time_buffer_size];
363 strftime(time_buffer, time_buffer_size, "%Y-%m-%d %H:%M:%S",
364 localtime(&now_seconds));
365 const size_t tid_buffer_size = 10;
366 char tid_buffer[tid_buffer_size] = "";
367 if (log_thread_id) {
368 snprintf(tid_buffer, sizeof(tid_buffer), " %7u", gettid());
369 }
370 // TODO(jeff,sanjay): Replace this with something that logs through the env.
371 fprintf(stderr, "%s.%06d: %c%s %s:%d] %s\n", time_buffer, micros_remainder,
372 "IWEF"[severity_], tid_buffer, fname_, line_, str().c_str());
373 #endif // TF_ANDROID_ENABLE_LOGSINK
374 }
375
MaxVLogLevel()376 int64 LogMessage::MaxVLogLevel() {
377 static int64_t max_vlog_level = MaxVLogLevelFromEnv();
378 return max_vlog_level;
379 }
380
VmoduleActivated(const char * fname,int level)381 bool LogMessage::VmoduleActivated(const char* fname, int level) {
382 if (level <= MaxVLogLevel()) {
383 return true;
384 }
385 static VmoduleMap* vmodules = VmodulesMapFromEnv();
386 if (TF_PREDICT_TRUE(vmodules == nullptr)) {
387 return false;
388 }
389 const char* last_slash = strrchr(fname, '/');
390 const char* module_start = last_slash == nullptr ? fname : last_slash + 1;
391 const char* dot_after = strchr(module_start, '.');
392 const char* module_limit =
393 dot_after == nullptr ? strchr(fname, '\0') : dot_after;
394 StringData module(module_start, module_limit - module_start);
395 auto it = vmodules->find(module);
396 return it != vmodules->end() && it->second >= level;
397 }
398
LogMessageFatal(const char * file,int line)399 LogMessageFatal::LogMessageFatal(const char* file, int line)
400 : LogMessage(file, line, FATAL) {}
~LogMessageFatal()401 LogMessageFatal::~LogMessageFatal() {
402 // abort() ensures we don't return (we promised we would not via
403 // ATTRIBUTE_NORETURN).
404 GenerateLogMessage();
405 abort();
406 }
407
LogString(const char * fname,int line,int severity,const string & message)408 void LogString(const char* fname, int line, int severity,
409 const string& message) {
410 LogMessage(fname, line, severity) << message;
411 }
412
413 template <>
MakeCheckOpValueString(std::ostream * os,const char & v)414 void MakeCheckOpValueString(std::ostream* os, const char& v) {
415 if (v >= 32 && v <= 126) {
416 (*os) << "'" << v << "'";
417 } else {
418 (*os) << "char value " << static_cast<int16>(v);
419 }
420 }
421
422 template <>
MakeCheckOpValueString(std::ostream * os,const signed char & v)423 void MakeCheckOpValueString(std::ostream* os, const signed char& v) {
424 if (v >= 32 && v <= 126) {
425 (*os) << "'" << v << "'";
426 } else {
427 (*os) << "signed char value " << static_cast<int16>(v);
428 }
429 }
430
431 template <>
MakeCheckOpValueString(std::ostream * os,const unsigned char & v)432 void MakeCheckOpValueString(std::ostream* os, const unsigned char& v) {
433 if (v >= 32 && v <= 126) {
434 (*os) << "'" << v << "'";
435 } else {
436 (*os) << "unsigned char value " << static_cast<uint16>(v);
437 }
438 }
439
440 #if LANG_CXX11
441 template <>
MakeCheckOpValueString(std::ostream * os,const std::nullptr_t & v)442 void MakeCheckOpValueString(std::ostream* os, const std::nullptr_t& v) {
443 (*os) << "nullptr";
444 }
445 #endif
446
CheckOpMessageBuilder(const char * exprtext)447 CheckOpMessageBuilder::CheckOpMessageBuilder(const char* exprtext)
448 : stream_(new std::ostringstream) {
449 *stream_ << "Check failed: " << exprtext << " (";
450 }
451
~CheckOpMessageBuilder()452 CheckOpMessageBuilder::~CheckOpMessageBuilder() { delete stream_; }
453
ForVar2()454 std::ostream* CheckOpMessageBuilder::ForVar2() {
455 *stream_ << " vs. ";
456 return stream_;
457 }
458
NewString()459 string* CheckOpMessageBuilder::NewString() {
460 *stream_ << ")";
461 return new string(stream_->str());
462 }
463
464 namespace {
465 // The following code behaves like AtomicStatsCounter::LossyAdd() for
466 // speed since it is fine to lose occasional updates.
467 // Returns old value of *counter.
LossyIncrement(std::atomic<uint32> * counter)468 uint32 LossyIncrement(std::atomic<uint32>* counter) {
469 const uint32 value = counter->load(std::memory_order_relaxed);
470 counter->store(value + 1, std::memory_order_relaxed);
471 return value;
472 }
473
474 } // namespace
475
ShouldLog(int n)476 bool LogEveryNState::ShouldLog(int n) {
477 return n != 0 && (LossyIncrement(&counter_) % n) == 0;
478 }
479
ShouldLog(int n)480 bool LogFirstNState::ShouldLog(int n) {
481 const int counter_value =
482 static_cast<int>(counter_.load(std::memory_order_relaxed));
483 if (counter_value < n) {
484 counter_.store(counter_value + 1, std::memory_order_relaxed);
485 return true;
486 }
487 return false;
488 }
489
ShouldLog(int ignored)490 bool LogEveryPow2State::ShouldLog(int ignored) {
491 const uint32 new_value = LossyIncrement(&counter_) + 1;
492 return (new_value & (new_value - 1)) == 0;
493 }
494
495 #ifdef TF_ANDROID_ENABLE_LOG_EVERY_N_SECONDS
ShouldLog(double seconds)496 bool LogEveryNSecState::ShouldLog(double seconds) {
497 LossyIncrement(&counter_);
498 const int64_t now_cycles = absl::base_internal::CycleClock::Now();
499 int64_t next_cycles = next_log_time_cycles_.load(std::memory_order_relaxed);
500 do {
501 if (now_cycles <= next_cycles) return false;
502 } while (!next_log_time_cycles_.compare_exchange_weak(
503 next_cycles,
504 now_cycles + seconds * absl::base_internal::CycleClock::Frequency(),
505 std::memory_order_relaxed, std::memory_order_relaxed));
506 return true;
507 }
508 #endif
509
510 } // namespace internal
511
512 #ifdef TF_ANDROID_ENABLE_LOGSINK
TFAddLogSink(TFLogSink * sink)513 void TFAddLogSink(TFLogSink* sink) {
514 internal::TFLogSinks::Instance().Add(sink);
515 }
516
TFRemoveLogSink(TFLogSink * sink)517 void TFRemoveLogSink(TFLogSink* sink) {
518 internal::TFLogSinks::Instance().Remove(sink);
519 }
520
TFGetLogSinks()521 std::vector<TFLogSink*> TFGetLogSinks() {
522 return internal::TFLogSinks::Instance().GetSinks();
523 }
524
Send(const TFLogEntry & entry)525 void TFDefaultLogSink::Send(const TFLogEntry& entry) {
526 #ifdef PLATFORM_POSIX_ANDROID
527 int android_log_level;
528 switch (entry.log_severity()) {
529 case absl::LogSeverity::kInfo:
530 android_log_level = ANDROID_LOG_INFO;
531 break;
532 case absl::LogSeverity::kWarning:
533 android_log_level = ANDROID_LOG_WARN;
534 break;
535 case absl::LogSeverity::kError:
536 android_log_level = ANDROID_LOG_ERROR;
537 break;
538 case absl::LogSeverity::kFatal:
539 android_log_level = ANDROID_LOG_FATAL;
540 break;
541 default:
542 if (entry.log_severity() < absl::LogSeverity::kInfo) {
543 android_log_level = ANDROID_LOG_VERBOSE;
544 } else {
545 android_log_level = ANDROID_LOG_ERROR;
546 }
547 break;
548 }
549
550 std::stringstream ss;
551 const auto& fname = entry.FName();
552 auto pos = fname.find("/");
553 ss << (pos != std::string::npos ? fname.substr(pos + 1) : fname) << ":"
554 << entry.Line() << " " << entry.ToString();
555 __android_log_write(android_log_level, "native", ss.str().c_str());
556
557 // Also log to stderr (for standalone Android apps).
558 // Don't use 'std::cerr' since it crashes on Android.
559 fprintf(stderr, "native : %s\n", ss.str().c_str());
560
561 // Android logging at level FATAL does not terminate execution, so abort()
562 // is still required to stop the program.
563 if (entry.log_severity() == absl::LogSeverity::kFatal) {
564 abort();
565 }
566 #else // PLATFORM_POSIX_ANDROID
567 static const internal::VlogFileMgr vlog_file;
568 static bool log_thread_id = internal::EmitThreadIdFromEnv();
569 uint64 now_micros = EnvTime::NowMicros();
570 time_t now_seconds = static_cast<time_t>(now_micros / 1000000);
571 int32_t micros_remainder = static_cast<int32>(now_micros % 1000000);
572 const size_t time_buffer_size = 30;
573 char time_buffer[time_buffer_size];
574 strftime(time_buffer, time_buffer_size, "%Y-%m-%d %H:%M:%S",
575 localtime(&now_seconds));
576 const size_t tid_buffer_size = 10;
577 char tid_buffer[tid_buffer_size] = "";
578 if (log_thread_id) {
579 snprintf(tid_buffer, sizeof(tid_buffer), " %7u",
580 absl::base_internal::GetTID());
581 }
582
583 char sev;
584 switch (entry.log_severity()) {
585 case absl::LogSeverity::kInfo:
586 sev = 'I';
587 break;
588
589 case absl::LogSeverity::kWarning:
590 sev = 'W';
591 break;
592
593 case absl::LogSeverity::kError:
594 sev = 'E';
595 break;
596
597 case absl::LogSeverity::kFatal:
598 sev = 'F';
599 break;
600
601 default:
602 assert(false && "Unknown logging severity");
603 sev = '?';
604 break;
605 }
606
607 fprintf(vlog_file.FilePtr(), "%s.%06d: %c%s %s:%d] %s\n", time_buffer,
608 micros_remainder, sev, tid_buffer, entry.FName().c_str(),
609 entry.Line(), entry.ToString().c_str());
610 #endif // PLATFORM_POSIX_ANDROID
611 }
612 #endif // TF_ANDROID_ENABLE_LOGSINK
613
614 } // namespace tensorflow
615