1 module logdefer.logger; 2 3 4 import std.array : Appender; 5 import std.conv : to; 6 7 public import logdefer.common : DefaultTimeProvider, DelegateWriter, EventContext, LogEntry, LogLevel, Verbosity; 8 import logdefer.serializer.json : JSONSerializer; 9 import logdefer.time.duration : Nanos; 10 import logdefer.time.utils : toDuration; 11 import logdefer.timer : Timer; 12 13 import unixtime : ClockType, UnixTimeHiRes; 14 15 alias DefaultLogger = Logger!(); 16 17 /** 18 The primary interface into the logging system. Logger is templatized 19 based on: 20 21 Serializer - formats the output and passes it to writer 22 TimeProvider - what source of time to use 23 24 By default the Serializer is JSON and the TimeProvider uses the standard Clock 25 in std.datetime. In this case simply pass a delegate callback that takes the 26 serialized string data and does whatever you want with it. The constructor 27 will take care of initializing the JSON serializer with your provided callback. 28 29 If you want to use a different serializer simple pass it in instead to the 30 properly templatized struct: 31 32 auto mySerializer = MySerializer(); 33 auto logger = Logger!(MySerializer)(mySerializer); 34 35 The only constraint is that your serializer implements opCall which takes 36 a ref to the EventContext object. Thus it could be a function, delegate, 37 struct or class. 38 39 If you want to provide your own source of time (for testing, or some external 40 time provider) you just call the constructor with your TimeProvider which 41 again must implement opCall and returns a UnixTime: 42 43 auto myTimeProvider = () { return UnixTime(0); }; 44 auto logger = Logger!(MySerializer, typeof(myTimeProvider))(xxx, myTimeProvider); 45 46 As you log against the log defer instance it will accumulate the logs into its 47 internal buffer. Once the object goes out of scope the logs are passed to the 48 serializer which handles formatting and writing out the logs. 49 */ 50 @safe 51 struct Logger( 52 Serializer = JSONSerializer!(DelegateWriter), 53 TimeProvider = typeof(DefaultTimeProvider) 54 ) 55 { 56 public: 57 58 @disable this(this); 59 60 /* 61 Convenience constructor. Uses default serializer and time provider 62 with provided delegate to write log 63 */ 64 @safe 65 alias Callback = void delegate(string msg); 66 67 this()(Callback callback) 68 { 69 serializer_ = Serializer(callback); 70 eventContext_.realStartTime = DefaultTimeProvider(); 71 eventContext_.monotonicStartTime = UnixTimeHiRes.now!(ClockType.MONOTONIC)(); 72 } 73 74 /* 75 Uses default time provider and provided serializer 76 */ 77 this()(Serializer serializer) 78 { 79 serializer_ = serializer; 80 eventContext_.realStartTime = DefaultTimeProvider(); 81 eventContext_.monotonicStartTime = UnixTimeHiRes.now!(ClockType.MONOTONIC)(); 82 } 83 84 /* 85 Uses provided serializer and time provider 86 */ 87 this()(Serializer serializer, TimeProvider timeProvider) 88 { 89 serializer_ = serializer; 90 eventContext_.realStartTime = timeProvider(); 91 eventContext_.monotonicStartTime = UnixTimeHiRes.now!(ClockType.MONOTONIC)(); 92 } 93 94 // Commit log when going out of scope 95 ~this() 96 { 97 commit(); 98 } 99 100 // Add given parameters to the log with given verbosity 101 void addLog(Param...)(Verbosity verbosity, const Param params) 102 { 103 // Log threshold not met, ignore log 104 if (verbosity > logLevel_) 105 { 106 return; 107 } 108 109 // Concatenate all the log params into a string 110 auto msg = Appender!string(); 111 foreach(param; params) 112 { 113 msg.put(to!string(param)); 114 } 115 // Store the message along with verbosity and duration 116 eventContext_.logs.put(LogEntry(toDuration!Nanos(eventContext_.monotonicStartTime, UnixTimeHiRes.now!(ClockType.MONOTONIC)()), verbosity, msg.data)); 117 } 118 119 // Add/update associated metadata with the event context 120 void opIndexAssign(Key, Value)(const auto ref Value value, const auto ref Key key) 121 { 122 eventContext_.metadata[to!string(key)] = to!string(value); 123 } 124 125 string opIndex(Key)(const auto ref Key key) 126 { 127 return eventContext_.metadata[to!string(key)]; 128 } 129 130 auto timer(string timerName) 131 { 132 eventContext_.timers.put(new Timer(timerName, eventContext_.monotonicStartTime)); 133 return eventContext_.timers.data[$-1].startTimer(); 134 } 135 136 // Convenience wrappers... 137 void error(Param...)(auto ref const Param params) 138 { 139 addLog(LogLevel.Error, params); 140 } 141 142 void warn(Param...)(auto ref const Param params) 143 { 144 addLog(LogLevel.Warn, params); 145 } 146 147 void info(Param...)(auto ref const Param params) 148 { 149 addLog(LogLevel.Info, params); 150 } 151 152 void trace(Param...)(auto ref const Param params) 153 { 154 addLog(LogLevel.Trace, params); 155 } 156 157 // Manipulate and view log levels... 158 void setLogLevel(LogLevel logLevel) 159 { 160 logLevel_ = logLevel; 161 } 162 163 void setLogLevel(int logLevel) 164 { 165 logLevel_ = logLevel; 166 } 167 168 int logLevel() 169 { 170 return logLevel_; 171 } 172 173 bool isError() 174 { 175 return logLevel_ <= LogLevel.Error; 176 } 177 178 void setError() 179 { 180 logLevel_ = LogLevel.Error; 181 } 182 183 bool isWarn() 184 { 185 return logLevel_ <= LogLevel.Warn; 186 } 187 188 void setWarn() 189 { 190 logLevel_ = LogLevel.Warn; 191 } 192 193 bool isInfo() 194 { 195 return logLevel_ <= LogLevel.Info; 196 } 197 198 void setInfo() 199 { 200 logLevel_ = LogLevel.Info; 201 } 202 203 bool isTrace() 204 { 205 return logLevel_ <= LogLevel.Trace; 206 } 207 208 void setTrace() 209 { 210 logLevel_ = LogLevel.Trace; 211 } 212 213 214 private: 215 216 Serializer serializer_ = void; 217 TimeProvider timeProvider_; 218 EventContext eventContext_; 219 int logLevel_ = int.max; 220 221 // Commit the logs in the buffer 222 void commit() 223 { 224 eventContext_.endOffset = toDuration!Nanos(eventContext_.monotonicStartTime, UnixTimeHiRes.now!(ClockType.MONOTONIC)()); 225 serializer_(eventContext_); 226 } 227 } 228 229 version (unittest) 230 { 231 import std.array : front; 232 import std.stdio : writeln; 233 import std.typecons : Tuple; 234 235 auto time = () 236 { 237 return UnixTimeHiRes(1234); 238 }; 239 240 const(EventContext)[] events; 241 auto serializer = (const ref EventContext eventContext) 242 { 243 events ~= eventContext; 244 }; 245 246 alias Spec = Tuple!(int, string); 247 248 auto testSpecs = [ 249 Spec(45, "custom 1"), 250 Spec(40, "trace"), 251 Spec(30, "info"), 252 Spec(20, "warn"), 253 Spec(10, "error"), 254 Spec(5, "custom 2"), 255 ]; 256 257 void verifyLogs(const ref EventContext evt, Spec[] specs) 258 { 259 auto logs = evt.logs.data; 260 assert(logs.length == specs.length); 261 uint i; 262 foreach(spec; specs) 263 { 264 assert(logs[i].verbosity == spec[0]); 265 assert(logs[i].msg == spec[1]); 266 i++; 267 } 268 } 269 270 alias TestLogger = Logger!(typeof(serializer), typeof(time)); 271 272 void testLog(ref TestLogger logger) 273 { 274 logger.addLog(45, "custom 1"); 275 logger.trace("trace"); 276 logger.info("info"); 277 logger.warn("warn"); 278 logger.error("error"); 279 logger.addLog(5, "custom 2"); 280 281 } 282 } 283 284 unittest 285 { 286 writeln("[UnitTest Logger] - default log level, start time"); 287 288 events.destroy; 289 290 { 291 auto logger = TestLogger(serializer, time); 292 testLog(logger); 293 { 294 auto timer = logger.timer("timer"); 295 } 296 logger["RequestID"] = 7; 297 assert(events.length == 0); 298 } 299 300 assert(events.length == 1); 301 302 auto evt = events.front; 303 assert(evt.realStartTime == time()); 304 assert(evt.endOffset.value > 0); 305 306 assert(evt.metadata.length == 1); 307 assert(evt.metadata["RequestID"] == "7"); 308 309 assert(evt.timers.data.length == 1); 310 assert(evt.timers.data.front.name == "timer"); 311 assert(evt.timers.data.front.start > 0); 312 assert(evt.timers.data.front.end > 0); 313 314 verifyLogs(evt, testSpecs); 315 } 316 317 unittest 318 { 319 writeln("[UnitTest Logger] - trace log level"); 320 321 events.destroy; 322 323 { 324 auto logger = TestLogger(serializer, time); 325 logger.setTrace(); 326 327 testLog(logger); 328 } 329 330 verifyLogs(events.front, testSpecs[1..$]); 331 } 332 333 unittest 334 { 335 writeln("[UnitTest Logger] - info log level"); 336 337 events.destroy; 338 339 { 340 auto logger = TestLogger(serializer, time); 341 logger.setInfo(); 342 343 testLog(logger); 344 } 345 346 verifyLogs(events.front, testSpecs[2..$]); 347 } 348 349 // Log level set to warn 350 unittest 351 { 352 writeln("[UnitTest Logger] - warn log level"); 353 354 events.destroy; 355 356 { 357 auto logger = TestLogger(serializer, time); 358 logger.setWarn(); 359 360 testLog(logger); 361 } 362 363 verifyLogs(events.front, testSpecs[3..$]); 364 } 365 366 367 // Log level set to error 368 unittest 369 { 370 writeln("[UnitTest Logger] - error log level"); 371 372 events.destroy; 373 374 { 375 auto logger = TestLogger(serializer, time); 376 logger.setError(); 377 378 testLog(logger); 379 } 380 381 verifyLogs(events.front, testSpecs[4..$]); 382 } 383 384 // Log level set to custom 385 unittest 386 { 387 writeln("[UnitTest Logger] - custom log level"); 388 389 events.destroy; 390 391 { 392 auto logger = TestLogger(serializer, time); 393 logger.setLogLevel(7); 394 395 testLog(logger); 396 } 397 398 verifyLogs(events.front, testSpecs[5..$]); 399 } 400 401 // Log level set to 0 402 unittest 403 { 404 writeln("[UnitTest Logger] - 0 log level"); 405 406 events.destroy; 407 408 { 409 auto logger = TestLogger(serializer, time); 410 logger.setLogLevel(0); 411 412 testLog(logger); 413 } 414 415 verifyLogs(events.front, testSpecs[6..$]); 416 }