1 /** 2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 Handles console logging in pretty colors. 7 8 The module disables colors when stdout and stderr isn't a TTY that support 9 colors. This is to avoid ASCII escape sequences in piped output. 10 */ 11 module colorlog; 12 13 import std.stdio : writefln, stderr, stdout; 14 import logger = std.experimental.logger; 15 import std.experimental.logger : LogLevel; 16 17 /// The verbosity level of the logging to use. 18 enum VerboseMode { 19 /// Warning+ 20 minimal, 21 /// Info+ 22 info, 23 /// Trace+ 24 trace, 25 /// Warnings+ 26 warning, 27 } 28 29 /** Configure `std.experimental.logger` with a colorlog instance. 30 */ 31 void confLogger(VerboseMode mode) { 32 switch (mode) { 33 case VerboseMode.info: 34 logger.globalLogLevel = logger.LogLevel.info; 35 logger.sharedLog = new SimpleLogger(logger.LogLevel.info); 36 break; 37 case VerboseMode.trace: 38 logger.globalLogLevel = logger.LogLevel.all; 39 logger.sharedLog = new DebugLogger(logger.LogLevel.all); 40 break; 41 case VerboseMode.warning: 42 logger.globalLogLevel = logger.LogLevel.warning; 43 logger.sharedLog = new SimpleLogger(logger.LogLevel.info); 44 break; 45 default: 46 logger.globalLogLevel = logger.LogLevel.info; 47 logger.sharedLog = new SimpleLogger(logger.LogLevel.info); 48 } 49 } 50 51 private template BaseColor(int n) { 52 enum BaseColor : int { 53 none = 39 + n, 54 55 black = 30 + n, 56 red = 31 + n, 57 green = 32 + n, 58 yellow = 33 + n, 59 blue = 34 + n, 60 magenta = 35 + n, 61 cyan = 36 + n, 62 white = 37 + n, 63 64 lightBlack = 90 + n, 65 lightRed = 91 + n, 66 lightGreen = 92 + n, 67 lightYellow = 93 + n, 68 lightBlue = 94 + n, 69 lightMagenta = 95 + n, 70 lightCyan = 96 + n, 71 lightWhite = 97 + n, 72 } 73 } 74 75 alias Color = BaseColor!0; 76 alias Background = BaseColor!10; 77 78 enum Mode { 79 none = 0, 80 bold = 1, 81 underline = 4, 82 blink = 5, 83 swap = 7, 84 hide = 8, 85 } 86 87 struct ColorImpl { 88 import std.format : FormatSpec; 89 90 private { 91 string text; 92 Color fg_; 93 Background bg_; 94 Mode mode_; 95 } 96 97 this(string txt) { 98 text = txt; 99 } 100 101 this(string txt, Color c) { 102 text = txt; 103 fg_ = c; 104 } 105 106 auto fg(Color c_) { 107 this.fg_ = c_; 108 return this; 109 } 110 111 auto bg(Background c_) { 112 this.bg_ = c_; 113 return this; 114 } 115 116 auto mode(Mode c_) { 117 this.mode_ = c_; 118 return this; 119 } 120 121 string toString() @safe const { 122 import std.exception : assumeUnique; 123 import std.format : FormatSpec; 124 125 char[] buf; 126 buf.reserve(100); 127 auto fmt = FormatSpec!char("%s"); 128 toString((const(char)[] s) { buf ~= s; }, fmt); 129 auto trustedUnique(T)(T t) @trusted { 130 return assumeUnique(t); 131 } 132 133 return trustedUnique(buf); 134 } 135 136 void toString(Writer, Char)(scope Writer w, FormatSpec!Char fmt) const { 137 import std.format : formattedWrite; 138 import std.range.primitives : put; 139 140 if (!_printColors || (fg_ == Color.none && bg_ == Background.none && mode_ == Mode.none)) 141 put(w, text); 142 else 143 formattedWrite(w, "\033[%d;%d;%dm%s\033[0m", mode_, fg_, bg_, text); 144 } 145 } 146 147 auto color(string s, Color c = Color.none) { 148 return ColorImpl(s, c); 149 } 150 151 /** Whether to print text with colors or not 152 * 153 * Defaults to true but will be set to false in initColors() if stdout or 154 * stderr are not a TTY (which means the output is probably being piped and we 155 * don't want ASCII escape chars in it) 156 */ 157 private shared bool _printColors = true; 158 private shared bool _isColorsInitialized = false; 159 160 // The width of the prefix. 161 private immutable _prefixWidth = 8; 162 163 /** It will detect whether or not stdout/stderr are a console/TTY and will 164 * consequently disable colored output if needed. 165 * 166 * Forgetting to call the function will result in ASCII escape sequences in the 167 * piped output, probably an undesiderable thing. 168 */ 169 void initColors() @trusted { 170 if (_isColorsInitialized) 171 return; 172 scope (exit) 173 _isColorsInitialized = true; 174 175 // Initially enable colors, we'll disable them during this functions if we 176 // find any reason to 177 _printColors = true; 178 179 version (Windows) { 180 _printColors = false; 181 } else { 182 import core.stdc.stdio; 183 import core.sys.posix.unistd; 184 185 if (!isatty(STDERR_FILENO) || !isatty(STDOUT_FILENO)) 186 _printColors = false; 187 } 188 } 189 190 class SimpleLogger : logger.Logger { 191 this(const LogLevel lvl = LogLevel.warning) @safe { 192 super(lvl); 193 initColors; 194 } 195 196 override void writeLogMsg(ref LogEntry payload) @trusted { 197 auto out_ = stderr; 198 auto use_color = Color.red; 199 auto use_mode = Mode.bold; 200 const use_bg = Background.black; 201 202 switch (payload.logLevel) { 203 case LogLevel.trace: 204 out_ = stdout; 205 use_color = Color.white; 206 use_mode = Mode.init; 207 break; 208 case LogLevel.info: 209 out_ = stdout; 210 use_color = Color.white; 211 break; 212 default: 213 } 214 215 import std.conv : to; 216 217 out_.writefln("%s: %s", payload.logLevel.to!string.color(use_color) 218 .bg(use_bg).mode(use_mode), payload.msg); 219 } 220 } 221 222 class DebugLogger : logger.Logger { 223 this(const logger.LogLevel lvl = LogLevel.trace) { 224 super(lvl); 225 initColors; 226 } 227 228 override void writeLogMsg(ref LogEntry payload) @trusted { 229 auto out_ = stderr; 230 auto use_color = Color.red; 231 auto use_mode = Mode.bold; 232 const use_bg = Background.black; 233 234 switch (payload.logLevel) { 235 case LogLevel.trace: 236 out_ = stdout; 237 use_color = Color.white; 238 use_mode = Mode.init; 239 break; 240 case LogLevel.info: 241 out_ = stdout; 242 use_color = Color.white; 243 break; 244 default: 245 } 246 247 import std.conv : to; 248 249 out_.writefln("%s: %s [%s:%d]", payload.logLevel.to!string.color(use_color) 250 .bg(use_bg).mode(use_mode), payload.msg, payload.funcName, payload.line); 251 } 252 }