1 /** 2 Handles all the console output of the Dub package manager, by providing useful 3 methods for handling colored text. The module also disables colors when stdout 4 and stderr are not a TTY in order to avoid ASCII escape sequences in piped 5 output. The module can autodetect and configure itself in this regard by 6 calling initLogging() at the beginning of the program. But, whether to color 7 text or not can also be set manually with setLoggingColorsEnabled(bool). 8 9 The output for the log levels error, warn and info is formatted like this: 10 11 " <tag> <text>" 12 '----------' 13 fixed width 14 15 the "tag" part can be colored (most oftenly will be) and always has a fixed 16 width, which is defined as a const at the beginning of this module. 17 18 The output for the log levels debug and diagnostic will be just the plain 19 string. 20 21 There are some default tag string and color values for some logging levels: 22 - warn: "Warning", yellow bold 23 - error: "Error", red bold 24 25 Actually, for error and warn levels, the tag color is fixed to the ones listed 26 above. 27 28 Also, the default tag string for the info level is "" (the empty string) and 29 the default color is white (usually it's manually set when calling logInfo 30 with the wanted tag string, but this allows to just logInfo("text") without 31 having to worry about the tag if it's not needed). 32 33 Usage: 34 After initializing the logging module with initLogging(), the functions 35 logDebug(..), logDiagnostic(..), logInfo(..), logWarning(..) and logError(..) 36 can be used to print log messages. Whether the messages are printed on stdout 37 or stderr depends on the log level (warning and error go to stderr). 38 The log(..) function can also be used. Check the signature and documentation 39 of the functions for more information. 40 41 The minimum log level to print can be configured using setLogLevel(..), 42 and whether to color outputted text or not can be set with 43 setLoggingColorsEnabled(..) 44 45 The color(str, color) function can be used to color text within a log 46 message, for instance like this: 47 48 logInfo("Tag", Color.green, "My %s message", "colored".color(Color.red)) 49 50 Copyright: © 2018 Giacomo De Lazzari 51 License: Subject to the terms of the MIT license, as written in the included LICENSE file. 52 Authors: Giacomo De Lazzari 53 */ 54 55 module dub.internal.logging; 56 57 import std.stdio; 58 import std.array; 59 import std.format; 60 import std.string; 61 62 import dub.internal.colorize : fg, mode; 63 64 /** 65 An enum listing possible colors for terminal output, useful to set the color 66 of a tag. Re-exported from d-colorize in dub.internal.colorize. See the enum 67 definition there for a list of possible values. 68 */ 69 public alias Color = fg; 70 71 /** 72 An enum listing possible text "modes" for terminal output, useful to set the 73 text to bold, underline, blinking, etc... 74 Re-exported from d-colorize in dub.internal.colorize. See the enum definition 75 there for a list of possible values. 76 */ 77 public alias Mode = mode; 78 79 /// Defines the current width of logging tags for justifying in chars. 80 /// Can be manipulated through push and pop. 81 struct TagWidth { 82 import core.atomic; 83 84 private shared int value = 12; 85 private shared int index; 86 private shared int[16] stack; 87 88 /// Gets the tag width in chars 89 public int get() { 90 return value; 91 } 92 93 /// Changes the tag width for all following logging calls, until $(LREF pop) is called. 94 public void push(int width) { 95 int currentIndex = index; 96 index.atomicOp!"+="(1); 97 stack[currentIndex] = value; 98 assert(index < stack.length, "too many TagWidth.push without pop"); 99 value = width; 100 } 101 102 /// Reverts the last $(LREF push) call. 103 public void pop() { 104 assert(index > 0); 105 value = stack[index.atomicOp!"-="(1)]; 106 } 107 } 108 109 /// The global tag width instance used for logging. 110 public __gshared TagWidth tagWidth; 111 112 /// Possible log levels supported 113 enum LogLevel { 114 debug_, 115 diagnostic, 116 info, 117 warn, 118 error, 119 none 120 } 121 122 // The current minimum log level to be printed 123 private shared LogLevel _minLevel = LogLevel.info; 124 125 /* 126 Whether to print text with colors or not, defaults to true but will be set 127 to false in initLogging() if stdout or stderr are not a TTY (which means the 128 output is probably being piped and we don't want ASCII escape chars in it) 129 */ 130 private shared bool _printColors = true; 131 132 /// Ditto 133 public bool hasColors () @trusted nothrow @nogc { return _printColors; } 134 135 // isatty() is used in initLogging() to detect whether or not we are on a TTY 136 extern (C) int isatty(int); 137 138 /** 139 This function must be called at the beginning for the program, before any 140 logging occurs. It will detect whether or not stdout/stderr are a console/TTY 141 and will consequently disable colored output if needed. Also, if a NO_COLOR 142 environment variable is defined, colors are disabled (https://no-color.org/). 143 144 Forgetting to call the function will result in ASCII escape sequences in the 145 piped output, probably an undesirable thing. 146 */ 147 void initLogging() 148 { 149 import std.process : environment; 150 import core.stdc.stdio; 151 152 _printColors = environment.get("NO_COLOR") == ""; 153 version (Windows) 154 { 155 version (CRuntime_DigitalMars) 156 { 157 if (!isatty(core.stdc.stdio.stdout._file) || 158 !isatty(core.stdc.stdio.stderr._file)) 159 _printColors = false; 160 } 161 else version (CRuntime_Microsoft) 162 { 163 if (!isatty(fileno(core.stdc.stdio.stdout)) || 164 !isatty(fileno(core.stdc.stdio.stderr))) 165 _printColors = false; 166 } 167 else 168 _printColors = false; 169 } 170 else version (Posix) 171 { 172 import core.sys.posix.unistd; 173 174 if (!isatty(STDERR_FILENO) || !isatty(STDOUT_FILENO)) 175 _printColors = false; 176 } 177 } 178 179 /// Sets the minimum log level to be printed 180 void setLogLevel(LogLevel level) nothrow 181 { 182 _minLevel = level; 183 } 184 185 /// Gets the minimum log level to be printed 186 LogLevel getLogLevel() 187 { 188 return _minLevel; 189 } 190 191 /// Set whether to print colors or not 192 void setLoggingColorsEnabled(bool enabled) 193 { 194 _printColors = enabled; 195 } 196 197 /** 198 Shorthand function to log a message with debug/diagnostic level, no tag string 199 or tag color required (since there will be no tag). 200 201 Params: 202 level = The log level for the logged message 203 fmt = See http://dlang.org/phobos/std_format.html#format-string 204 */ 205 void logDebug(T...)(string fmt, lazy T args) nothrow 206 { 207 log(LogLevel.debug_, false, "", Color.init, fmt, args); 208 } 209 210 /// ditto 211 void logDiagnostic(T...)(string fmt, lazy T args) nothrow 212 { 213 log(LogLevel.diagnostic, false, "", Color.init, fmt, args); 214 } 215 216 /** 217 Shorthand function to log a message with info level, with custom tag string 218 and tag color. 219 220 Params: 221 tag = The string the tag at the beginning of the line should contain 222 tagColor = The color the tag string should have 223 level = The log level for the logged message 224 fmt = See http://dlang.org/phobos/std_format.html#format-string 225 */ 226 void logInfo(T...)(string tag, Color tagColor, string fmt, lazy T args) nothrow 227 { 228 log(LogLevel.info, false, tag, tagColor, fmt, args); 229 } 230 231 /** 232 Shorthand function to log a message with info level, this version prints an 233 empty tag automatically (which is different from not having a tag - in this 234 case there will be an identation of tagWidth chars on the left anyway). 235 236 Params: 237 level = The log level for the logged message 238 fmt = See http://dlang.org/phobos/std_format.html#format-string 239 */ 240 void logInfo(T...)(string fmt, lazy T args) nothrow if (!is(T[0] : Color)) 241 { 242 log(LogLevel.info, false, "", Color.init, fmt, args); 243 } 244 245 /** 246 Shorthand function to log a message with info level, this version doesn't 247 print a tag at all, it effectively just prints the given string. 248 249 Params: 250 level = The log level for the logged message 251 fmt = See http://dlang.org/phobos/std_format.html#format-string 252 */ 253 void logInfoNoTag(T...)(string fmt, lazy T args) nothrow if (!is(T[0] : Color)) 254 { 255 log(LogLevel.info, true, "", Color.init, fmt, args); 256 } 257 258 /** 259 Shorthand function to log a message with warning level, with custom tag string. 260 The tag color is fixed to yellow. 261 262 Params: 263 tag = The string the tag at the beginning of the line should contain 264 level = The log level for the logged message 265 fmt = See http://dlang.org/phobos/std_format.html#format-string 266 */ 267 void logWarnTag(T...)(string tag, string fmt, lazy T args) nothrow 268 { 269 log(LogLevel.warn, false, tag, Color.yellow, fmt, args); 270 } 271 272 /** 273 Shorthand function to log a message with warning level, using the default 274 tag "Warning". The tag color is also fixed to yellow. 275 276 Params: 277 level = The log level for the logged message 278 fmt = See http://dlang.org/phobos/std_format.html#format-string 279 */ 280 void logWarn(T...)(string fmt, lazy T args) nothrow 281 { 282 log(LogLevel.warn, false, "Warning", Color.yellow, fmt, args); 283 } 284 285 /** 286 Shorthand function to log a message with error level, with custom tag string. 287 The tag color is fixed to red. 288 289 Params: 290 tag = The string the tag at the beginning of the line should contain 291 level = The log level for the logged message 292 fmt = See http://dlang.org/phobos/std_format.html#format-string 293 */ 294 void logErrorTag(T...)(string tag, string fmt, lazy T args) nothrow 295 { 296 log(LogLevel.error, false, tag, Color.red, fmt, args); 297 } 298 299 /** 300 Shorthand function to log a message with error level, using the default 301 tag "Error". The tag color is also fixed to red. 302 303 Params: 304 level = The log level for the logged message 305 fmt = See http://dlang.org/phobos/std_format.html#format-string 306 */ 307 void logError(T...)(string fmt, lazy T args) nothrow 308 { 309 log(LogLevel.error, false, "Error", Color.red, fmt, args); 310 } 311 312 /** 313 Log a message with the specified log level and with the specified tag string 314 and color. If the log level is debug or diagnostic, the tag is not printed 315 thus the tag string and tag color will be ignored. If the log level is error 316 or warning, the tag will be in bold text. Also the tag can be disabled (for 317 any log level) by passing true as the second argument. 318 319 Params: 320 level = The log level for the logged message 321 disableTag = Setting this to true disables the tag, no matter what 322 tag = The string the tag at the beginning of the line should contain 323 tagColor = The color the tag string should have 324 fmt = See http://dlang.org/phobos/std_format.html#format-string 325 */ 326 void log(T...)( 327 LogLevel level, 328 bool disableTag, 329 string tag, 330 Color tagColor, 331 string fmt, 332 lazy T args 333 ) nothrow 334 { 335 if (level < _minLevel) 336 return; 337 338 auto hasTag = true; 339 if (level <= LogLevel.diagnostic) 340 hasTag = false; 341 if (disableTag) 342 hasTag = false; 343 344 auto boldTag = false; 345 if (level >= LogLevel.warn) 346 boldTag = true; 347 348 try 349 { 350 string result = format(fmt, args); 351 352 if (hasTag) 353 result = tag.rightJustify(tagWidth.get, ' ').color(tagColor, boldTag ? Mode.bold : Mode.init) ~ " " ~ result; 354 355 import dub.internal.colorize : cwrite; 356 357 File output = (level <= LogLevel.info) ? stdout : stderr; 358 359 if (output.isOpen) 360 { 361 output.cwrite(result, "\n"); 362 output.flush(); 363 } 364 } 365 catch (Exception e) 366 { 367 debug assert(false, e.msg); 368 } 369 } 370 371 /** 372 Colors the specified string with the specified color. The function is used to 373 print colored text within a log message. The function also checks whether 374 color output is enabled or disabled (when not outputting to a TTY) and, in the 375 last case, just returns the plain string. This allows to use it like so: 376 377 logInfo("Tag", Color.green, "My %s log message", "colored".color(Color.red)); 378 379 without worring whether or not colored output is enabled or not. 380 381 Also a mode can be specified, such as bold/underline/etc... 382 383 Params: 384 str = The string to color 385 color = The color to apply 386 mode = An optional mode, such as bold/underline/etc... 387 */ 388 string color(const string str, const Color c, const Mode m = Mode.init) 389 { 390 import dub.internal.colorize; 391 392 if (_printColors) 393 return dub.internal.colorize.color(str, c, bg.init, m); 394 else 395 return str; 396 } 397 398 /** 399 This function is the same as the above one, but just accepts a mode. 400 It's useful, for instance, when outputting bold text without changing the 401 color. 402 403 Params: 404 str = The string to color 405 mode = The mode, such as bold/underline/etc... 406 */ 407 string color(const string str, const Mode m = Mode.init) 408 { 409 import dub.internal.colorize; 410 411 if (_printColors) 412 return dub.internal.colorize.color(str, fg.init, bg.init, m); 413 else 414 return str; 415 }