1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
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 <chrono>
17 #include <cstdarg>
18 #include <ctime>
19 #include <fstream>
20 #include <iomanip>
21 #include <iostream>
22 #include <sstream>
23 #include <string>
24
25 #include "Logger.h"
26
27 #if defined(_WIN32)
28 #include <windows.h>
29 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
30 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
31 #endif
32 #ifdef ERROR
33 #undef ERROR
34 #endif
35 #else
36 #include <unistd.h>
37 #endif
38
39 namespace lume {
40
41 namespace {
42 // Note: these must match the ColorCode enum.
43 constexpr int COLOR_CODE_COUNT = 17;
44 constexpr const char* COLOR_CODES[COLOR_CODE_COUNT] = {
45 "\x1B[30m",
46 "\x1B[31m",
47 "\x1B[32m",
48 "\x1B[33m",
49 "\x1B[34m",
50 "\x1B[35m",
51 "\x1B[36m",
52 "\x1B[37m",
53 "\x1B[30;1m",
54 "\x1B[31;1m",
55 "\x1B[32;1m",
56 "\x1B[33;1m",
57 "\x1B[34;1m",
58 "\x1B[35;1m",
59 "\x1B[36;1m",
60 "\x1B[37;1m",
61 "\x1B[0m",
62 };
63
64 // Gets the filename part from the path.
GetFilename(const std::string & path)65 std::string GetFilename(const std::string& path)
66 {
67 for (int i = static_cast<int>(path.size()) - 1; i >= 0; --i) {
68 unsigned int index = static_cast<size_t>(i);
69 if (path[index] == '\\' || path[index] == '/') {
70 return path.substr(index + 1);
71 }
72 }
73 return path;
74 }
75
76 } // namespace
77
78 #if !defined(__PLATFORM_AD__)
79
80 class StdOutput : public ILogger::IOutput {
81 public:
82 enum class ColorCode {
83 BLACK = 0,
84 RED,
85 GREEN,
86 YELLOW,
87 BLUE,
88 MAGENTA,
89 CYAN,
90 WHITE,
91 BLACK_BRIGHT,
92 RED_BRIGHT,
93 GREEN_BRIGHT,
94 YELLOW_BRIGHT,
95 BLUE_BRIGHT,
96 MAGENTA_BRIGHT,
97 CYAN_BRIGHT,
98 WHITE_BRIGHT,
99 RESET,
100 };
101
GetColorString(ColorCode aColorCode)102 static const char* GetColorString(ColorCode aColorCode)
103 {
104 int colorCode = static_cast<int>(aColorCode);
105 LUME_ASSERT(colorCode >= 0 && colorCode < COLOR_CODE_COUNT);
106 return COLOR_CODES[colorCode];
107 }
108
StdOutput()109 StdOutput() : mUseColor(false), mCurrentColorString(nullptr)
110 {
111 #if defined(_WIN32)
112 // Set console (for this program) to use utf-8.
113 constexpr UINT codePageUtf8 = 65001u;
114 SetConsoleOutputCP(codePageUtf8);
115 #endif
116
117 // Try to figure out if this output stream supports colors.
118 #ifdef _WIN32
119 const HANDLE stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
120 if (stdHandle) {
121 // Check if the output is being redirected.
122 DWORD handleMode;
123 if (GetConsoleMode(stdHandle, &handleMode) != 0) {
124 // Try to enable the option needed that supports colors.
125 handleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
126 SetConsoleMode(stdHandle, handleMode);
127
128 GetConsoleMode(stdHandle, &handleMode);
129 if ((handleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) {
130 mUseColor = true;
131 }
132 }
133 }
134 #else
135 if (isatty(fileno(stdout))) {
136 // Using colors if the output is not being redirected.
137 mUseColor = true;
138 }
139 #endif
140 }
141
SetColor(std::ostream & outputStream,ColorCode aColorCode)142 void SetColor(std::ostream& outputStream, ColorCode aColorCode)
143 {
144 if (!mUseColor) {
145 return;
146 }
147
148 const char* colorString = GetColorString(aColorCode);
149 if (colorString == mCurrentColorString) {
150 return;
151 }
152
153 mCurrentColorString = colorString;
154 if (colorString) {
155 outputStream << colorString;
156 }
157 }
158
Write(ILogger::LogLevel logLevel,const char * filename,int linenumber,const char * message)159 void Write(ILogger::LogLevel logLevel, const char* filename, int linenumber, const char* message) override
160 {
161 auto& outputStream = std::cout;
162
163 auto now = std::chrono::system_clock::now();
164 auto time = std::chrono::system_clock::to_time_t(now);
165 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) -
166 std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
167 static constexpr std::streamsize digitSize = 3;
168 outputStream << std::put_time(std::localtime(&time), "%H:%M:%S.") << std::setw(digitSize) << std::left
169 << ms.count() << " " << Logger::GetLogLevelName(logLevel, true);
170
171 if (filename) {
172 static constexpr std::streamsize filenameSize = 30;
173 const std::string filenameLink = " (" + GetFilename(filename) + ':' + std::to_string(linenumber) + ')';
174 outputStream << std::right << std::setw(filenameSize) << filenameLink;
175 }
176 outputStream << ": ";
177
178 if (logLevel >= ILogger::LogLevel::ERROR) {
179 SetColor(outputStream, ColorCode::RED);
180 } else if (logLevel == ILogger::LogLevel::WARNING) {
181 SetColor(outputStream, ColorCode::YELLOW);
182 } else if (logLevel <= ILogger::LogLevel::DEBUG) {
183 SetColor(outputStream, ColorCode::BLACK_BRIGHT);
184 } else {
185 SetColor(outputStream, ColorCode::RESET);
186 }
187
188 outputStream << message;
189 SetColor(outputStream, ColorCode::RESET);
190 outputStream << std::endl;
191 }
192
193 private:
194 bool mUseColor;
195 const char* mCurrentColorString;
196 };
197
198 #endif // !defined(__PLATFORM_AD__)
199
200 #if defined(_WIN32) && !defined(NDEBUG)
201 class WindowsDebugOutput : public ILogger::IOutput {
202 public:
Write(ILogger::LogLevel logLevel,const char * filename,int linenumber,const char * message)203 void Write(ILogger::LogLevel logLevel, const char* filename, int linenumber, const char* message) override
204 {
205 std::stringstream outputStream;
206
207 if (filename) {
208 outputStream << filename << "(" << linenumber << ") : ";
209 } else {
210 outputStream << "lume : ";
211 }
212
213 auto now = std::chrono::system_clock::now();
214 auto time = std::chrono::system_clock::to_time_t(now);
215 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) -
216 std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
217
218 outputStream << std::put_time(std::localtime(&time), "%D %H:%M:%S.") << ms.count() << " "
219 << Logger::GetLogLevelName(logLevel, true);
220 outputStream << ": " << message;
221 outputStream << '\n';
222
223 // Convert from utf8 to windows wide unicode string.
224 std::string output = outputStream.str();
225 int wStringLength =
226 ::MultiByteToWideChar(CP_UTF8, 0, output.c_str(), static_cast<int>(output.size()), nullptr, 0);
227 std::wstring wString(static_cast<size_t>(wStringLength), 0);
228 ::MultiByteToWideChar(CP_UTF8, 0, output.c_str(), static_cast<int>(output.size()), &wString[0], wStringLength);
229
230 ::OutputDebugStringW(wString.c_str());
231 }
232 };
233 #endif
234
235 class FileOutput : public ILogger::IOutput {
236 public:
FileOutput(const std::string & aFilePath)237 explicit FileOutput(const std::string& aFilePath) : IOutput(), mOutputStream(aFilePath, std::ios::app) {}
238
239 ~FileOutput() override = default;
240
Write(ILogger::LogLevel logLevel,const char * filename,int linenumber,const char * message)241 void Write(ILogger::LogLevel logLevel, const char* filename, int linenumber, const char* message) override
242 {
243 if (mOutputStream.is_open()) {
244 auto& outputStream = mOutputStream;
245
246 auto now = std::chrono::system_clock::now();
247 auto time = std::chrono::system_clock::to_time_t(now);
248 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) -
249 std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
250
251 outputStream << std::put_time(std::localtime(&time), "%D %H:%M:%S.") << ms.count() << " "
252 << Logger::GetLogLevelName(logLevel, false);
253
254 if (filename) {
255 outputStream << " (" << filename << ":" << linenumber << "): ";
256 } else {
257 outputStream << ": ";
258 }
259
260 outputStream << message << std::endl;
261 }
262 }
263
264 private:
265 std::ofstream mOutputStream;
266 };
267
CreateLoggerConsoleOutput()268 std::unique_ptr<ILogger::IOutput> CreateLoggerConsoleOutput()
269 {
270 #ifdef __PLATFORM_AD__
271 return std::make_unique<LogcatOutput>();
272 #else
273 return std::make_unique<StdOutput>();
274 #endif
275 }
276
CreateLoggerDebugOutput()277 std::unique_ptr<ILogger::IOutput> CreateLoggerDebugOutput()
278 {
279 #if defined(_WIN32) && !defined(NDEBUG)
280 return std::make_unique<WindowsDebugOutput>();
281 #else
282 return std::unique_ptr<ILogger::IOutput>();
283 #endif
284 }
285
CreateLoggerFileOutput(const char * filename)286 std::unique_ptr<ILogger::IOutput> CreateLoggerFileOutput(const char* filename)
287 {
288 return std::make_unique<FileOutput>(filename);
289 }
290 } // namespace lume
291