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_view>
24 
25 #include <core/log.h>
26 
27 #include "log/logger_output.h"
28 
29 #ifndef WIN32_LEAN_AND_MEAN
30 #define WIN32_LEAN_AND_MEAN
31 #endif
32 #pragma warning(push)
33 // C5039	'function': pointer or reference to potentially throwing function passed to extern C function under -EHc.
34 // Undefined behavior may occur if this function throws an exception.
35 #pragma warning(disable : 5039)
36 #include <windows.h>
37 #pragma warning(pop)
38 
39 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
40 constexpr auto ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
41 #endif
42 
43 #include <core/namespace.h>
44 
45 #include "log/logger.h"
46 
47 CORE_BEGIN_NAMESPACE()
48 using BASE_NS::string_view;
49 
50 class StdOutput final : public ILogger::IOutput {
51 public:
StdOutput()52     StdOutput()
53     {
54         // Set console (for this program) to use utf-8.
55         SetConsoleOutputCP(65001u);
56 
57         // Try to figure out if this output stream supports colors.
58         const HANDLE stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
59         if (stdHandle) {
60             // Check if the output is being redirected.
61             DWORD handleMode;
62             if (GetConsoleMode(stdHandle, &handleMode) != 0) {
63                 // Try to enable the option needed that supports colors.
64                 handleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
65                 SetConsoleMode(stdHandle, handleMode);
66 
67                 GetConsoleMode(stdHandle, &handleMode);
68                 if ((handleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) {
69                     useColor_ = true;
70                 }
71             }
72         }
73     }
74 
Write(ILogger::LogLevel logLevel,const string_view filename,int linenumber,const string_view message)75     void Write(
76         ILogger::LogLevel logLevel, const string_view filename, int linenumber, const string_view message) override
77     {
78         auto& outputStream = std::cout;
79 
80         LoggerUtils::PrintTimeStamp(outputStream);
81         const auto levelString = Logger::GetLogLevelName(logLevel, true);
82         outputStream << ' ' << std::string_view(levelString.data(), levelString.size());
83 
84         if (!filename.empty()) {
85             // Align the printed messages to same horizontal position regardless of the printed filename length.
86             // (Unless the filename is very long)
87             constexpr int fileLinkFieldSize = 30;
88 
89             auto const filenameView = LoggerUtils::GetFilename(filename);
90             // Break long messages to multiple lines. 0..9 on one line. 10..99 on two lines. 100..999 on three and above
91             // that four.
92             const int lineNumberLength = (linenumber < 10 ? 1 : (linenumber < 100 ? 2 : (linenumber < 1000 ? 3 : 4)));
93             const int fileLinkPadding =
94                 fileLinkFieldSize - (static_cast<int>(filenameView.length()) + lineNumberLength);
95             if (fileLinkPadding > 0) {
96                 outputStream << std::setw(fileLinkPadding) << "";
97             }
98             outputStream << " (" << std::string_view(filenameView.data(), filenameView.size()) << ':' << linenumber
99                          << ')';
100         }
101         outputStream << ": ";
102 
103         if (logLevel >= ILogger::LogLevel::LOG_ERROR) {
104             SetColor(outputStream, ColorCode::RED);
105         } else if (logLevel == ILogger::LogLevel::LOG_WARNING) {
106             SetColor(outputStream, ColorCode::YELLOW);
107         } else if (logLevel <= ILogger::LogLevel::LOG_DEBUG) {
108             SetColor(outputStream, ColorCode::BLACK_BRIGHT);
109         } else {
110             SetColor(outputStream, ColorCode::RESET);
111         }
112 
113         outputStream << std::string_view(message.data(), message.size());
114         SetColor(outputStream, ColorCode::RESET);
115         outputStream << std::endl;
116     }
117 
118 protected:
Destroy()119     void Destroy() override
120     {
121         delete this;
122     }
123 
124 private:
125     enum class ColorCode {
126         BLACK = 0,
127         RED,
128         GREEN,
129         YELLOW,
130         BLUE,
131         MAGENTA,
132         CYAN,
133         WHITE,
134         BLACK_BRIGHT,
135         RED_BRIGHT,
136         GREEN_BRIGHT,
137         YELLOW_BRIGHT,
138         BLUE_BRIGHT,
139         MAGENTA_BRIGHT,
140         CYAN_BRIGHT,
141         WHITE_BRIGHT,
142         RESET,
143         COLOR_CODE_COUNT
144     };
145     // Note: these must match the ColorCode enum.
146     static constexpr const string_view COLOR_CODES[static_cast<int>(ColorCode::COLOR_CODE_COUNT)] = {
147         "\x1B[30m",
148         "\x1B[31m",
149         "\x1B[32m",
150         "\x1B[33m",
151         "\x1B[34m",
152         "\x1B[35m",
153         "\x1B[36m",
154         "\x1B[37m",
155         "\x1B[30;1m",
156         "\x1B[31;1m",
157         "\x1B[32;1m",
158         "\x1B[33;1m",
159         "\x1B[34;1m",
160         "\x1B[35;1m",
161         "\x1B[36;1m",
162         "\x1B[37;1m",
163         "\x1B[0m",
164     };
165 
GetColorString(ColorCode colorCode)166     static string_view GetColorString(ColorCode colorCode)
167     {
168         const int code = static_cast<int>(colorCode);
169         CORE_ASSERT(code >= 0 && code < static_cast<int>(ColorCode::COLOR_CODE_COUNT));
170         return COLOR_CODES[code];
171     }
172 
SetColor(std::ostream & outputStream,ColorCode colorCode)173     void SetColor(std::ostream& outputStream, ColorCode colorCode)
174     {
175         if (colorCode < ColorCode::BLACK || colorCode >= ColorCode::COLOR_CODE_COUNT) {
176             return;
177         }
178 
179         if (!useColor_) {
180             return;
181         }
182 
183         if (colorCode == currentColorString_) {
184             return;
185         }
186 
187         currentColorString_ = colorCode;
188 
189         const auto colorString = GetColorString(colorCode);
190         outputStream << std::string_view(colorString.data(), colorString.size());
191     }
192 
193     bool useColor_ { false };
194     ColorCode currentColorString_ { ColorCode::RESET };
195 };
196 
197 #if !defined(NDEBUG)
198 class WindowsDebugOutput final : public ILogger::IOutput {
199 public:
Write(ILogger::LogLevel logLevel,const string_view filename,int linenumber,const string_view message)200     void Write(
201         ILogger::LogLevel logLevel, const string_view filename, int linenumber, const string_view message) override
202     {
203         std::stringstream outputStream;
204 
205         if (!filename.empty()) {
206             outputStream << std::string_view(filename.data(), filename.size()) << '(' << linenumber << ") : ";
207         } else {
208             outputStream << "core : ";
209         }
210 
211         LoggerUtils::PrintTimeStamp(outputStream);
212         const auto levelString = Logger::GetLogLevelName(logLevel, true);
213         outputStream << ' ' << std::string_view(levelString.data(), levelString.size());
214         outputStream << ": " << std::string_view(message.data(), message.size());
215         outputStream << '\n';
216 
217         // Convert from utf8 to windows wide unicode string.
218         const std::string string = outputStream.str();
219         const int wStringLength =
220             ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), static_cast<int>(string.size()), nullptr, 0);
221         std::wstring wString(static_cast<const size_t>(wStringLength), 0);
222         ::MultiByteToWideChar(
223             CP_UTF8, 0, string.c_str(), static_cast<int>(string.size()), wString.data(), wStringLength);
224 
225         ::OutputDebugStringW(wString.c_str());
226     }
227 
228 protected:
Destroy()229     void Destroy() override
230     {
231         delete this;
232     }
233 };
234 #endif
235 
CreateLoggerConsoleOutput()236 ILogger::IOutput::Ptr CreateLoggerConsoleOutput()
237 {
238     return ILogger::IOutput::Ptr { new StdOutput };
239 }
240 
CreateLoggerDebugOutput()241 ILogger::IOutput::Ptr CreateLoggerDebugOutput()
242 {
243 #if !defined(NDEBUG)
244     return ILogger::IOutput::Ptr { new WindowsDebugOutput };
245 #else
246     return {};
247 #endif
248 }
249 CORE_END_NAMESPACE()
250