AppInstallerLogging.h (8178B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #pragma once 4 #include <AppInstallerLanguageUtilities.h> 5 #include <chrono> 6 #include <filesystem> 7 #include <memory> 8 #include <sstream> 9 #include <string> 10 #include <string_view> 11 #include <type_traits> 12 #include <vector> 13 14 #define AICLI_LOG_DIRECT(_logger_,_channel_,_level_,_outstream_) \ 15 do { \ 16 auto _aicli_log_channel = AppInstaller::Logging::Channel:: _channel_; \ 17 auto _aicli_log_level = AppInstaller::Logging::Level:: _level_; \ 18 auto& _aicli_log_log = _logger_; \ 19 if (_aicli_log_log.IsEnabled(_aicli_log_channel, _aicli_log_level)) \ 20 { \ 21 AppInstaller::Logging::LoggingStream _aicli_log_strstr; \ 22 _aicli_log_strstr _outstream_; \ 23 _aicli_log_log.Write(_aicli_log_channel, _aicli_log_level, _aicli_log_strstr.str()); \ 24 } \ 25 } while (0, 0) 26 27 #define AICLI_LOG(_channel_,_level_,_outstream_) AICLI_LOG_DIRECT(AppInstaller::Logging::Log(),_channel_,_level_,_outstream_) 28 29 // Consider using this macro when the string might be larger than 4K. 30 // The normal macro has some buffering that occurs; it can cut off larger strings and is slower. 31 #define AICLI_LOG_LARGE_STRING(_channel_,_level_,_headerStream_,_largeString_) \ 32 do { \ 33 auto _aicli_log_channel = AppInstaller::Logging::Channel:: _channel_; \ 34 auto _aicli_log_level = AppInstaller::Logging::Level:: _level_; \ 35 auto& _aicli_log_log = AppInstaller::Logging::Log(); \ 36 if (_aicli_log_log.IsEnabled(_aicli_log_channel, _aicli_log_level)) \ 37 { \ 38 AppInstaller::Logging::LoggingStream _aicli_log_strstr; \ 39 _aicli_log_strstr _headerStream_; \ 40 _aicli_log_log.Write(_aicli_log_channel, _aicli_log_level, _aicli_log_strstr.str()); \ 41 _aicli_log_log.WriteDirect(_aicli_log_channel, _aicli_log_level, _largeString_); \ 42 } \ 43 } while (0, 0) 44 45 namespace AppInstaller::Logging 46 { 47 // The channel that the log is from. 48 // Channels enable large groups of logs to be enabled or disabled together. 49 enum class Channel : uint32_t 50 { 51 Fail = 0x1, 52 CLI = 0x2, 53 SQL = 0x4, 54 Repo = 0x8, 55 YAML = 0x10, 56 Core = 0x20, 57 Test = 0x40, 58 Config = 0x80, 59 Workflow = 0x100, 60 None = 0, 61 All = 0xFFFFFFFF, 62 Defaults = All & ~(SQL | Workflow), 63 }; 64 65 DEFINE_ENUM_FLAG_OPERATORS(Channel); 66 67 // Gets the channel's name as a string. 68 std::string_view GetChannelName(Channel channel); 69 70 // Gets the channel from it's name. 71 Channel GetChannelFromName(std::string_view channel); 72 73 // Gets the maximum channel name length in characters. 74 size_t GetMaxChannelNameLength(); 75 76 // The level of the log. 77 enum class Level 78 { 79 Verbose, 80 Info, 81 Warning, 82 Error, 83 Crit, 84 }; 85 86 // The interface that a log target must implement. 87 struct ILogger 88 { 89 virtual ~ILogger() = default; 90 91 // Gets the name of the logger for internal use. 92 virtual std::string GetName() const = 0; 93 94 // Informs the logger of the given log. 95 virtual void Write(Channel channel, Level level, std::string_view message) noexcept = 0; 96 97 // Informs the logger of the given log with the intention that no buffering occurs (in winget code). 98 virtual void WriteDirect(Channel channel, Level level, std::string_view message) noexcept = 0; 99 }; 100 101 // This type contains the set of loggers that diagnostic logging will be sent to. 102 // Each binary that leverages it must configure any loggers and filters to their 103 // desired level, as nothing is enabled by default. 104 struct DiagnosticLogger 105 { 106 DiagnosticLogger() = default; 107 108 ~DiagnosticLogger() = default; 109 110 DiagnosticLogger(const DiagnosticLogger&) = delete; 111 DiagnosticLogger& operator=(const DiagnosticLogger&) = delete; 112 113 DiagnosticLogger(DiagnosticLogger&&) = delete; 114 DiagnosticLogger& operator=(DiagnosticLogger&&) = delete; 115 116 // Gets the singleton instance of this type. 117 static DiagnosticLogger& GetInstance(); 118 119 // NOTE: The logger management functionality is *SINGLE THREAD SAFE*. 120 // This includes with logging itself. 121 // As it is not expected that adding/removing loggers is an 122 // extremely frequent operation, no care has been made to protect 123 // it from modifying loggers while logging may be occurring. 124 125 // Adds a logger to the active set. 126 void AddLogger(std::unique_ptr<ILogger>&& logger); 127 128 // Determines if a logger with the given name is present. 129 bool ContainsLogger(const std::string& name); 130 131 // Removes a logger from the active set, returning it. 132 std::unique_ptr<ILogger> RemoveLogger(const std::string& name); 133 134 // Removes all loggers. 135 void RemoveAllLoggers(); 136 137 // Enables the given channel(s), in addition to the currently enabled channels. 138 void EnableChannel(Channel channel); 139 140 // The given channel mask will become the only enabled channels. 141 void SetEnabledChannels(Channel channel); 142 143 // Disables the given channel. 144 void DisableChannel(Channel channel); 145 146 // Sets the enabled level. 147 // All levels above this level will be enabled. 148 // For example; SetLevel(Verbose) will enable all logs. 149 void SetLevel(Level level); 150 151 // Gets the enabled level. 152 Level GetLevel() const; 153 154 // Checks whether a given channel and level are enabled. 155 bool IsEnabled(Channel channel, Level level) const; 156 157 // Writes a log line, if the given channel and level are enabled. 158 void Write(Channel channel, Level level, std::string_view message); 159 160 // Writes a log line, if the given channel and level are enabled. 161 // Use to make large logs more efficient by writing directly to the output streams. 162 void WriteDirect(Channel channel, Level level, std::string_view message); 163 164 private: 165 166 std::vector<std::unique_ptr<ILogger>> m_loggers; 167 Channel m_enabledChannels = Channel::None; 168 Level m_enabledLevel = Level::Info; 169 }; 170 171 DiagnosticLogger& Log(); 172 173 // Calls the various stream format functions to produce an 8 character hexadecimal output. 174 std::ostream& SetHRFormat(std::ostream& out); 175 176 // This type allows us to override the default behavior of output operators for logging. 177 struct LoggingStream 178 { 179 // Force use of the UTF-8 string from a file path. 180 // This should not be necessary when we move to C++20 and convert to using u8string. 181 friend AppInstaller::Logging::LoggingStream& operator<<(AppInstaller::Logging::LoggingStream& out, const std::filesystem::path& path) 182 { 183 out.m_out << path.u8string(); 184 return out; 185 } 186 187 // Enums 188 template <typename T> 189 friend std::enable_if_t<std::is_enum_v<std::decay_t<T>>, AppInstaller::Logging::LoggingStream&> 190 operator<<(AppInstaller::Logging::LoggingStream& out, T t) 191 { 192 out.m_out << ToIntegral(t); 193 return out; 194 } 195 196 // Everything else. 197 template <typename T> 198 friend std::enable_if_t<!std::disjunction_v<std::is_same<std::decay_t<T>, std::filesystem::path>, std::is_enum<std::decay_t<T>>>, AppInstaller::Logging::LoggingStream&> 199 operator<<(AppInstaller::Logging::LoggingStream& out, T&& t) 200 { 201 out.m_out << std::forward<T>(t); 202 return out; 203 } 204 205 std::string str() const { return m_out.str(); } 206 207 private: 208 std::stringstream m_out; 209 }; 210 } 211 212 namespace std 213 { 214 std::ostream& operator<<(std::ostream& out, const std::chrono::system_clock::time_point& time); 215 std::ostream& operator<<(std::ostream& out, const GUID& guid); 216 }