Newer
Older
dub_jkp / source / dub / logging.d
@Giacomo De Lazzari Giacomo De Lazzari on 28 Jul 2022 11 KB Now checks if the output is a console or not
  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 printColorsInLog(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(..), and
  42. whether to color outputted text or not can be set with printColorsInLog(..).
  43.  
  44. The color(str, color) function can be used to color text within a log
  45. message, for instance like this:
  46.  
  47. logInfo("Tag", Color.green, "My %s message", "colored".color(Color.red))
  48.  
  49. Copyright: © 2018 Giacomo De Lazzari
  50. License: Subject to the terms of the MIT license, as written in the included LICENSE file.
  51. Authors: Giacomo De Lazzari
  52. */
  53.  
  54. module dub.logging;
  55.  
  56. import std.stdio;
  57. import std.array;
  58. import std.format;
  59. import std.string;
  60.  
  61. import dub.internal.colorize : fg, mode;
  62.  
  63. /**
  64. An enum listing possible colors for terminal output, useful to set the color
  65. of a tag. Re-exported from d-colorize in dub.internal.colorize. See the enum
  66. definition there for a list of possible values.
  67. */
  68. public alias Color = fg;
  69.  
  70. /**
  71. An enum listing possible text "modes" for terminal output, useful to set the
  72. text to bold, underline, blinking, etc...
  73. Re-exported from d-colorize in dub.internal.colorize. See the enum definition
  74. there for a list of possible values.
  75. */
  76. public alias Mode = mode;
  77.  
  78. /// The tag width in chars, defined as a constant here
  79. private const int TAG_WIDTH = 12;
  80.  
  81. /// Possible log levels supported
  82. enum LogLevel {
  83. debug_,
  84. diagnostic,
  85. info,
  86. warn,
  87. error,
  88. none
  89. }
  90.  
  91. // The current minimum log level to be printed
  92. private LogLevel _minLevel = LogLevel.info;
  93.  
  94. /*
  95. Whether to print text with colors or not, defaults to true but will be set
  96. to false in initLogging() if stdout or stderr are not a TTY (which means the
  97. output is probably being piped and we don't want ASCII escape chars in it)
  98. */
  99. private bool _printColors = true;
  100.  
  101. // isatty() is used in initLogging() to detect whether or not we are on a TTY
  102. extern (C) int isatty(int);
  103.  
  104. /**
  105. This function must be called at the beginning for the program, before any
  106. logging occurs. It will detect whether or not stdout/stderr are a console/TTY
  107. and will consequently disable colored output if needed.
  108.  
  109. Forgetting to call the function will result in ASCII escape sequences in the
  110. piped output, probably an undesiderable thing.
  111. */
  112. void initLogging()
  113. {
  114. import core.stdc.stdio;
  115.  
  116. // Initially enable colors, we'll disable them during this functions if we
  117. // find any reason to
  118. _printColors = true;
  119.  
  120. // The following stuff depends on the platform
  121. version (Windows)
  122. {
  123. version (CRuntime_DigitalMars)
  124. {
  125. if (!isatty(core.stdc.stdio.stdout) ||
  126. !isatty(core.stdc.stdio.stderr))
  127. _printColors = false;
  128. }
  129. else version (CRuntime_Microsoft)
  130. {
  131. if (!isatty(fileno(core.stdc.stdio.stdout)) ||
  132. !isatty(fileno(core.stdc.stdio.stderr)))
  133. _printColors = false;
  134. }
  135. else
  136. _printColors = false;
  137. }
  138. else version (Posix)
  139. {
  140. import core.sys.posix.unistd;
  141.  
  142. if (!isatty(STDERR_FILENO) || !isatty(STDOUT_FILENO))
  143. _printColors = false;
  144. }
  145. }
  146.  
  147. /// Sets the minimum log level to be printed
  148. void setLogLevel(LogLevel level) nothrow
  149. {
  150. _minLevel = level;
  151. }
  152.  
  153. /// Gets the minimum log level to be printed
  154. LogLevel getLogLevel()
  155. {
  156. return _minLevel;
  157. }
  158.  
  159. /// Set whether to print colors or not
  160. void printColorsInLog(bool enabled)
  161. {
  162. _printColors = enabled;
  163. }
  164.  
  165. /**
  166. Shorthand function to log a message with debug/diagnostic level, no tag string
  167. or tag color required (since there will be no tag).
  168.  
  169. Params:
  170. level = The log level for the logged message
  171. fmt = See http://dlang.org/phobos/std_format.html#format-string
  172. */
  173. void logDebug(T...)(string fmt, lazy T args) nothrow
  174. {
  175. log(LogLevel.debug_, false, "", Color.init, fmt, args);
  176. }
  177.  
  178. /// ditto
  179. void logDiagnostic(T...)(string fmt, lazy T args) nothrow
  180. {
  181. log(LogLevel.diagnostic, false, "", Color.init, fmt, args);
  182. }
  183.  
  184. /**
  185. Shorthand function to log a message with info level, with custom tag string
  186. and tag color.
  187.  
  188. Params:
  189. tag = The string the tag at the beginning of the line should contain
  190. tagColor = The color the tag string should have
  191. level = The log level for the logged message
  192. fmt = See http://dlang.org/phobos/std_format.html#format-string
  193. */
  194. void logInfo(T...)(string tag, Color tagColor, string fmt, lazy T args) nothrow
  195. {
  196. log(LogLevel.info, false, tag, tagColor, fmt, args);
  197. }
  198.  
  199. /**
  200. Shorthand function to log a message with info level, this version prints an
  201. empty tag automatically (which is different from not having a tag - in this
  202. case there will be an identation of TAG_WIDTH chars on the left anyway).
  203.  
  204. Params:
  205. level = The log level for the logged message
  206. fmt = See http://dlang.org/phobos/std_format.html#format-string
  207. */
  208. void logInfo(T...)(string fmt, lazy T args) nothrow if (!is(T[0] : Color))
  209. {
  210. log(LogLevel.info, false, "", Color.init, fmt, args);
  211. }
  212.  
  213. /**
  214. Shorthand function to log a message with info level, this version doesn't
  215. print a tag at all, it effectively just prints the given string.
  216.  
  217. Params:
  218. level = The log level for the logged message
  219. fmt = See http://dlang.org/phobos/std_format.html#format-string
  220. */
  221. void logInfoNoTag(T...)(string fmt, lazy T args) nothrow if (!is(T[0] : Color))
  222. {
  223. log(LogLevel.info, true, "", Color.init, fmt, args);
  224. }
  225.  
  226. /**
  227. Shorthand function to log a message with warning level, with custom tag string.
  228. The tag color is fixed to yellow.
  229.  
  230. Params:
  231. tag = The string the tag at the beginning of the line should contain
  232. level = The log level for the logged message
  233. fmt = See http://dlang.org/phobos/std_format.html#format-string
  234. */
  235. void logWarnTag(T...)(string tag, string fmt, lazy T args) nothrow
  236. {
  237. log(LogLevel.warn, false, tag, Color.yellow, fmt, args);
  238. }
  239.  
  240. /**
  241. Shorthand function to log a message with warning level, using the default
  242. tag "Warning". The tag color is also fixed to yellow.
  243.  
  244. Params:
  245. level = The log level for the logged message
  246. fmt = See http://dlang.org/phobos/std_format.html#format-string
  247. */
  248. void logWarn(T...)(string fmt, lazy T args) nothrow
  249. {
  250. log(LogLevel.warn, false, "Warning", Color.yellow, fmt, args);
  251. }
  252.  
  253. /**
  254. Shorthand function to log a message with error level, with custom tag string.
  255. The tag color is fixed to red.
  256.  
  257. Params:
  258. tag = The string the tag at the beginning of the line should contain
  259. level = The log level for the logged message
  260. fmt = See http://dlang.org/phobos/std_format.html#format-string
  261. */
  262. void logErrorTag(T...)(string tag, string fmt, lazy T args) nothrow
  263. {
  264. log(LogLevel.error, false, tag, Color.red, fmt, args);
  265. }
  266.  
  267. /**
  268. Shorthand function to log a message with error level, using the default
  269. tag "Error". The tag color is also fixed to red.
  270.  
  271. Params:
  272. level = The log level for the logged message
  273. fmt = See http://dlang.org/phobos/std_format.html#format-string
  274. */
  275. void logError(T...)(string fmt, lazy T args) nothrow
  276. {
  277. log(LogLevel.error, false, "Error", Color.red, fmt, args);
  278. }
  279.  
  280. /**
  281. Log a message with the specified log level and with the specified tag string
  282. and color. If the log level is debug or diagnostic, the tag is not printed
  283. thus the tag string and tag color will be ignored.
  284.  
  285. Params:
  286. level = The log level for the logged message
  287. disableTag = Setting this to true disables the tag, no matter what
  288. tag = The string the tag at the beginning of the line should contain
  289. tagColor = The color the tag string should have
  290. fmt = See http://dlang.org/phobos/std_format.html#format-string
  291. */
  292. void log(T...)(
  293. LogLevel level,
  294. bool disableTag,
  295. string tag,
  296. Color tagColor,
  297. string fmt,
  298. lazy T args
  299. ) nothrow
  300. {
  301. if (level < _minLevel)
  302. return;
  303.  
  304. auto hasTag = true;
  305. if (level <= LogLevel.diagnostic)
  306. hasTag = false;
  307. if (disableTag)
  308. hasTag = false;
  309.  
  310. try
  311. {
  312. string result = format(fmt, args);
  313.  
  314. if (hasTag)
  315. result = tag.rightJustify(TAG_WIDTH, ' ').color(tagColor) ~ " " ~ result;
  316.  
  317. import dub.internal.colorize : cwrite;
  318.  
  319. File output = (level <= LogLevel.info) ? stdout : stderr;
  320.  
  321. if (output.isOpen)
  322. {
  323. output.cwrite(result, "\n");
  324. output.flush();
  325. }
  326. }
  327. catch (Exception e)
  328. {
  329. debug assert(false, e.msg);
  330. }
  331. }
  332.  
  333. /**
  334. Colors the specified string with the specified color. The function is used to
  335. print colored text within a log message. The function also checks whether
  336. color output is enabled or disabled (when not outputting to a TTY) and, in the
  337. last case, just returns the plain string. This allows to use it like so:
  338.  
  339. logInfo("Tag", Color.green, "My %s log message", "colored".color(Color.red));
  340.  
  341. without worring whether or not colored output is enabled or not.
  342.  
  343. Also a mode can be specified, such as bold/underline/etc...
  344.  
  345. Params:
  346. str = The string to color
  347. color = The color to apply
  348. mode = An optional mode, such as bold/underline/etc...
  349. */
  350. string color(const string str, const Color color, const Mode mode = Mode.init)
  351. {
  352. import dub.internal.colorize;
  353.  
  354. if (_printColors == true)
  355. return dub.internal.colorize.color(str, color, bg.init, mode);
  356. else
  357. return str;
  358. }
  359.  
  360. /**
  361. This function is the same as the above one, but just accepts a mode.
  362. It's useful, for instance, when outputting bold text without changing the
  363. color.
  364.  
  365. Params:
  366. str = The string to color
  367. mode = The mode, such as bold/underline/etc...
  368. */
  369. string color(const string str, const Mode m = Mode.init)
  370. {
  371. import dub.internal.colorize;
  372.  
  373. if (_printColors == true)
  374. return dub.internal.colorize.color(str, fg.init, bg.init, m);
  375. else
  376. return str;
  377. }