1 /*
2  * SPDX-FileCopyrightText: 2017-2017 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 
8 #include "log.h"
9 #include <chrono>
10 #include <mutex>
11 #include <type_traits>
12 #include <unordered_set>
13 #include <fmt/format.h>
14 #if FMT_VERSION >= 50300
15 #include <fmt/chrono.h>
16 #endif
17 #include "fs.h"
18 #include "stringutils.h"
19 
20 namespace fcitx {
21 
22 namespace {
23 
24 FCITX_DEFINE_LOG_CATEGORY(defaultCategory, "default");
25 
26 static std::ostream *defaultLogStream = &std::cerr;
27 
validateLogLevel(std::underlying_type_t<LogLevel> l)28 bool validateLogLevel(std::underlying_type_t<LogLevel> l) {
29     return (l >= 0 &&
30             l <= std::underlying_type_t<LogLevel>(LogLevel::LastLogLevel));
31 }
32 
33 class LogRegistry {
34 public:
instance()35     static LogRegistry &instance() {
36         static LogRegistry instance_;
37         return instance_;
38     }
39 
registerCategory(LogCategory & category)40     void registerCategory(LogCategory &category) {
41         std::lock_guard<std::mutex> lock(mutex_);
42         if (!categories_.count(&category)) {
43             categories_.insert(&category);
44             applyRule(&category);
45         }
46     }
47 
unregisterCategory(LogCategory & category)48     void unregisterCategory(LogCategory &category) {
49         std::lock_guard<std::mutex> lock(mutex_);
50         categories_.erase(&category);
51     }
52 
setLogRule(const std::string & ruleString)53     void setLogRule(const std::string &ruleString) {
54         std::lock_guard<std::mutex> lock(mutex_);
55 
56         rules_.clear();
57         auto rules = stringutils::split(ruleString, ",");
58         rules_.reserve(rules.size());
59         for (const auto &rule : rules) {
60             auto ruleItem = stringutils::split(rule, "=");
61             if (ruleItem.size() != 2) {
62                 continue;
63             }
64             auto &name = ruleItem[0];
65             try {
66                 auto level = std::stoi(ruleItem[1]);
67                 if (validateLogLevel(level)) {
68                     rules_.emplace_back(name, static_cast<LogLevel>(level));
69                 }
70             } catch (const std::exception &) {
71                 continue;
72             }
73         }
74 
75         for (auto *category : categories_) {
76             applyRule(category);
77         }
78     }
79 
applyRule(LogCategory * category)80     void applyRule(LogCategory *category) {
81         category->resetLogLevel();
82         for (auto &rule : rules_) {
83             if (rule.first == "*" || rule.first == category->name()) {
84                 category->setLogLevel(rule.second);
85             }
86         }
87     }
88 
89 private:
90     std::unordered_set<LogCategory *> categories_;
91     std::vector<std::pair<std::string, LogLevel>> rules_;
92     std::mutex mutex_;
93 };
94 } // namespace
95 
96 class LogCategoryPrivate {
97 public:
LogCategoryPrivate(const char * name,LogLevel level)98     LogCategoryPrivate(const char *name, LogLevel level)
99         : name_(name), level_(level), defaultLevel_(level) {}
100 
101     std::string name_;
102     LogLevel level_;
103     LogLevel defaultLevel_;
104 };
105 
LogCategory(const char * name,LogLevel level)106 LogCategory::LogCategory(const char *name, LogLevel level)
107     : d_ptr(std::make_unique<LogCategoryPrivate>(name, level)) {
108     LogRegistry::instance().registerCategory(*this);
109 }
110 
~LogCategory()111 LogCategory::~LogCategory() {
112     LogRegistry::instance().unregisterCategory(*this);
113 }
114 
checkLogLevel(LogLevel l) const115 bool LogCategory::checkLogLevel(LogLevel l) const {
116     FCITX_D();
117     return l != LogLevel::NoLog &&
118            static_cast<std::underlying_type_t<LogLevel>>(l) <=
119                static_cast<std::underlying_type_t<LogLevel>>(d->level_);
120 }
121 
resetLogLevel()122 void LogCategory::resetLogLevel() {
123     FCITX_D();
124     d->level_ = d->defaultLevel_;
125 }
126 
setLogLevel(std::underlying_type_t<LogLevel> l)127 void LogCategory::setLogLevel(std::underlying_type_t<LogLevel> l) {
128     if (validateLogLevel(l)) {
129         setLogLevel(static_cast<LogLevel>(l));
130     }
131 }
132 
setLogLevel(LogLevel l)133 void LogCategory::setLogLevel(LogLevel l) {
134     FCITX_D();
135     d->level_ = l;
136 }
137 
logLevel() const138 LogLevel LogCategory::logLevel() const {
139     FCITX_D();
140     return d->level_;
141 }
142 
name() const143 const std::string &LogCategory::name() const {
144     FCITX_D();
145     return d->name_;
146 }
147 
fatalWrapper(LogLevel level) const148 bool LogCategory::fatalWrapper(LogLevel level) const {
149     // If level if fatal and we don't write fatal log, abort right away.
150     bool needLog = checkLogLevel(level);
151     if (level == LogLevel::Fatal && !needLog) {
152         std::abort();
153     }
154     return needLog;
155 }
156 
fatalWrapper2(LogLevel level)157 bool LogCategory::fatalWrapper2(LogLevel level) {
158     if (level == LogLevel::Fatal) {
159         std::abort();
160     }
161     return false;
162 }
163 
defaultCategory()164 const LogCategory &Log::defaultCategory() { return fcitx::defaultCategory(); }
165 
setLogRule(const std::string & ruleString)166 void Log::setLogRule(const std::string &ruleString) {
167     LogRegistry::instance().setLogRule(ruleString);
168 }
169 
setLogStream(std::ostream & stream)170 void Log::setLogStream(std::ostream &stream) { defaultLogStream = &stream; }
171 
logStream()172 std::ostream &Log::logStream() { return *defaultLogStream; }
173 
LogMessageBuilder(std::ostream & out,LogLevel l,const char * filename,int lineNumber)174 LogMessageBuilder::LogMessageBuilder(std::ostream &out, LogLevel l,
175                                      const char *filename, int lineNumber)
176     : out_(out) {
177     switch (l) {
178     case LogLevel::Fatal:
179         out_ << "F";
180         break;
181     case LogLevel::Debug:
182         out_ << "D";
183         break;
184     case LogLevel::Info:
185         out_ << "I";
186         break;
187     case LogLevel::Warn:
188         out_ << "W";
189         break;
190     case LogLevel::Error:
191         out_ << "E";
192         break;
193     default:
194         break;
195     }
196 
197 #if FMT_VERSION >= 50300
198     auto now = std::chrono::system_clock::now();
199     auto floor = std::chrono::floor<std::chrono::seconds>(now);
200     auto micro =
201         std::chrono::duration_cast<std::chrono::microseconds>(now - floor);
202     auto t = fmt::localtime(std::chrono::system_clock::to_time_t(now));
203     out_ << fmt::format("{:%F %T}.{:06d}", t, micro.count()) << " ";
204 #endif
205     out_ << filename << ":" << lineNumber << "] ";
206 }
207 
~LogMessageBuilder()208 LogMessageBuilder::~LogMessageBuilder() { out_ << std::endl; }
209 } // namespace fcitx
210