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