1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "log.h"
10
11 #include "CompileInfo.h"
12 #include "ServiceBroker.h"
13 #include "filesystem/File.h"
14 #include "guilib/LocalizeStrings.h"
15 #if defined(TARGET_ANDROID)
16 #include "platform/android/utils/AndroidInterfaceForCLog.h"
17 #elif defined(TARGET_DARWIN)
18 #include "platform/darwin/utils/DarwinInterfaceForCLog.h"
19 #elif defined(TARGET_WINDOWS) || defined(TARGET_WIN10)
20 #include "platform/win32/utils/Win32InterfaceForCLog.h"
21 #else
22 #include "platform/posix/utils/PosixInterfaceForCLog.h"
23 #endif
24 #include "settings/SettingUtils.h"
25 #include "settings/Settings.h"
26 #include "settings/SettingsComponent.h"
27 #include "settings/lib/Setting.h"
28 #include "settings/lib/SettingsManager.h"
29 #include "utils/URIUtils.h"
30
31 #include <cstring>
32 #include <set>
33
34 #include <spdlog/sinks/basic_file_sink.h>
35 #include <spdlog/sinks/dist_sink.h>
36 #include <spdlog/sinks/dup_filter_sink.h>
37
38 static constexpr unsigned char Utf8Bom[3] = {0xEF, 0xBB, 0xBF};
39 static const std::string LogFileExtension = ".log";
40 static const std::string LogPattern = "%Y-%m-%d %T.%e T:%-5t %7l <%n>: %v";
41
CLog()42 CLog::CLog()
43 : m_platform(IPlatformLog::CreatePlatformLog()),
44 m_sinks(std::make_shared<spdlog::sinks::dist_sink_mt>()),
45 m_defaultLogger(CreateLogger("general")),
46 m_logLevel(LOG_LEVEL_DEBUG),
47 m_componentLogEnabled(false),
48 m_componentLogLevels(0)
49 {
50 // add platform-specific debug sinks
51 m_platform->AddSinks(m_sinks);
52
53 // register the default logger with spdlog
54 spdlog::set_default_logger(m_defaultLogger);
55
56 // set the formatting pattern globally
57 spdlog::set_pattern(LogPattern);
58
59 // flush on debug logs
60 spdlog::flush_on(spdlog::level::debug);
61
62 // set the log level
63 SetLogLevel(m_logLevel);
64 }
65
OnSettingsLoaded()66 void CLog::OnSettingsLoaded()
67 {
68 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
69 m_componentLogEnabled = settings->GetBool(CSettings::SETTING_DEBUG_EXTRALOGGING);
70 SetComponentLogLevel(settings->GetList(CSettings::SETTING_DEBUG_SETEXTRALOGLEVEL));
71 }
72
OnSettingChanged(const std::shared_ptr<const CSetting> & setting)73 void CLog::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
74 {
75 if (setting == NULL)
76 return;
77
78 const std::string& settingId = setting->GetId();
79 if (settingId == CSettings::SETTING_DEBUG_EXTRALOGGING)
80 m_componentLogEnabled = std::static_pointer_cast<const CSettingBool>(setting)->GetValue();
81 else if (settingId == CSettings::SETTING_DEBUG_SETEXTRALOGLEVEL)
82 SetComponentLogLevel(
83 CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(setting)));
84 }
85
Initialize(const std::string & path)86 void CLog::Initialize(const std::string& path)
87 {
88 if (m_fileSink != nullptr)
89 return;
90
91 // register setting callbacks
92 auto settingsManager =
93 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSettingsManager();
94 settingsManager->RegisterSettingOptionsFiller("loggingcomponents",
95 SettingOptionsLoggingComponentsFiller);
96 settingsManager->RegisterSettingsHandler(this);
97 std::set<std::string> settingSet;
98 settingSet.insert(CSettings::SETTING_DEBUG_EXTRALOGGING);
99 settingSet.insert(CSettings::SETTING_DEBUG_SETEXTRALOGLEVEL);
100 settingsManager->RegisterCallback(this, settingSet);
101
102 if (path.empty())
103 return;
104
105 // put together the path to the log file(s)
106 std::string appName = CCompileInfo::GetAppName();
107 StringUtils::ToLower(appName);
108 const std::string filePathBase = URIUtils::AddFileToFolder(path, appName);
109 const std::string filePath = filePathBase + LogFileExtension;
110 const std::string oldFilePath = filePathBase + ".old" + LogFileExtension;
111
112 // handle old.log by deleting an existing old.log and renaming the last log to old.log
113 XFILE::CFile::Delete(oldFilePath);
114 XFILE::CFile::Rename(filePath, oldFilePath);
115
116 // write UTF-8 BOM
117 {
118 XFILE::CFile file;
119 if (file.OpenForWrite(filePath, true))
120 file.Write(Utf8Bom, sizeof(Utf8Bom));
121 }
122
123 // create the file sink within a duplicate filter sink
124 auto duplicateFilterSink =
125 std::make_shared<spdlog::sinks::dup_filter_sink_st>(std::chrono::seconds(10));
126 auto basicFileSink = std::make_shared<spdlog::sinks::basic_file_sink_st>(
127 m_platform->GetLogFilename(filePath), false);
128 basicFileSink->set_pattern(LogPattern);
129 duplicateFilterSink->add_sink(basicFileSink);
130 m_fileSink = duplicateFilterSink;
131
132 // add it to the existing sinks
133 m_sinks->add_sink(m_fileSink);
134 }
135
Uninitialize()136 void CLog::Uninitialize()
137 {
138 if (m_fileSink == nullptr)
139 return;
140
141 // unregister setting callbacks
142 auto settingsManager =
143 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSettingsManager();
144 settingsManager->UnregisterSettingOptionsFiller("loggingcomponents");
145 settingsManager->UnregisterSettingsHandler(this);
146 settingsManager->UnregisterCallback(this);
147
148 // flush all loggers
149 spdlog::apply_all([](const std::shared_ptr<spdlog::logger>& logger) { logger->flush(); });
150
151 // flush the file sink
152 m_fileSink->flush();
153
154 // remove and destroy the file sink
155 m_sinks->remove_sink(m_fileSink);
156 m_fileSink.reset();
157 }
158
SetLogLevel(int level)159 void CLog::SetLogLevel(int level)
160 {
161 if (level < LOG_LEVEL_NONE || level > LOG_LEVEL_MAX)
162 return;
163
164 m_logLevel = level;
165
166 auto spdLevel = spdlog::level::info;
167 if (level <= LOG_LEVEL_NONE)
168 spdLevel = spdlog::level::off;
169 else if (level >= LOG_LEVEL_DEBUG)
170 spdLevel = spdlog::level::trace;
171
172 if (m_defaultLogger != nullptr && m_defaultLogger->level() == spdLevel)
173 return;
174
175 spdlog::set_level(spdLevel);
176 FormatAndLogInternal(spdlog::level::info, "Log level changed to \"{}\"",
177 spdlog::level::to_string_view(spdLevel));
178 }
179
IsLogLevelLogged(int loglevel)180 bool CLog::IsLogLevelLogged(int loglevel)
181 {
182 if (m_logLevel >= LOG_LEVEL_DEBUG)
183 return true;
184 if (m_logLevel <= LOG_LEVEL_NONE)
185 return false;
186
187 return (loglevel & LOGMASK) >= LOGINFO;
188 }
189
CanLogComponent(uint32_t component) const190 bool CLog::CanLogComponent(uint32_t component) const
191 {
192 if (!m_componentLogEnabled || component == 0)
193 return false;
194
195 return ((m_componentLogLevels & component) == component);
196 }
197
SettingOptionsLoggingComponentsFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)198 void CLog::SettingOptionsLoggingComponentsFiller(const SettingConstPtr& setting,
199 std::vector<IntegerSettingOption>& list,
200 int& current,
201 void* data)
202 {
203 list.emplace_back(g_localizeStrings.Get(669), LOGSAMBA);
204 list.emplace_back(g_localizeStrings.Get(670), LOGCURL);
205 list.emplace_back(g_localizeStrings.Get(672), LOGFFMPEG);
206 list.emplace_back(g_localizeStrings.Get(675), LOGJSONRPC);
207 list.emplace_back(g_localizeStrings.Get(676), LOGAUDIO);
208 list.emplace_back(g_localizeStrings.Get(680), LOGVIDEO);
209 list.emplace_back(g_localizeStrings.Get(683), LOGAVTIMING);
210 list.emplace_back(g_localizeStrings.Get(684), LOGWINDOWING);
211 list.emplace_back(g_localizeStrings.Get(685), LOGPVR);
212 list.emplace_back(g_localizeStrings.Get(686), LOGEPG);
213 list.emplace_back(g_localizeStrings.Get(39117), LOGANNOUNCE);
214 #ifdef HAS_DBUS
215 list.emplace_back(g_localizeStrings.Get(674), LOGDBUS);
216 #endif
217 #ifdef HAS_WEB_SERVER
218 list.emplace_back(g_localizeStrings.Get(681), LOGWEBSERVER);
219 #endif
220 #ifdef HAS_AIRTUNES
221 list.emplace_back(g_localizeStrings.Get(677), LOGAIRTUNES);
222 #endif
223 #ifdef HAS_UPNP
224 list.emplace_back(g_localizeStrings.Get(678), LOGUPNP);
225 #endif
226 #ifdef HAVE_LIBCEC
227 list.emplace_back(g_localizeStrings.Get(679), LOGCEC);
228 #endif
229 list.emplace_back(g_localizeStrings.Get(682), LOGDATABASE);
230 }
231
GetLogger(const std::string & loggerName)232 Logger CLog::GetLogger(const std::string& loggerName)
233 {
234 auto logger = spdlog::get(loggerName);
235 if (logger == nullptr)
236 logger = CreateLogger(loggerName);
237
238 return logger;
239 }
240
GetInstance()241 CLog& CLog::GetInstance()
242 {
243 return CServiceBroker::GetLogging();
244 }
245
MapLogLevel(int level)246 spdlog::level::level_enum CLog::MapLogLevel(int level)
247 {
248 switch (level)
249 {
250 case LOGDEBUG:
251 return spdlog::level::debug;
252 case LOGINFO:
253 return spdlog::level::info;
254 case LOGWARNING:
255 return spdlog::level::warn;
256 case LOGERROR:
257 return spdlog::level::err;
258 case LOGFATAL:
259 return spdlog::level::critical;
260 case LOGNONE:
261 return spdlog::level::off;
262
263 default:
264 break;
265 }
266
267 return spdlog::level::info;
268 }
269
CreateLogger(const std::string & loggerName)270 Logger CLog::CreateLogger(const std::string& loggerName)
271 {
272 // create the logger
273 auto logger = std::make_shared<spdlog::logger>(loggerName, m_sinks);
274
275 // initialize the logger
276 spdlog::initialize_logger(logger);
277
278 return logger;
279 }
280
SetComponentLogLevel(const std::vector<CVariant> & components)281 void CLog::SetComponentLogLevel(const std::vector<CVariant>& components)
282 {
283 m_componentLogLevels = 0;
284 for (const auto& component : components)
285 {
286 if (!component.isInteger())
287 continue;
288
289 m_componentLogLevels |= static_cast<uint32_t>(component.asInteger());
290 }
291 }
292