Merge: nitrpg: Move `nitrpg` to its own repository
[nit.git] / lib / logger / logger.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # A simple logger for Nit
16 #
17 # ## Basic Usage
18 #
19 # Create a new `Logger` with a severity level threshold set to `warn_level`:
20 #
21 # ~~~
22 # var logger = new Logger(warn_level)
23 # ~~~
24 #
25 # Messages with a severity equal or higher than `warn_level` will be displayed:
26 #
27 # ~~~
28 # logger.error "Displays an error."
29 # logger.warn "Displays a warning."
30 # ~~~
31 #
32 # Messages with a lower severity are silenced:
33 #
34 # ~~~
35 # logger.info "Displays nothing."
36 # ~~~
37 #
38 # `FileLogger` can be used to output the messages into a file:
39 #
40 # ~~~
41 # var log_file = "my.log"
42 #
43 # logger = new FileLogger(warn_level, log_file, append = false)
44 # logger.error("An error")
45 # logger.info("Some info")
46 # logger.close
47 #
48 # assert log_file.to_path.read_all == "An error\n"
49 # log_file.to_path.delete
50 # ~~~
51 #
52 # ## Severity levels
53 #
54 # Each message is associated with a level that indicate its severity.
55 # Only messages with a severity equal to or higher than the logger `level`
56 # threshold will be displayed.
57 #
58 # Severity levels from the most severe to the least severe:
59 #
60 # * `unknown_level`: An unknown message that should always be outputted.
61 # * `fatal_level`: An unhandleable error that results in a program crash.
62 # * `error_level`: A handleable error condition.
63 # * `warn_level`: A warning.
64 # * `info_level`: Generic (useful) information about system operation.
65 # * `debug_level`: Low-level information for developpers.
66 #
67 # ## Formatting messages
68 #
69 # You can create custom formatters by implementing the `Formatter` interface.
70 #
71 # ~~~
72 # class MyFormatter
73 # super Formatter
74 #
75 # redef fun format(level, message) do
76 # if level < warn_level then return super
77 # return "!!!{message}!!!"
78 # end
79 # end
80 # ~~~
81 #
82 # See `DefaultFormatter` for a more advanced implementation example.
83 #
84 # Each Logger can be given a default formatter used to format the every messages
85 # before outputting them:
86 #
87 # ~~~
88 # var formatter = new MyFormatter
89 # var stderr = new StringWriter
90 # var logger = new Logger(warn_level, stderr, formatter)
91 #
92 # logger.warn("This is a warning.")
93 # assert stderr.to_s.trim.split("\n").last == "!!!This is a warning.!!!"
94 # ~~~
95 #
96 # Optionally, a `Formatter` can be given to replace the `default_formatter`
97 # used by default:
98 #
99 # ~~~
100 # # Create a formatter with no default decorator
101 # logger = new Logger(warn_level, stderr, null)
102 #
103 # # Display a message without any formatter
104 # logger.warn("This is a warning.")
105 # assert stderr.to_s.trim.split("\n").last == "This is a warning."
106 #
107 # # Display a message with a custom formatter
108 # logger.warn("This is a warning.", formatter)
109 # assert stderr.to_s.trim.split("\n").last == "!!!This is a warning.!!!"
110 # ~~~
111 module logger
112
113 import console
114
115 # A simple logging utility
116 #
117 # `Logger` provides a simple way to output messages from applications.
118 #
119 # Each message is associated with a level that indicate its severity.
120 # Only messages with a severity equal to or higher than the logger `level`
121 # threshold will be displayed.
122 #
123 # ~~~
124 # var logger = new Logger(warn_level)
125 # assert logger.unknown("unkown")
126 # assert logger.fatal("fatal")
127 # assert logger.error("error")
128 # assert logger.warn("warn")
129 # assert not logger.info("info")
130 # assert not logger.debug("debug")
131 # ~~~
132 class Logger
133
134 # Severity threshold
135 #
136 # Messages with a severity level greater than or equal to `level` will be displayed.
137 # Default is `warn_level`.
138 #
139 # See `unknown_level`, `fatal_level`, error_level``, `warn_level`,
140 # `info_level` and `debug_level`.
141 var level: Int = warn_level is optional, writable
142
143 # Kind of `Writer` used to output messages
144 type OUT: Writer
145
146 # Writer used to output messages
147 #
148 # Default is `stderr`.
149 var out: OUT = stderr is optional
150
151 # Formatter used to format messages before outputting them
152 #
153 # By default no formatter is used.
154 #
155 # See `DefaultFormatter`.
156 var default_formatter: nullable Formatter = null is optional, writable
157
158 # Output a message with `level` severity
159 #
160 # Only output messages with `level` severity greater than of equal to `self.level`.
161 #
162 # ~~~
163 # var stderr = new StringWriter
164 # var logger = new Logger(warn_level, stderr, null)
165 #
166 # # This message will be displayed:
167 # assert logger.warn("This is a warning.")
168 # assert stderr.to_s.trim.split("\n").last == "This is a warning."
169 #
170 # # This message will not:
171 # assert not logger.info("This is some info.")
172 # assert stderr.to_s.trim.split("\n").last == "This is a warning."
173 # ~~~
174 #
175 # Each logger can be given a default formatter used to format the messages
176 # before outputting them:
177 #
178 # ~~~
179 # var formatter = new DefaultFormatter(no_color = true)
180 # logger = new Logger(warn_level, stderr, formatter)
181 # logger.warn("This is a warning.")
182 # assert stderr.to_s.trim.split("\n").last == "Warning: This is a warning."
183 # ~~~
184 #
185 # Optionally, a `Formatter` can be given to replace the `default_formatter`
186 # used by default.
187 #
188 # ~~~
189 # # Create a formatter with no default decorator
190 # logger = new Logger(warn_level, stderr, null)
191 #
192 # # Display a message without any formatter
193 # logger.warn("This is a warning.")
194 # assert stderr.to_s.trim.split("\n").last == "This is a warning."
195 #
196 # # Display a message with a custom formatter
197 # logger.warn("This is a warning.", formatter)
198 # assert stderr.to_s.trim.split("\n").last == "Warning: This is a warning."
199 # ~~~
200 fun add(level: Int, message: Writable, formatter: nullable Formatter): Bool do
201 var format = formatter or else default_formatter
202 if format == null then
203 return add_raw(level, message)
204 end
205 return add_raw(level, format.format(level, message))
206 end
207
208 # Output a message with `level` severity without formatting it
209 #
210 # Only output messages with `level` severity greater than of equal to `self.level`.
211 #
212 # ~~~
213 # var stderr = new StringWriter
214 # var logger = new Logger(warn_level, stderr, null)
215 #
216 # # This message will be displayed:
217 # assert logger.add_raw(warn_level, "This is a warning.")
218 # assert stderr.to_s.trim.split("\n").last == "This is a warning."
219 #
220 # # This message will not:
221 # assert not logger.add_raw(info_level, "This is some info.")
222 # assert stderr.to_s.trim.split("\n").last == "This is a warning."
223 # ~~~
224 fun add_raw(level: Int, message: Writable): Bool do
225 if level < self.level then return false
226 out.write(message.write_to_string)
227 out.write("\n")
228 return true
229 end
230
231 # Output a message with `unknown_level` severity
232 #
233 # Unkown severity messages are always outputted.
234 fun unknown(message: String, formatter: nullable Formatter): Bool do
235 return add(unknown_level, message, formatter)
236 end
237
238 # Output a message with `fatal_level` severity
239 fun fatal(message: String, formatter: nullable Formatter): Bool do
240 return add(fatal_level, message, formatter)
241 end
242
243 # Output a message with `error_level` severity
244 fun error(message: String, formatter: nullable Formatter): Bool do
245 return add(error_level, message, formatter)
246 end
247
248 # Output a message with `warn_level` severity
249 fun warn(message: String, formatter: nullable Formatter): Bool do
250 return add(warn_level, message, formatter)
251 end
252
253 # Output a message with `info_level` severity
254 fun info(message: String, formatter: nullable Formatter): Bool do
255 return add(info_level, message, formatter)
256 end
257
258 # Output a message with `debug` severity
259 fun debug(message: String, formatter: nullable Formatter): Bool do
260 return add(debug_level, message, formatter)
261 end
262 end
263
264 # Log messages to a file
265 #
266 # ~~~
267 # var log_file = "my_file.log"
268 # var logger = new FileLogger(warn_level, log_file, append = false)
269 # logger.error("An error")
270 # logger.info("Some info")
271 # logger.close
272 # assert log_file.to_path.read_all == "An error\n"
273 #
274 # logger = new FileLogger(warn_level, log_file, append = true)
275 # logger.error("Another error")
276 # logger.close
277 # assert log_file.to_path.read_all == "An error\nAnother error\n"
278 #
279 # log_file.to_path.delete
280 # ~~~
281 class FileLogger
282 super Logger
283 autoinit level, file, append, default_formatter
284
285 redef type OUT: FileWriter
286
287 # File where messages will be written
288 var file: String
289
290 # Append messages to `file`
291 #
292 # If `append` is `false`, the `file` will be overwritten.
293 var append: Bool = true is optional
294
295 init do
296 var old = null
297 if append then
298 old = file.to_path.read_all
299 end
300 out = new FileWriter.open(file)
301 out.set_buffering_mode(0, buffer_mode_line)
302 if old != null then
303 out.write(old)
304 end
305 end
306
307 # Close the logger and its `file`
308 fun close do out.close
309 end
310
311 # Format messages before outputing them
312 #
313 # A `Logger` can use a `Formatter` to format the messages before outputting them.
314 #
315 # See `DefaultFormatter`.
316 interface Formatter
317
318 # Format `message` depending of its severity `level`
319 fun format(level: Int, message: Writable): Writable do return message
320 end
321
322 # Default `Logger` formatter
323 #
324 # The default formatter decorates the messages with severity labels and colors.
325 class DefaultFormatter
326 super Formatter
327
328 # Do not decorate messages with colors
329 #
330 # ~~~
331 # var formatter = new DefaultFormatter(no_color = true)
332 # assert formatter.format(error_level, "My message.") == "Error: My message."
333 # ~~~
334 var no_color = false is optional, writable
335
336 redef fun format(level, message) do
337 var string = message.write_to_string
338
339 if level == fatal_level then
340 string = "Fatal: {string}"
341 else if level == error_level then
342 string = "Error: {string}"
343 else if level == warn_level then
344 string = "Warning: {string}"
345 else if level == info_level then
346 string = "Info: {string}"
347 else if level == debug_level then
348 string = "Debug: {string}"
349 end
350
351 if no_color then return string
352
353 if level == fatal_level then
354 return string.red
355 else if level == error_level then
356 return string.red
357 else if level == warn_level then
358 return string.yellow
359 else if level == info_level then
360 return string.purple
361 else if level == debug_level then
362 return string.blue
363 end
364
365 return string
366 end
367 end
368
369 redef class Sys
370
371 # Unknown severity level
372 #
373 # These messages are always displayed.
374 #
375 # See `Logger`.
376 var unknown_level = 5
377
378 # Fatal severity level
379 #
380 # See `Logger`.
381 var fatal_level = 4
382
383 # Error severity level
384 #
385 # See `Logger`.
386 var error_level = 3
387
388 # Warning severity level
389 #
390 # See `Logger`.
391 var warn_level = 2
392
393 # Info severity level
394 #
395 # See `Logger`.
396 var info_level = 1
397
398 # Debug severity level
399 #
400 # See `Logger`.
401 var debug_level = 0
402 end