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