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