1 /*
2  * Copyright (C) 2023 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 "Logger.h"
17 
18 
19 #include <iostream>
20 #include <string>
21 #include <sstream>
22 #include <fstream>
23 #include <cstdarg>
24 #include <ctime>
25 #include <iomanip>
26 #include <chrono>
27 
28 #ifdef __ANDROID__
29 #include <android/log.h>
30 #endif
31 
32 #if _WIN32
33 #include <windows.h>
34 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
35 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
36 #endif
37 
38 #else
39 #include <unistd.h>
40 #endif
41 
42 
43 namespace lume
44 {
45 
46 namespace
47 {
48 
49 //Gets the filename part from the path.
GetFilename(const std::string & aPath)50 std::string GetFilename(const std::string &aPath)
51 {
52     for (int i = static_cast<int>(aPath.size()) - 1; i >= 0 ; --i) {
53         unsigned int index = static_cast<size_t>(i);
54         if (aPath[index] == '\\' || aPath[index] == '/') {
55             return aPath.substr(index + 1);
56         }
57     }
58     return aPath;
59 }
60 
61 } // empty namespace
62 
63 
64 #if !defined(__ANDROID__)
65 
66 class StdOutput : public ILogger::IOutput
67 {
68 public:
69     enum class ColorCode
70     {
71         BLACK = 0,
72         RED,
73         GREEN,
74         YELLOW,
75         BLUE,
76         MAGENTA,
77         CYAN,
78         WHITE,
79         BLACK_BRIGHT,
80         RED_BRIGHT,
81         GREEN_BRIGHT,
82         YELLOW_BRIGHT,
83         BLUE_BRIGHT,
84         MAGENTA_BRIGHT,
85         CYAN_BRIGHT,
86         WHITE_BRIGHT,
87         RESET,
88     };
89 
getColorString(ColorCode aColorCode)90     static const char* getColorString(ColorCode aColorCode)
91     {
92         // Note: these must match the ColorCode enum.
93         constexpr int COLOR_CODE_COUNT = 17;
94         constexpr const char* COLOR_CODES[COLOR_CODE_COUNT] =
95         {
96             "\x1B[30m", "\x1B[31m", "\x1B[32m", "\x1B[33m",
97             "\x1B[34m", "\x1B[35m", "\x1B[36m", "\x1B[37m",
98             "\x1B[30;1m", "\x1B[31;1m", "\x1B[32;1m", "\x1B[33;1m",
99             "\x1B[34;1m", "\x1B[35;1m", "\x1B[36;1m", "\x1B[37;1m",
100             "\x1B[0m",
101         };
102 
103         int colorCode = static_cast<int>(aColorCode);
104         LUME_ASSERT(colorCode >= 0 && colorCode < COLOR_CODE_COUNT);
105         return COLOR_CODES[colorCode];
106     }
107 
StdOutput()108     StdOutput() : mUseColor(false), mCurrentColorString(nullptr)
109     {
110 #if _WIN32
111         // Set console (for this program) to use utf-8.
112         SetConsoleOutputCP(65001);
113 #endif
114 
115         // Try to figure out if this output stream supports colors.
116 #ifdef _WIN32
117         const HANDLE stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
118         if (stdHandle)
119         {
120             // Check if the output is being redirected.
121             DWORD handleMode;
122             if (GetConsoleMode(stdHandle, &handleMode) != 0)
123             {
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                 {
131                     mUseColor = true;
132                 }
133             }
134         }
135 #else
136         if (isatty(fileno(stdout)))
137         {
138             // Using colors if the output is not being redirected.
139             mUseColor = true;
140         }
141 #endif
142     }
143 
SetColor(std::ostream & outputStream,ColorCode aColorCode)144     void SetColor(std::ostream &outputStream, ColorCode aColorCode)
145     {
146         if (!mUseColor) {
147             return;
148         }
149 
150         const char* colorString = getColorString(aColorCode);
151         if (colorString == mCurrentColorString) {
152             return;
153         }
154 
155         mCurrentColorString = colorString;
156         if (colorString) {
157             outputStream << colorString;
158         }
159     }
160 
write(ILogger::LogLevel aLogLevel,const char * aFilename,int aLinenumber,const char * aMessage)161     void write(ILogger::LogLevel aLogLevel, const char *aFilename, int aLinenumber, const char *aMessage) override
162     {
163         auto &outputStream = std::cout;
164 
165         auto now = std::chrono::system_clock::now();
166         auto time = std::chrono::system_clock::to_time_t(now);
167         auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) -
168             std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
169 
170         outputStream << std::put_time(std::localtime(&time), "%H:%M:%S.") << std::setw(3) << std::left << ms.count() << " " << Logger::getLogLevelName(aLogLevel, true);
171 
172         if (aFilename)
173         {
174             const std::string filenameLink = " (" + GetFilename(aFilename) + ":" + std::to_string(aLinenumber) + ")";
175             outputStream << std::right << std::setw(30) << filenameLink;
176         }
177         outputStream << ": ";
178 
179         if (aLogLevel >= ILogger::LogLevel::Error)
180         {
181             SetColor(outputStream, ColorCode::RED);
182         }
183         else if (aLogLevel == ILogger::LogLevel::Warning)
184         {
185             SetColor(outputStream, ColorCode::YELLOW);
186         }
187         else if (aLogLevel <= ILogger::LogLevel::Debug)
188         {
189             SetColor(outputStream, ColorCode::BLACK_BRIGHT);
190         }
191         else
192         {
193             SetColor(outputStream, ColorCode::RESET);
194         }
195 
196         outputStream << aMessage;
197         SetColor(outputStream, ColorCode::RESET);
198         outputStream << std::endl;
199     }
200 
201 private:
202     bool mUseColor;
203     const char *mCurrentColorString;
204 
205 };
206 
207 #endif // !defined(__ANDROID__)
208 
209 
210 #if defined(_WIN32) && !defined(NDEBUG)
211 class WindowsDebugOutput : public ILogger::IOutput
212 {
213 public:
write(ILogger::LogLevel aLogLevel,const char * aFilename,int aLinenumber,const char * aMessage)214     void write(ILogger::LogLevel aLogLevel, const char *aFilename, int aLinenumber, const char *aMessage) override
215     {
216         std::stringstream outputStream;
217 
218         if (aFilename)
219         {
220             outputStream << aFilename << "(" << aLinenumber << ") : ";
221         }
222         else
223         {
224             outputStream << "lume : ";
225         }
226 
227         auto now = std::chrono::system_clock::now();
228         auto time = std::chrono::system_clock::to_time_t(now);
229         auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) -
230             std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
231 
232         outputStream << std::put_time(std::localtime(&time), "%D %H:%M:%S.") << ms.count() << " " << Logger::getLogLevelName(aLogLevel, true);
233         outputStream << ": " << aMessage;
234         outputStream << '\n';
235 
236         // Convert from utf8 to windows wide unicode string.
237         std::string message = outputStream.str();
238         int wStringLength = ::MultiByteToWideChar(CP_UTF8, 0, message.c_str(), static_cast<int>(message.size()), nullptr, 0);
239         std::wstring wString(static_cast<size_t>(wStringLength), 0);
240         ::MultiByteToWideChar(CP_UTF8, 0, message.c_str(), static_cast<int>(message.size()), &wString[0], wStringLength);
241 
242         ::OutputDebugStringW(wString.c_str());
243     }
244 };
245 #endif
246 
247 class FileOutput : public ILogger::IOutput
248 {
249 public:
FileOutput(const std::string & aFilePath)250     explicit FileOutput(const std::string &aFilePath) : IOutput(), mOutputStream(aFilePath, std::ios::app) {}
251 
252     ~FileOutput() override = default;
253 
write(ILogger::LogLevel aLogLevel,const char * aFilename,int aLinenumber,const char * aMessage)254     void write(ILogger::LogLevel aLogLevel, const char *aFilename, int aLinenumber, const char *aMessage) override
255     {
256         if (mOutputStream.is_open())
257         {
258             auto &outputStream = mOutputStream;
259 
260             auto now = std::chrono::system_clock::now();
261             auto time = std::chrono::system_clock::to_time_t(now);
262             auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) -
263                 std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
264 
265             outputStream << std::put_time(std::localtime(&time), "%D %H:%M:%S.") << ms.count() << " " << Logger::getLogLevelName(aLogLevel, false);
266 
267             if (aFilename)
268             {
269                 outputStream << " (" << aFilename << ":" << aLinenumber << "): ";
270             }
271             else
272             {
273                 outputStream << ": ";
274             }
275 
276             outputStream << aMessage << std::endl;
277         }
278     }
279 private:
280     std::ofstream mOutputStream;
281 };
282 
283 #if defined(__ANDROID__)
284 class LogcatOutput : public Logger::IOutput
285 {
286 public:
write(ILogger::LogLevel aLogLevel,const char * aFilename,int aLinenumber,const char * aMessage)287     void write(ILogger::LogLevel aLogLevel, const char *aFilename, int aLinenumber, const char *aMessage) override
288     {
289         int logPriority;
290         switch (aLogLevel)
291         {
292         case ILogger::LogLevel::Verbose:
293             logPriority = ANDROID_LOG_VERBOSE;
294             break;
295 
296         case ILogger::LogLevel::Debug:
297             logPriority = ANDROID_LOG_DEBUG;
298             break;
299 
300         case ILogger::LogLevel::Info:
301             logPriority = ANDROID_LOG_INFO;
302             break;
303 
304         case ILogger::LogLevel::Warning:
305             logPriority = ANDROID_LOG_WARN;
306             break;
307 
308         case ILogger::LogLevel::Error:
309             logPriority = ANDROID_LOG_ERROR;
310             break;
311 
312         case ILogger::LogLevel::Fatal:
313             logPriority = ANDROID_LOG_FATAL;
314             break;
315 
316         default:
317             logPriority = ANDROID_LOG_VERBOSE;
318             break;
319         }
320 
321         if (aFilename)
322         {
323             std::stringstream outputStream;
324             outputStream << "(" << GetFilename(aFilename) << ":" << aLinenumber << "): ";
325             outputStream << aMessage;
326             __android_log_write(logPriority, "lume", outputStream.str().c_str());
327         }
328         else
329         {
330             __android_log_write(logPriority, "lume", aMessage);
331         }
332     }
333 };
334 #endif
335 
336 
337 
createLoggerConsoleOutput()338 std::unique_ptr<ILogger::IOutput> createLoggerConsoleOutput()
339 {
340 #ifdef __ANDROID__
341     return std::make_unique<LogcatOutput>();
342 #else
343     return std::make_unique<StdOutput>();
344 #endif
345 }
346 
347 
createLoggerDebugOutput()348 std::unique_ptr<ILogger::IOutput> createLoggerDebugOutput()
349 {
350 #if defined(_WIN32) && !defined(NDEBUG)
351     return std::make_unique<WindowsDebugOutput>();
352 #else
353     return std::unique_ptr<ILogger::IOutput>();
354 #endif
355 }
356 
357 
createLoggerFileOutput(const char * aFilename)358 std::unique_ptr<ILogger::IOutput> createLoggerFileOutput(const char *aFilename)
359 {
360     return std::make_unique<FileOutput>(aFilename);
361 }
362 
363 } // lume