1 // Copyright 2023 The Android Open Source Project
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 "logging.h"
16 
17 #include <chrono>
18 #include <cinttypes>
19 #include <cstdarg>
20 #include <cstring>
21 #include <sstream>
22 #include <string>
23 #include <thread>
24 
25 #ifdef _WIN32
26 #include <windows.h>
27 #endif
28 
29 #ifdef __linux__
30 #include <sys/syscall.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33 #endif
34 
35 namespace {
36 
37 constexpr int kMaxThreadIdLength = 7;  // 7 digits for the thread id is what Google uses everywhere.
38 
39 gfxstream_logger_t sLogger = nullptr;
40 bool sEnableVerbose = false;
41 bool sEnableColors = false;
42 
43 // Returns the current thread id as a string of at most kMaxThreadIdLength characters.
44 // We try to avoid using std::this_thread::get_id() because on Linux at least it returns a long
45 // number (e.g. 139853607339840) which isn't the same as the thread id from the OS itself.
46 // The current logic is inspired by:
47 // https://github.com/abseil/abseil-cpp/blob/52d41a9ec23e39db7e2cbce5c9449506cf2d3a5c/absl/base/internal/sysinfo.cc#L367-L381
getThreadID()48 std::string getThreadID() {
49     std::ostringstream ss;
50 #if defined(_WIN32)
51     ss << GetCurrentThreadId();
52 #elif defined(__linux__)
53     ss << syscall(__NR_gettid);
54 #else
55     ss << std::this_thread::get_id();
56 #endif
57     std::string result = ss.str();
58     // Truncate on the left if necessary
59     return result.length() > kMaxThreadIdLength
60                ? result.substr(result.length() - kMaxThreadIdLength)
61                : result;
62 }
63 
64 // Caches the thread id in thread local storage to increase performance
65 // Inspired by:
66 // https://github.com/abseil/abseil-cpp/blob/52d41a9ec23e39db7e2cbce5c9449506cf2d3a5c/absl/base/internal/sysinfo.cc#L494-L504
getCachedThreadID()67 const char* getCachedThreadID() {
68     static thread_local std::string thread_id = getThreadID();
69     return thread_id.c_str();
70 }
71 
72 // Borrowed from
73 // https://cs.android.com/android/platform/superproject/+/master:system/libbase/logging.cpp;l=84-98;drc=18c2bd4f3607cb300bb96e543df91dfdda6a9655
74 // Note: we use this over std::filesystem::path to keep it as fast as possible.
GetFileBasename(const char * file)75 const char* GetFileBasename(const char* file) {
76 #if defined(_WIN32)
77     const char* last_backslash = strrchr(file, '\\');
78     if (last_backslash != nullptr) {
79         return last_backslash + 1;
80     }
81 #endif
82     const char* last_slash = strrchr(file, '/');
83     if (last_slash != nullptr) {
84         return last_slash + 1;
85     }
86     return file;
87 }
88 
89 }  // namespace
90 
get_gfx_stream_logger()91 gfxstream_logger_t get_gfx_stream_logger() { return sLogger; };
set_gfxstream_logger(gfxstream_logger_t f)92 void set_gfxstream_logger(gfxstream_logger_t f) { sLogger = f; }
93 
set_gfxstream_enable_verbose_logs()94 void set_gfxstream_enable_verbose_logs() { sEnableVerbose = true; }
95 
set_gfxstream_enable_log_colors()96 void set_gfxstream_enable_log_colors() { sEnableColors = true; }
97 
OutputLog(FILE * stream,char severity,const char * file,unsigned int line,int64_t timestamp_us,const char * format,...)98 void OutputLog(FILE* stream, char severity, const char* file, unsigned int line,
99                int64_t timestamp_us, const char* format, ...) {
100     if (sLogger) {
101         char formatted_message[2048];
102         va_list args;
103         va_start(args, format);
104         int ret = vsnprintf(formatted_message, sizeof(formatted_message), format, args);
105         va_end(args);
106         if (timestamp_us == 0) {
107             timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>(
108                                std::chrono::system_clock::now().time_since_epoch())
109                                .count();
110         }
111 
112         sLogger(severity, file, line, timestamp_us, formatted_message);
113         return;
114     }
115 
116     if (severity == 'V' && !sEnableVerbose) {
117         return;
118     }
119     if (timestamp_us == 0) {
120         timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>(
121                            std::chrono::system_clock::now().time_since_epoch())
122                            .count();
123     }
124     std::time_t timestamp_s = timestamp_us / 1000000;
125 
126     // Break down the timestamp into the individual time parts
127     std::tm ts_parts = {};
128 #if defined(_WIN32)
129     localtime_s(&ts_parts, &timestamp_s);
130 #else
131     localtime_r(&timestamp_s, &ts_parts);
132 #endif
133 
134     // Get the microseconds part of the timestamp since it's not available in the tm struct
135     int64_t microseconds = timestamp_us % 1000000;
136 
137     // Standard Google logging prefix
138     // See also:
139     // https://github.com/google/glog/blob/9dc1107f88d3a1613d61b80040d83c1c1acbac3d/src/logging.cc#L1612-L1615
140     char prefix[1024];
141     snprintf(prefix, sizeof(prefix), "%c%02d%02d %02d:%02d:%02d.%06" PRId64 " %7s %s:%d]", severity,
142              ts_parts.tm_mon + 1, ts_parts.tm_mday, ts_parts.tm_hour, ts_parts.tm_min,
143              ts_parts.tm_sec, microseconds, getCachedThreadID(), GetFileBasename(file), line);
144 
145     // Actual log message
146     va_list args;
147     va_start(args, format);
148     char formatted_message[2048];
149     int ret = vsnprintf(formatted_message, sizeof(formatted_message), format, args);
150     formatted_message[sizeof(formatted_message) - 1] = 0;
151 
152     // Output prefix and the message with a newline
153     if (sEnableColors) {
154         const char* colorTag = "";
155         const char* colorTagReset = "\x1B[0m";
156 
157         // Colorize errors and warnings
158         if (severity == 'E' || severity == 'F') {
159             colorTag = "\x1B[31m";  // Red
160         } else if (severity == 'W') {
161             colorTag = "\x1B[33m";  // Yellow
162         }
163 
164         fprintf(stream, "%s%s %s\n%s", colorTag, prefix, formatted_message, colorTagReset);
165     } else {
166         fprintf(stream, "%s %s\n", prefix, formatted_message);
167     }
168     va_end(args);
169 }
170