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