1/**
2 * Copyright (c) 2021-2022 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 "logger.h"
17#include "os/filesystem.h"
18#include "os/thread.h"
19#include "string_helpers.h"
20#include "generated/base_options.h"
21
22#include <cstdarg>
23#include <cstdlib>
24#include <cstring>
25
26#include <fstream>
27#include <iostream>
28#include <string_view>
29
30namespace panda {
31
32Logger *Logger::logger = nullptr;
33thread_local int Logger::nesting = 0;
34
35#include <logger_impl_gen.inc>
36
37void Logger::Initialize(const base_options::Options &options)
38{
39    panda::Logger::ComponentMask component_mask;
40    auto load_components = [&component_mask](auto components) {
41        for (const auto &s : components) {
42            component_mask |= Logger::ComponentMaskFromString(s);
43        }
44    };
45    Level level = Level::LAST;
46
47    if (options.WasSetLogFatal()) {
48        ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options");
49        load_components(options.GetLogFatal());
50        level = Level::FATAL;
51    } else if (options.WasSetLogError()) {
52        ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options");
53        load_components(options.GetLogError());
54        level = Level::ERROR;
55    } else if (options.WasSetLogWarning()) {
56        ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options");
57        load_components(options.GetLogWarning());
58        level = Level::WARNING;
59    } else if (options.WasSetLogInfo()) {
60        ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options");
61        load_components(options.GetLogInfo());
62        level = Level::INFO;
63    } else if (options.WasSetLogDebug()) {
64        ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options");
65        load_components(options.GetLogDebug());
66        level = Level::DEBUG;
67    } else {
68        ASSERT_PRINT(level == Level::LAST, "There are conflicting logger options");
69        load_components(options.GetLogComponents());
70        level = Logger::LevelFromString(options.GetLogLevel());
71    }
72
73#ifdef ENABLE_HILOG
74    Logger::InitializeHiLogging(level, component_mask);
75    return;
76#endif
77
78    if (options.GetLogStream() == "std") {
79        Logger::InitializeStdLogging(level, component_mask);
80    } else if (options.GetLogStream() == "file" || options.GetLogStream() == "fast-file") {
81        const std::string &file_name = options.GetLogFile();
82        Logger::InitializeFileLogging(file_name, level, component_mask, options.GetLogStream() == "fast-file");
83    } else if (options.GetLogStream() == "dummy") {
84        Logger::InitializeDummyLogging(level, component_mask);
85    } else {
86        UNREACHABLE();
87    }
88}
89
90#ifndef NDEBUG
91/**
92 * In debug builds this function allowes or disallowes proceeding with actual logging (i.e. creating Message{})
93 */
94/* static */
95bool Logger::IsMessageSuppressed([[maybe_unused]] Level level, [[maybe_unused]] Component component)
96{
97    // Allowing only to log if it's not a nested log, or it's nested and it's severity is suitable
98    return level >= Logger::logger->nested_allowed_level_ && nesting > 0;
99}
100
101/**
102 * Increases log nesting (i.e. depth, or how many instances of Message{} is active atm) in a given thread
103 */
104/* static */
105void Logger::LogNestingInc()
106{
107    nesting++;
108}
109
110/**
111 * Decreases log nesting (i.e. depth, or how many instances of Message{} is active atm) in a given thread
112 */
113/* static */
114void Logger::LogNestingDec()
115{
116    nesting--;
117}
118#endif  // NDEBUG
119
120auto Logger::Buffer::printf(const char *format, ...) -> Buffer &
121{
122    va_list args;
123    va_start(args, format);  // NOLINT(cppcoreguidelines-pro-type-vararg)
124
125    [[maybe_unused]] int put = vsnprintf_s(this->data(), this->size(), this->size() - 1, format, args);
126    ASSERT(put >= 0 && static_cast<size_t>(put) < BUFFER_SIZE);
127
128    va_end(args);
129    return *this;
130}
131
132os::memory::Mutex Logger::mutex;  // NOLINT(fuchsia-statically-constructed-objects)
133FUNC_MOBILE_LOG_PRINT mlog_buf_print = nullptr;
134
135Logger::Message::~Message()
136{
137    if (print_system_error_) {
138        stream_ << ": " << os::Error(errno).ToString();
139    }
140
141    Logger::Log(level_, component_, stream_.str());
142#ifndef NDEBUG
143    panda::Logger::LogNestingDec();
144#endif
145
146    if (level_ == Level::FATAL) {
147        std::cerr << "FATAL ERROR" << std::endl;
148        std::cerr << "Backtrace [tid=" << os::thread::GetCurrentThreadId() << "]:\n";
149        PrintStack(std::cerr);
150        std::abort();
151    }
152}
153
154/* static */
155void Logger::Log(Level level, Component component, const std::string &str)
156{
157    if (!IsLoggingOn(level, component)) {
158        return;
159    }
160
161    os::memory::LockHolder<os::memory::Mutex> lock(mutex);
162    if (!IsLoggingOn(level, component)) {
163        return;
164    }
165
166    size_t nl = str.find('\n');
167    if (nl == std::string::npos) {
168        logger->LogLineInternal(level, component, str);
169        logger->WriteMobileLog(level, GetComponentTag(component), str.c_str());
170    } else {
171        size_t i = 0;
172        while (nl != std::string::npos) {
173            std::string line = str.substr(i, nl - i);
174            logger->LogLineInternal(level, component, line);
175            logger->WriteMobileLog(level, GetComponentTag(component), line.c_str());
176            i = nl + 1;
177            nl = str.find('\n', i);
178        }
179
180        logger->LogLineInternal(level, component, str.substr(i));
181        logger->WriteMobileLog(level, GetComponentTag(component), str.substr(i).c_str());
182    }
183}
184
185/* static */
186std::string GetPrefix(Logger::Level level, Logger::Component component)
187{
188    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
189    return helpers::string::Format("[TID %06x] %s/%s: ", os::thread::GetCurrentThreadId(), GetLevelTag(level),
190                                   GetComponentTag(component));
191}
192
193/* static */
194void Logger::InitializeFileLogging(const std::string &log_file, Level level, const ComponentMask &component_mask,
195                                   bool is_fast_logging)
196{
197    if (IsInitialized()) {
198        return;
199    }
200
201    os::memory::LockHolder<os::memory::Mutex> lock(mutex);
202
203    if (IsInitialized()) {
204        return;
205    }
206
207    auto path = os::GetAbsolutePath(log_file);
208    if (path == "") {
209        logger = new StderrLogger(level, component_mask);
210        std::string msg = helpers::string::Format("Fallback to stderr logging: resolve file path error");
211        logger->LogLineInternal(Level::ERROR, Component::COMMON, msg);
212        return;
213    }
214
215    std::ofstream stream(path);
216    if (stream) {
217        if (is_fast_logging) {
218            logger = new FastFileLogger(std::move(stream), level, component_mask);
219        } else {
220            logger = new FileLogger(std::move(stream), level, component_mask);
221        }
222    } else {
223        logger = new StderrLogger(level, component_mask);
224        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
225        std::string msg = helpers::string::Format("Fallback to stderr logging: cannot open log file '%s': %s",
226                                                  log_file.c_str(), os::Error(errno).ToString().c_str());
227        logger->LogLineInternal(Level::ERROR, Component::COMMON, msg);
228    }
229}
230
231#ifdef ENABLE_HILOG
232/* static */
233void Logger::InitializeHiLogging(Level level, const ComponentMask &component_mask)
234{
235    if (IsInitialized()) {
236        return;
237    }
238
239    {
240        os::memory::LockHolder<os::memory::Mutex> lock(mutex);
241
242        if (IsInitialized()) {
243            return;
244        }
245
246        logger = new HiLogger(level, component_mask);
247    }
248}
249#endif
250
251/* static */
252void Logger::InitializeStdLogging(Level level, const ComponentMask &component_mask)
253{
254    if (IsInitialized()) {
255        return;
256    }
257
258    {
259        os::memory::LockHolder<os::memory::Mutex> lock(mutex);
260
261        if (IsInitialized()) {
262            return;
263        }
264
265        logger = new StderrLogger(level, component_mask);
266    }
267}
268
269/* static */
270void Logger::InitializeDummyLogging(Level level, const ComponentMask &component_mask)
271{
272    if (IsInitialized()) {
273        return;
274    }
275
276    {
277        os::memory::LockHolder<os::memory::Mutex> lock(mutex);
278
279        if (IsInitialized()) {
280            return;
281        }
282
283        logger = new DummyLogger(level, component_mask);
284    }
285}
286
287/* static */
288void Logger::Destroy()
289{
290    if (!IsInitialized()) {
291        return;
292    }
293
294    Logger *l = nullptr;
295
296    {
297        os::memory::LockHolder<os::memory::Mutex> lock(mutex);
298
299        if (!IsInitialized()) {
300            return;
301        }
302
303        l = logger;
304        logger = nullptr;
305    }
306
307    delete l;
308}
309
310/* static */
311void Logger::ProcessLogLevelFromString(std::string_view s)
312{
313    if (Logger::IsInLevelList(s)) {
314        Logger::SetLevel(Logger::LevelFromString(s));
315    } else {
316        LOG(ERROR, RUNTIME) << "Unknown level " << s;
317    }
318}
319
320/* static */
321void Logger::ProcessLogComponentsFromString(std::string_view s)
322{
323    Logger::ResetComponentMask();
324    size_t last_pos = s.find_first_not_of(',', 0);
325    size_t pos = s.find(',', last_pos);
326    while (last_pos != std::string_view::npos) {
327        std::string_view component_str = s.substr(last_pos, pos - last_pos);
328        last_pos = s.find_first_not_of(',', pos);
329        pos = s.find(',', last_pos);
330        if (Logger::IsInComponentList(component_str)) {
331            Logger::EnableComponent(Logger::ComponentMaskFromString(component_str));
332        } else {
333            LOG(ERROR, RUNTIME) << "Unknown component " << component_str;
334        }
335    }
336}
337
338void FileLogger::LogLineInternal(Level level, Component component, const std::string &str)
339{
340    std::string prefix = GetPrefix(level, component);
341    stream_ << prefix << str << std::endl << std::flush;
342}
343
344void FastFileLogger::LogLineInternal(Level level, Component component, const std::string &str)
345{
346    std::string prefix = GetPrefix(level, component);
347    stream_ << prefix << str << '\n';
348}
349
350#ifdef ENABLE_HILOG
351void HiLogger::LogLineInternal(Level level, Component component, const std::string &str)
352{
353    std::string prefix = GetPrefix(level, component);
354    stream_ << prefix << str;
355    switch (level) {
356        case Level::DEBUG:
357            HILOG_DEBUG(LOG_CORE, "%{public}s", stream_.str().c_str());
358            break;
359        case Level::INFO:
360            HILOG_INFO(LOG_CORE, "%{public}s", stream_.str().c_str());
361            break;
362        case Level::ERROR:
363            HILOG_ERROR(LOG_CORE, "%{public}s", stream_.str().c_str());
364            break;
365        case Level::FATAL:
366            HILOG_FATAL(LOG_CORE, "%{public}s", stream_.str().c_str());
367            break;
368        case Level::WARNING:
369            HILOG_WARN(LOG_CORE, "%{public}s", stream_.str().c_str());
370            break;
371        default:
372            UNREACHABLE();
373    }
374}
375#endif
376
377void StderrLogger::LogLineInternal(Level level, Component component, const std::string &str)
378{
379    std::string prefix = GetPrefix(level, component);
380    std::cerr << prefix << str << std::endl << std::flush;
381}
382
383void FastFileLogger::SyncOutputResource()
384{
385    stream_ << std::flush;
386}
387
388}  // namespace panda
389