• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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