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