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 #include <unistd.h>
29
30 #include <core/namespace.h>
31
32 #include "log/logger.h"
33
34 CORE_BEGIN_NAMESPACE()
35 using BASE_NS::string_view;
36
37 namespace {
38 // Gets the filename part from the path.
GetFilename(std::string_view path)39 std::string_view GetFilename(std::string_view path)
40 {
41 if (auto const pos = path.find_last_of("\\/"); pos != std::string_view::npos) {
42 return path.substr(pos + 1);
43 }
44 return path;
45 }
46
PrintTimeStamp(std::ostream & outputStream)47 void PrintTimeStamp(std::ostream& outputStream)
48 {
49 const auto now = std::chrono::system_clock::now();
50 const auto time = std::chrono::system_clock::to_time_t(now);
51 const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) -
52 std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
53
54 outputStream << std::put_time(std::localtime(&time), "%D %H:%M:%S.");
55 outputStream << std::right << std::setfill('0') << std::setw(3) << ms.count() << std::setfill(' '); // 3: alignment
56 }
57 } // namespace
58
59 class StdOutput final : public ILogger::IOutput {
60 public:
StdOutput()61 StdOutput()
62 {
63 // Try to figure out if this output stream supports colors.
64 if (isatty(fileno(stdout))) {
65 // Using colors if the output is not being redirected.
66 useColor_ = true;
67 }
68 }
69
Write(ILogger::LogLevel logLevel,const string_view filename,int linenumber,const string_view message)70 void Write(
71 ILogger::LogLevel logLevel, const string_view filename, int linenumber, const string_view message) override
72 {
73 auto& outputStream = std::cout;
74
75 PrintTimeStamp(outputStream);
76 const auto levelString = Logger::GetLogLevelName(logLevel, true);
77 outputStream << ' ' << std::string_view(levelString.data(), levelString.size());
78
79 if (!filename.empty()) {
80 // Align the printed messages to same horizontal position regardless of the printed filename length.
81 // (Unless the filename is very long)
82 constexpr int fileLinkFieldSize = 30;
83
84 auto const filenameView = GetFilename({ filename.data(), filename.size() });
85 // Break long messages to multiple lines. 0..9 on one line. 10..99 on two lines. 100..999 on three and above
86 // that four.
87 const int lineNumberLength = (linenumber < 10 ? 1 : (linenumber < 100 ? 2 : (linenumber < 1000 ? 3 : 4)));
88 const int fileLinkPadding =
89 fileLinkFieldSize - (static_cast<int>(filenameView.length()) + lineNumberLength);
90 if (fileLinkPadding > 0) {
91 outputStream << std::setw(fileLinkPadding) << "";
92 }
93 outputStream << " (" << filenameView << ':' << linenumber << ')';
94 }
95 outputStream << ": ";
96
97 if (logLevel >= ILogger::LogLevel::LOG_ERROR) {
98 SetColor(outputStream, ColorCode::RED);
99 } else if (logLevel == ILogger::LogLevel::LOG_WARNING) {
100 SetColor(outputStream, ColorCode::YELLOW);
101 } else if (logLevel <= ILogger::LogLevel::LOG_DEBUG) {
102 SetColor(outputStream, ColorCode::BLACK_BRIGHT);
103 } else {
104 SetColor(outputStream, ColorCode::RESET);
105 }
106
107 outputStream << std::string_view(message.data(), message.size());
108 SetColor(outputStream, ColorCode::RESET);
109 outputStream << std::endl;
110 }
111
112 protected:
Destroy()113 void Destroy() override
114 {
115 delete this;
116 }
117
118 private:
119 enum class ColorCode {
120 BLACK = 0,
121 RED,
122 GREEN,
123 YELLOW,
124 BLUE,
125 MAGENTA,
126 CYAN,
127 WHITE,
128 BLACK_BRIGHT,
129 RED_BRIGHT,
130 GREEN_BRIGHT,
131 YELLOW_BRIGHT,
132 BLUE_BRIGHT,
133 MAGENTA_BRIGHT,
134 CYAN_BRIGHT,
135 WHITE_BRIGHT,
136 RESET,
137 COLOR_CODE_COUNT
138 };
139 // Note: these must match the ColorCode enum.
140 static constexpr const string_view COLOR_CODES[static_cast<int>(ColorCode::COLOR_CODE_COUNT)] = {
141 "\x1B[30m",
142 "\x1B[31m",
143 "\x1B[32m",
144 "\x1B[33m",
145 "\x1B[34m",
146 "\x1B[35m",
147 "\x1B[36m",
148 "\x1B[37m",
149 "\x1B[30;1m",
150 "\x1B[31;1m",
151 "\x1B[32;1m",
152 "\x1B[33;1m",
153 "\x1B[34;1m",
154 "\x1B[35;1m",
155 "\x1B[36;1m",
156 "\x1B[37;1m",
157 "\x1B[0m",
158 };
159
GetColorString(ColorCode colorCode)160 static const string_view GetColorString(ColorCode colorCode)
161 {
162 const int code = static_cast<int>(colorCode);
163 CORE_ASSERT(code >= 0 && code < static_cast<int>(ColorCode::COLOR_CODE_COUNT));
164 return COLOR_CODES[code];
165 }
166
SetColor(std::ostream & outputStream,ColorCode colorCode)167 void SetColor(std::ostream& outputStream, ColorCode colorCode)
168 {
169 if (colorCode < ColorCode::BLACK || colorCode >= ColorCode::COLOR_CODE_COUNT) {
170 return;
171 }
172
173 if (!useColor_) {
174 return;
175 }
176
177 if (colorCode == currentColorString_) {
178 return;
179 }
180
181 currentColorString_ = colorCode;
182
183 const auto colorString = GetColorString(colorCode);
184 outputStream << std::string_view(colorString.data(), colorString.size());
185 }
186
187 bool useColor_ { false };
188 ColorCode currentColorString_ { ColorCode::RESET };
189 };
190
191 class FileOutput final : public ILogger::IOutput {
192 public:
FileOutput(const string_view filePath)193 explicit FileOutput(const string_view filePath) : IOutput(), outputStream_(filePath.data(), std::ios::app) {}
194
195 ~FileOutput() override = default;
196
Write(ILogger::LogLevel logLevel,const string_view filename,int linenumber,const string_view message)197 void Write(
198 ILogger::LogLevel logLevel, const string_view filename, int linenumber, const string_view message) override
199 {
200 if (outputStream_.is_open()) {
201 auto& outputStream = outputStream_;
202
203 PrintTimeStamp(outputStream);
204 const auto levelString = Logger::GetLogLevelName(logLevel, true);
205 outputStream << ' ' << std::string_view(levelString.data(), levelString.size());
206
207 if (!filename.empty()) {
208 outputStream << " (" << std::string_view(filename.data(), filename.size()) << ':' << linenumber
209 << "): ";
210 } else {
211 outputStream << ": ";
212 }
213
214 outputStream << std::string_view(message.data(), message.size()) << std::endl;
215 }
216 }
217
218 protected:
Destroy()219 void Destroy() override
220 {
221 delete this;
222 }
223
224 private:
225 std::ofstream outputStream_;
226 };
227
228
CreateLoggerConsoleOutput()229 ILogger::IOutput::Ptr CreateLoggerConsoleOutput()
230 {
231 return ILogger::IOutput::Ptr { new StdOutput };
232 }
233
CreateLoggerDebugOutput()234 ILogger::IOutput::Ptr CreateLoggerDebugOutput()
235 {
236 return {};
237 }
238
239 CORE_END_NAMESPACE()
240