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