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 auto-detect 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 often will be) and always has a fixed 16 width, which is defined as a constant 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 fmt = See http://dlang.org/phobos/std_format.html#format-string 203 args = Arguments matching the 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 fmt = See http://dlang.org/phobos/std_format.html#format-string 224 args = Arguments matching the 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 indentation of tagWidth chars on the left anyway). 235 236 Params: 237 fmt = See http://dlang.org/phobos/std_format.html#format-string 238 args = Arguments matching the 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 fmt = See http://dlang.org/phobos/std_format.html#format-string 251 args = Arguments matching the 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 fmt = See http://dlang.org/phobos/std_format.html#format-string 265 args = Arguments matching the 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 fmt = See http://dlang.org/phobos/std_format.html#format-string 278 args = Arguments matching the 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 fmt = See http://dlang.org/phobos/std_format.html#format-string 292 args = Arguments matching the 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 fmt = See http://dlang.org/phobos/std_format.html#format-string 305 args = Arguments matching the 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 args = Arguments matching the format string 326 */ 327 void log(T...)( 328 LogLevel level, 329 bool disableTag, 330 string tag, 331 Color tagColor, 332 string fmt, 333 lazy T args 334 ) nothrow 335 { 336 if (level < _minLevel) 337 return; 338 339 auto hasTag = true; 340 if (level <= LogLevel.diagnostic) 341 hasTag = false; 342 if (disableTag) 343 hasTag = false; 344 345 auto boldTag = false; 346 if (level >= LogLevel.warn) 347 boldTag = true; 348 349 try 350 { 351 string result = format(fmt, args); 352 353 if (hasTag) 354 result = tag.rightJustify(tagWidth.get, ' ').color(tagColor, boldTag ? Mode.bold : Mode.init) ~ " " ~ result; 355 356 import dub.internal.colorize : cwrite; 357 358 File output = (level <= LogLevel.info) ? stdout : stderr; 359 360 if (output.isOpen) 361 { 362 output.cwrite(result, "\n"); 363 output.flush(); 364 } 365 } 366 catch (Exception e) 367 { 368 debug assert(false, e.msg); 369 } 370 } 371 372 /** 373 Colors the specified string with the specified color. The function is used to 374 print colored text within a log message. The function also checks whether 375 color output is enabled or disabled (when not outputting to a TTY) and, in the 376 last case, just returns the plain string. This allows to use it like so: 377 378 logInfo("Tag", Color.green, "My %s log message", "colored".color(Color.red)); 379 380 without worrying whether or not colored output is enabled or not. 381 382 Also a mode can be specified, such as bold/underline/etc... 383 384 Params: 385 str = The string to color 386 c = The color to apply 387 m = An optional mode, such as bold/underline/etc... 388 */ 389 string color(const string str, const Color c, const Mode m = Mode.init) 390 { 391 import dub.internal.colorize; 392 393 if (_printColors) 394 return dub.internal.colorize.color(str, c, bg.init, m); 395 else 396 return str; 397 } 398 399 /** 400 This function is the same as the above one, but just accepts a mode. 401 It's useful, for instance, when outputting bold text without changing the 402 color. 403 404 Params: 405 str = The string to color 406 m = The mode, such as bold/underline/etc... 407 */ 408 string color(const string str, const Mode m = Mode.init) 409 { 410 import dub.internal.colorize; 411 412 if (_printColors) 413 return dub.internal.colorize.color(str, fg.init, bg.init, m); 414 else 415 return str; 416 }