1 /** 
2  * Core logging services
3  */
4 module dlog.core;
5 
6 import std.conv : to;
7 import std.range : join;
8 import dlog.transform : MessageTransform;
9 import dlog.defaults;
10 import dlog.context : Context, CompilationInfo, Level;
11 import dlog.utilities : flatten;
12 
13 /**
14 * Logger
15 *
16 * Represents a logger instance
17 */
18 public class Logger
19 {
20 	/* Starting transformation */
21 	private MessageTransform messageTransform;
22 	
23 	/** 
24 	 * The multiple argument joiner
25 	 */
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 	/** 
336 	 * Alias for debug_
337 	 */
338 	public alias dbg = debug_;
339 	
340 	/** 
341 	 * Logging implementation, this is where the final
342 	 * transformed text will be transferred to and finally
343 	 * logged
344 	 *
345 	 * Params:
346 	 *   message = the message to log
347 	 */
348 	protected abstract void logImpl(string message);
349 }
350 
351 
352 version(unittest)
353 {
354 	import std.meta : AliasSeq;
355 	import std.stdio : writeln;
356 }
357 
358 /**
359 * Tests the DefaultLogger
360 */
361 unittest
362 {
363 	Logger logger = new DefaultLogger();
364 
365 	alias testParameters = AliasSeq!("This is a log message", 1.1, true, [1,2,3], 'f', logger);
366 
367 	
368 	// Test various types one-by-one
369 	static foreach(testParameter; testParameters)
370 	{
371 		logger.log(testParameter);
372 	}
373 
374 	// Test various parameters (of various types) all at once
375 	logger.log(testParameters);
376 
377 	// Same as above but with a custom joiner set
378 	logger = new DefaultLogger("(-)");
379 	logger.log(testParameters);
380 
381 	writeln();
382 }
383 
384 /**
385  * Printing out some mixed data-types, also using a DEFAULT context 
386  */
387 unittest
388 {
389 	Logger logger = new DefaultLogger();
390 
391 	// Create a default logger with the default joiner
392 	logger = new DefaultLogger();
393 	logger.log(["a", "b", "c", "d"], [1, 2], true);
394 
395 	writeln();
396 }
397 
398 /**
399  * Printing out some mixed data-types, also using a CUSTOM context 
400  */
401 unittest
402 {
403 	Logger logger = new DefaultLogger();
404 
405 	// Create a default logger with the default joiner
406 	logger = new DefaultLogger();
407 
408 	// Create a custom context
409 	Context customContext = new Context();
410 
411 	// Log with the custom context
412 	logger.logc(customContext, logger.args(["an", "array"], 1, "hello", true));
413 
414 	writeln();
415 }
416 
417 /**
418  * Printing out some mixed data-types, also using a DEFAULT context
419  * but also testing out the `error()`, `warn()`, `info()` and `debug()`
420  */
421 unittest
422 {
423 	Logger logger = new DefaultLogger();
424 
425 	// Create a default logger with the default joiner
426 	logger = new DefaultLogger();
427 
428 	// Test out `error()`
429 	logger.error(["woah", "LEVELS!"], 69.420);
430 
431 	// Test out `info()`
432 	logger.info(["woah", "LEVELS!"], 69.420);
433 
434 	// Test out `warn()`
435 	logger.warn(["woah", "LEVELS!"], 69.420);
436 
437 	// Test out `debug_()`
438 	logger.debug_(["woah", "LEVELS!"], 69.420);
439 
440 	writeln();
441 }