1 /**
2  * Default logger
3  *
4  * Authors: Tristan Brice Velloza Kildaire (deavmi)
5  */
6 module dlog.defaults;
7 
8 import dlog.core;
9 import dlog.basic : BasicMessage, FileHandler, Level, BasicLogger;
10 import std.stdio : stdout;
11 import std.conv : to;
12 import dlog.utilities : flatten;
13 import std.array :join;
14 import std.datetime.systime : Clock, SysTime;
15 
16 /**
17 * DefaultLogger
18 *
19 * The default logger logs using
20 * a pretty stock-standard (non-colored)
21 * message transformation and supports
22 * the basic levels of logging.
23 */
24 public final class DefaultLogger : BasicLogger
25 {
26 	/** 
27 	 * The joiner for multi-argument
28 	 * log messages
29 	 */
30 	private string multiArgJoiner;
31 
32 	/** 
33 	 * Constructs a new default logger
34 	 *
35 	 * Params:
36 	 *   multiArgJoiner = the joiner to use
37 	 */
38 	this(string multiArgJoiner = " ")
39 	{
40 		this.multiArgJoiner = multiArgJoiner;
41 		
42 		addTransform(new DefaultTransform());
43 		addHandler(new FileHandler(stdout));
44 	}
45 
46 	/** 
47 	 * Logs the given message of an arbitrary amount of
48 	 * arguments and specifically sets the level to ERROR
49 	 *
50 	 * Params:
51 	 *   segments = the arbitrary argumnets (alias sequence)
52 	 */
53 	public void error(TextType...)(TextType segments)
54 	{
55 		doLog(segments, Level.ERROR);
56 	}
57 
58 	/** 
59 	 * Logs the given message of an arbitrary amount of
60 	 * arguments and specifically sets the level to INFO
61 	 *
62 	 * Params:
63 	 *   segments = the arbitrary argumnets (alias sequence)
64 	 */
65 	public void info(TextType...)(TextType segments)
66 	{
67 		doLog(segments, Level.INFO);
68 	}
69 
70 	/** 
71 	 * Logs the given message of an arbitrary amount of
72 	 * arguments and specifically sets the level to WARN
73 	 *
74 	 * Params:
75 	 *   segments = the arbitrary argumnets (alias sequence)
76 	 */
77 	public void warn(TextType...)(TextType segments)
78 	{
79 		doLog(segments, Level.WARN);
80 	}
81 
82 	/** 
83 	 * Logs the given message of an arbitrary amount of
84 	 * arguments and specifically sets the level to DEBUG
85 	 *
86 	 * Params:
87 	 *   segments = the arbitrary argumnets (alias sequence)
88 	 */
89 	public void debug_(TextType...)(TextType segments)
90 	{
91 		doLog(segments, Level.DEBUG);
92 	}
93 
94 	/** 
95      * Performs the actual logging
96      * by packing up everything before
97      * sending it to the `log(Message)`
98      * method
99      *
100      * Params:
101      *   segments = the compile-time segments
102      *   level = the log level to use
103      */
104     private void doLog(TextType...)(TextType segments, Level level)
105     {
106         /* Create a new basic message */
107         BasicMessage message = new BasicMessage();
108 
109         /* Set the level */
110         message.setLevel(level);
111 
112         /** 
113          * Grab all compile-time arguments and make them
114          * into an array, then join them together and
115          * set that text as the message's text
116          */
117         message.setText(join(flatten(segments), multiArgJoiner));
118 
119         /* Log this message */
120 		log(message);
121     }
122 
123 	/** 
124 	 * Alias for debug_
125 	 */
126 	public alias dbg = debug_;
127 }
128 
129 /**
130  * DefaultTransform
131  *
132  * Provides a transformation of the kind
133  *
134  * [date+time] (level): message `\n`
135  */
136 private final class DefaultTransform : Transform
137 {
138 	/** 
139 	 * Performs the default transformation.
140 	 * If the message is not a `BasicMessage`
141 	 * then no transformation occurs.
142 	 *
143 	 * Params:
144 	 *   message = the message to transform
145 	 * Returns: the transformed message
146 	 */
147 	public Message transform(Message message)
148 	{
149 		// Only handle BasicMessage(s)
150 		BasicMessage bmesg = cast(BasicMessage)message;
151 		if(bmesg is null)
152 		{
153 			return message;
154 		}
155 
156 		string text;
157 
158 		/* Date and time */
159 		SysTime currTime = Clock.currTime();
160 		string timestamp = to!(string)(currTime);
161 		text = "["~timestamp~"]";
162 
163 		/* Level */
164 		text = text ~ "\t(";
165 		text = text ~ to!(string)(bmesg.getLevel());
166 		text = text ~ "): "~bmesg.getText();
167 
168 		/* Add trailing newline */
169 		text = text ~ '\n';
170 		
171 		/* Store the updated text */
172 		bmesg.setText(text);
173 
174 		return message;
175 	}
176 }
177 
178 version(unittest)
179 {
180 	import std.meta : AliasSeq;
181 	import std.stdio : writeln;
182 }
183 
184 /**
185 * Tests the DefaultLogger
186 */
187 unittest
188 {
189 	DefaultLogger logger = new DefaultLogger();
190 
191 	// Set logging level to at least INFO
192 	logger.setLevel(Level.INFO);
193 
194 	alias testParameters = AliasSeq!("This is a log message", 1.1, true, [1,2,3], 'f', logger);
195 
196 	
197 	// Test various types one-by-one
198 	static foreach(testParameter; testParameters)
199 	{
200 		logger.info(testParameter);
201 	}
202 
203 	// Test various parameters (of various types) all at once
204 	logger.info(testParameters);
205 
206 	// Same as above but with a custom joiner set
207 	logger = new DefaultLogger("(-)");
208 
209 	// Set logging level to at least INFO
210 	logger.setLevel(Level.INFO);
211 
212 	logger.info(testParameters);
213 
214 	writeln();
215 }
216 
217 /**
218  * Printing out some mixed data-types, also using a DEFAULT context 
219  */
220 unittest
221 {
222 	// Create a default logger with the default joiner
223 	DefaultLogger logger = new DefaultLogger();
224 
225 	// Set logging level to at least INFO
226 	logger.setLevel(Level.INFO);
227 	
228 	// Log some stuff
229 	logger.info(["a", "b", "c", "d"], [1, 2], true);
230 
231 	writeln();
232 }
233 
234 /**
235  * Printing out some mixed data-types, also using a DEFAULT context
236  * but also testing out the `error()`, `warn()`, `info()` and `debug()`
237  */
238 unittest
239 {
240 	// Create a default logger with the default joiner
241 	DefaultLogger logger = new DefaultLogger();
242 
243 	// Set logging level to at least DEBUG
244 	logger.setLevel(Level.DEBUG);
245 
246 	// Test out `error()`
247 	logger.error(["woah", "LEVELS!"], 69.420);
248 
249 	// Test out `info()`
250 	logger.info(["woah", "LEVELS!"], 69.420);
251 
252 	// Test out `warn()`
253 	logger.warn(["woah", "LEVELS!"], 69.420);
254 
255 	// Test out `debug_()`
256 	logger.debug_(["woah", "LEVELS!"], 69.420);
257 
258 	// Should not be able to see this
259 	logger.setLevel(Level.INFO);
260 	logger.debug_("Can't see me!");
261 
262 	writeln();
263 }