1 /**
2 * Core module containing types pertaining to the base Logger
3 * class and base MessageTransform class (along with a default
4 * transform, DefaultTransform)
5 */
6 module dlog.core;
7 
8 import std.conv : to;
9 import std.range : join;
10 import dlog.transform : MessageTransform;
11 import dlog.defaults;
12 import dlog.context : Context, CompilationInfo, Level;
13 import dlog.utilities : flatten;
14 
15 /**
16 * Logger
17 *
18 * Represents a logger instance
19 */
20 public class Logger
21 {
22 	/* Starting transformation */
23 	private MessageTransform messageTransform;
24 	
25 	/* The multiple argument joiner */
26 	protected string multiArgJoiner;
27 
28 	/** 
29 	 * Constructs a new Logger with the default
30 	 * MessageTransform
31 	 *
32 	 * Params:
33 	 *   multiArgJoiner = optional joiner for segmented prints (default is " ")
34 	 */
35 	this(string multiArgJoiner = " ")
36 	{
37 		this(new DefaultTransform(), multiArgJoiner);
38 	}
39 
40 	/** 
41 	 * Constructs a new Logger with the provided
42 	 * custom message transform
43 	 * Params:
44 	 *   messageTransform = the message transform to use
45 	 *   multiArgJoiner = optional joiner for segmented prints (default is " ")
46 	 */
47 	this(MessageTransform messageTransform, string multiArgJoiner = " ")
48 	{
49 		this.messageTransform = messageTransform;
50 		this.multiArgJoiner = multiArgJoiner;
51 	}
52 
53 	/** 
54 	 * Given an arbitrary amount of arguments, convert each to a string
55 	 * and return it as an array joined by the joiner
56 	 *
57 	 * Params:
58 	 *   segments = alias sequence
59 	 * Returns: a string of the argumnets
60 	 */
61 	public string args(TextType...)(TextType segments)
62 	{
63 		/* The flattened components */
64 		string[] components = flatten(segments);
65 
66 		/* Join all `components` into a single string */
67 		string joined = join(components, multiArgJoiner);
68 
69 		return joined;
70 	}
71 
72 	
73 	/** 
74 	 * Logs the given string using the default context
75 	 *
76 	 * Params:
77 	 *   text = the string to log
78 	 *   __FILE_FULL_PATH__ = compile time usage file
79 	 *   __FILE__ = compile time usage file (relative)
80 	 *   __LINE__ = compile time usage line number
81 	 *   __MODULE__ = compile time usage module
82 	 *   __FUNCTION__ = compile time usage function
83 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
84 	 */
85 	public final void log(string text, string c1 = __FILE_FULL_PATH__,
86 									string c2 = __FILE__, ulong c3 = __LINE__,
87 									string c4 = __MODULE__, string c5 = __FUNCTION__,
88 									string c6 = __PRETTY_FUNCTION__)
89 	{
90 		/* Use the default context `Context` */
91 		Context defaultContext = new Context();
92 
93 		/* Build up the line information */
94 		CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
95 
96 		/* Set the line information in the context */
97 		defaultContext.setLineInfo(compilationInfo);
98 
99 		/* Call the log */
100 		logc(defaultContext, text, c1, c2, c3, c4, c5, c6);
101 	}
102 
103 	/** 
104 	 * Logs using the default context an arbitrary amount of arguments
105 	 *
106 	 * Params:
107 	 *   segments = the arbitrary argumnets (alias sequence)
108 	 *   __FILE_FULL_PATH__ = compile time usage file
109 	 *   __FILE__ = compile time usage file (relative)
110 	 *   __LINE__ = compile time usage line number
111 	 *   __MODULE__ = compile time usage module
112 	 *   __FUNCTION__ = compile time usage function
113 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
114 	 */
115 	public final void log(TextType...)(TextType segments, string c1 = __FILE_FULL_PATH__,
116 									string c2 = __FILE__, ulong c3 = __LINE__,
117 									string c4 = __MODULE__, string c5 = __FUNCTION__,
118 									string c6 = __PRETTY_FUNCTION__)
119 	{
120 		/**
121 		 * Grab at compile-time all arguments and generate runtime code to add them to `components`
122 		 */		
123 		string[] components = flatten(segments);
124 
125 		/* Join all `components` into a single string */
126 		string messageOut = join(components, multiArgJoiner);
127 
128 		/* Call the log (with text and default context) */
129 		log(messageOut, c1, c2, c3, c4, c5, c6);
130 	}
131 	
132 	/** 
133 	 * Logs the given string using the provided context
134 	 *
135 	 * Params:
136 	 *	 context = the custom context to use
137 	 *   text = the string to log
138 	 *   __FILE_FULL_PATH__ = compile time usage file
139 	 *   __FILE__ = compile time usage file (relative)
140 	 *   __LINE__ = compile time usage line number
141 	 *   __MODULE__ = compile time usage module
142 	 *   __FUNCTION__ = compile time usage function
143 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
144 	 */
145 	public final void logc(Context context, string text, string c1 = __FILE_FULL_PATH__,
146 									string c2 = __FILE__, ulong c3 = __LINE__,
147 									string c4 = __MODULE__, string c5 = __FUNCTION__,
148 									string c6 = __PRETTY_FUNCTION__)
149 	{
150 		/* Build up the line information */
151 		CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
152 
153 		/* Set the line information in the context */
154 		context.setLineInfo(compilationInfo);
155 
156 		/* Apply the transformation on the message */
157 		string transformedMesage = messageTransform.execute(text, context);
158 		
159 		/* Call the underlying logger implementation */
160 		logImpl(transformedMesage);
161 	}
162 
163 	/** 
164 	 * Logs using the default context an arbitrary amount of arguments
165 	 * specifically setting the context's level to ERROR
166 	 *
167 	 * Params:
168 	 *   segments = the arbitrary argumnets (alias sequence)
169 	 *   __FILE_FULL_PATH__ = compile time usage file
170 	 *   __FILE__ = compile time usage file (relative)
171 	 *   __LINE__ = compile time usage line number
172 	 *   __MODULE__ = compile time usage module
173 	 *   __FUNCTION__ = compile time usage function
174 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
175 	 */
176 	public void error(TextType...)(TextType segments,
177 									string c1 = __FILE_FULL_PATH__,
178 									string c2 = __FILE__, ulong c3 = __LINE__,
179 									string c4 = __MODULE__, string c5 = __FUNCTION__,
180 									string c6 = __PRETTY_FUNCTION__)
181 	{
182 		/* Use the default context `Context` */
183 		Context defaultContext = new Context();
184 
185 		/* Build up the line information */
186 		CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
187 
188 		/* Set the line information in the context */
189 		defaultContext.setLineInfo(compilationInfo);
190 
191 		/* Set the level to ERROR */
192 		defaultContext.setLevel(Level.ERROR);
193 
194 		/**
195 		 * Grab at compile-time all arguments and generate runtime code to add them to `components`
196 		 */		
197 		string[] components = flatten(segments);
198 
199 		/* Join all `components` into a single string */
200 		string messageOut = join(components, multiArgJoiner);
201 
202 		/* Call the log */
203 		logc(defaultContext, messageOut, c1, c2, c3, c4, c5, c6);
204 	}
205 
206 	/** 
207 	 * Logs using the default context an arbitrary amount of arguments
208 	 * specifically setting the context's level to INFO
209 	 *
210 	 * Params:
211 	 *   segments = the arbitrary argumnets (alias sequence)
212 	 *   __FILE_FULL_PATH__ = compile time usage file
213 	 *   __FILE__ = compile time usage file (relative)
214 	 *   __LINE__ = compile time usage line number
215 	 *   __MODULE__ = compile time usage module
216 	 *   __FUNCTION__ = compile time usage function
217 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
218 	 */
219 	public void info(TextType...)(TextType segments,
220 									string c1 = __FILE_FULL_PATH__,
221 									string c2 = __FILE__, ulong c3 = __LINE__,
222 									string c4 = __MODULE__, string c5 = __FUNCTION__,
223 									string c6 = __PRETTY_FUNCTION__)
224 	{
225 		/* Use the default context `Context` */
226 		Context defaultContext = new Context();
227 
228 		/* Build up the line information */
229 		CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
230 
231 		/* Set the line information in the context */
232 		defaultContext.setLineInfo(compilationInfo);
233 
234 		/* Set the level to INFO */
235 		defaultContext.setLevel(Level.INFO);
236 
237 		/**
238 		 * Grab at compile-time all arguments and generate runtime code to add them to `components`
239 		 */		
240 		string[] components = flatten(segments);
241 
242 		/* Join all `components` into a single string */
243 		string messageOut = join(components, multiArgJoiner);
244 
245 		/* Call the log */
246 		logc(defaultContext, messageOut, c1, c2, c3, c4, c5, c6);
247 	}
248 
249 	/** 
250 	 * Logs using the default context an arbitrary amount of arguments
251 	 * specifically setting the context's level to WARN
252 	 *
253 	 * Params:
254 	 *   segments = the arbitrary argumnets (alias sequence)
255 	 *   __FILE_FULL_PATH__ = compile time usage file
256 	 *   __FILE__ = compile time usage file (relative)
257 	 *   __LINE__ = compile time usage line number
258 	 *   __MODULE__ = compile time usage module
259 	 *   __FUNCTION__ = compile time usage function
260 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
261 	 */
262 	public void warn(TextType...)(TextType segments,
263 									string c1 = __FILE_FULL_PATH__,
264 									string c2 = __FILE__, ulong c3 = __LINE__,
265 									string c4 = __MODULE__, string c5 = __FUNCTION__,
266 									string c6 = __PRETTY_FUNCTION__)
267 	{
268 		/* Use the default context `Context` */
269 		Context defaultContext = new Context();
270 
271 		/* Build up the line information */
272 		CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
273 
274 		/* Set the line information in the context */
275 		defaultContext.setLineInfo(compilationInfo);
276 
277 		/* Set the level to WARN */
278 		defaultContext.setLevel(Level.WARN);
279 
280 		/**
281 		 * Grab at compile-time all arguments and generate runtime code to add them to `components`
282 		 */		
283 		string[] components = flatten(segments);
284 
285 		/* Join all `components` into a single string */
286 		string messageOut = join(components, multiArgJoiner);
287 
288 		/* Call the log */
289 		logc(defaultContext, messageOut, c1, c2, c3, c4, c5, c6);
290 	}
291 
292 	/** 
293 	 * Logs using the default context an arbitrary amount of arguments
294 	 * specifically setting the context's level to DEBUG
295 	 *
296 	 * Params:
297 	 *   segments = the arbitrary argumnets (alias sequence)
298 	 *   __FILE_FULL_PATH__ = compile time usage file
299 	 *   __FILE__ = compile time usage file (relative)
300 	 *   __LINE__ = compile time usage line number
301 	 *   __MODULE__ = compile time usage module
302 	 *   __FUNCTION__ = compile time usage function
303 	 *   __PRETTY_FUNCTION__ = compile time usage function (pretty)
304 	 */
305 	public void debug_(TextType...)(TextType segments,
306 									string c1 = __FILE_FULL_PATH__,
307 									string c2 = __FILE__, ulong c3 = __LINE__,
308 									string c4 = __MODULE__, string c5 = __FUNCTION__,
309 									string c6 = __PRETTY_FUNCTION__)
310 	{
311 		/* Use the default context `Context` */
312 		Context defaultContext = new Context();
313 
314 		/* Build up the line information */
315 		CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
316 
317 		/* Set the line information in the context */
318 		defaultContext.setLineInfo(compilationInfo);
319 
320 		/* Set the level to DEBUG */
321 		defaultContext.setLevel(Level.DEBUG);
322 
323 		/**
324 		 * Grab at compile-time all arguments and generate runtime code to add them to `components`
325 		 */		
326 		string[] components = flatten(segments);
327 
328 		/* Join all `components` into a single string */
329 		string messageOut = join(components, multiArgJoiner);
330 
331 		/* Call the log */
332 		logc(defaultContext, messageOut, c1, c2, c3, c4, c5, c6);
333 	}
334 
335 	/* You can also call using `dbg` */
336 	public alias dbg = debug_;
337 	
338 	/** 
339 	 * Logging implementation, this is where the final
340 	 * transformed text will be transferred to and finally
341 	 * logged
342 	 *
343 	 * Params:
344 	 *   message = the message to log
345 	 */
346 	protected abstract void logImpl(string message);
347 }
348 
349 
350 version(unittest)
351 {
352 	import std.meta : AliasSeq;
353 	import std.stdio : writeln;
354 }
355 
356 /**
357 * Tests the DefaultLogger
358 */
359 unittest
360 {
361 	Logger logger = new DefaultLogger();
362 
363 	alias testParameters = AliasSeq!("This is a log message", 1.1, true, [1,2,3], 'f', logger);
364 
365 	
366 	// Test various types one-by-one
367 	static foreach(testParameter; testParameters)
368 	{
369 		logger.log(testParameter);
370 	}
371 
372 	// Test various parameters (of various types) all at once
373 	logger.log(testParameters);
374 
375 	// Same as above but with a custom joiner set
376 	logger = new DefaultLogger("(-)");
377 	logger.log(testParameters);
378 
379 	writeln();
380 }
381 
382 /**
383  * Printing out some mixed data-types, also using a DEFAULT context 
384  */
385 unittest
386 {
387 	Logger logger = new DefaultLogger();
388 
389 	// Create a default logger with the default joiner
390 	logger = new DefaultLogger();
391 	logger.log(["a", "b", "c", "d"], [1, 2], true);
392 
393 	writeln();
394 }
395 
396 /**
397  * Printing out some mixed data-types, also using a CUSTOM context 
398  */
399 unittest
400 {
401 	Logger logger = new DefaultLogger();
402 
403 	// Create a default logger with the default joiner
404 	logger = new DefaultLogger();
405 
406 	// Create a custom context
407 	Context customContext = new Context();
408 
409 	// Log with the custom context
410 	logger.logc(customContext, logger.args(["an", "array"], 1, "hello", true));
411 
412 	writeln();
413 }
414 
415 /**
416  * Printing out some mixed data-types, also using a DEFAULT context
417  * but also testing out the `error()`, `warn()`, `info()` and `debug()`
418  */
419 unittest
420 {
421 	Logger logger = new DefaultLogger();
422 
423 	// Create a default logger with the default joiner
424 	logger = new DefaultLogger();
425 
426 	// Test out `error()`
427 	logger.error(["woah", "LEVELS!"], 69.420);
428 
429 	// Test out `info()`
430 	logger.info(["woah", "LEVELS!"], 69.420);
431 
432 	// Test out `warn()`
433 	logger.warn(["woah", "LEVELS!"], 69.420);
434 
435 	// Test out `debug_()`
436 	logger.debug_(["woah", "LEVELS!"], 69.420);
437 
438 	writeln();
439 }