--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A simple logger for Nit
+#
+# ## Basic Usage
+#
+# Create a new `Logger` with a severity level threshold set to `warn_level`:
+#
+# ~~~
+# var logger = new Logger(warn_level)
+# ~~~
+#
+# Messages with a severity equal or higher than `warn_level` will be displayed:
+#
+# ~~~
+# logger.error "Displays an error."
+# logger.warn "Displays a warning."
+# ~~~
+#
+# Messages with a lower severity are silenced:
+#
+# ~~~
+# logger.info "Displays nothing."
+# ~~~
+#
+# `FileLogger` can be used to output the messages into a file:
+#
+# ~~~
+# var log_file = "my.log"
+#
+# logger = new FileLogger(warn_level, log_file, append = false)
+# logger.error("An error")
+# logger.info("Some info")
+# logger.close
+#
+# assert log_file.to_path.read_all == "An error\n"
+# log_file.to_path.delete
+# ~~~
+#
+# ## Severity levels
+#
+# Each message is associated with a level that indicate its severity.
+# Only messages with a severity equal to or higher than the logger `level`
+# threshold will be displayed.
+#
+# Severity levels from the most severe to the least severe:
+#
+# * `unknown_level`: An unknown message that should always be outputted.
+# * `fatal_level`: An unhandleable error that results in a program crash.
+# * `error_level`: A handleable error condition.
+# * `warn_level`: A warning.
+# * `info_level`: Generic (useful) information about system operation.
+# * `debug_level`: Low-level information for developpers.
+#
+# ## Formatting messages
+#
+# You can create custom formatters by implementing the `Formatter` interface.
+#
+# ~~~
+# class MyFormatter
+# super Formatter
+#
+# redef fun format(level, message) do
+# if level < warn_level then return super
+# return "!!!{message}!!!"
+# end
+# end
+# ~~~
+#
+# See `DefaultFormatter` for a more advanced implementation example.
+#
+# Each Logger can be given a default formatter used to format the every messages
+# before outputting them:
+#
+# ~~~
+# var formatter = new MyFormatter
+# var stderr = new StringWriter
+# var logger = new Logger(warn_level, stderr, formatter)
+#
+# logger.warn("This is a warning.")
+# assert stderr.to_s.trim.split("\n").last == "!!!This is a warning.!!!"
+# ~~~
+#
+# Optionally, a `Formatter` can be given to replace the `default_formatter`
+# used by default:
+#
+# ~~~
+# # Create a formatter with no default decorator
+# logger = new Logger(warn_level, stderr, null)
+#
+# # Display a message without any formatter
+# logger.warn("This is a warning.")
+# assert stderr.to_s.trim.split("\n").last == "This is a warning."
+#
+# # Display a message with a custom formatter
+# logger.warn("This is a warning.", formatter)
+# assert stderr.to_s.trim.split("\n").last == "!!!This is a warning.!!!"
+# ~~~
+module logger
+
+import console
+
+# A simple logging utility
+#
+# `Logger` provides a simple way to output messages from applications.
+#
+# Each message is associated with a level that indicate its severity.
+# Only messages with a severity equal to or higher than the logger `level`
+# threshold will be displayed.
+#
+# ~~~
+# var logger = new Logger(warn_level)
+# assert logger.unknown("unkown")
+# assert logger.fatal("fatal")
+# assert logger.error("error")
+# assert logger.warn("warn")
+# assert not logger.info("info")
+# assert not logger.debug("debug")
+# ~~~
+class Logger
+
+ # Severity threshold
+ #
+ # Messages with a severity level greater than or equal to `level` will be displayed.
+ # Default is `warn_level`.
+ #
+ # See `unknown_level`, `fatal_level`, error_level``, `warn_level`,
+ # `info_level` and `debug_level`.
+ var level: Int = warn_level is optional, writable
+
+ # Kind of `Writer` used to output messages
+ type OUT: Writer
+
+ # Writer used to output messages
+ #
+ # Default is `stderr`.
+ var out: OUT = stderr is optional
+
+ # Formatter used to format messages before outputting them
+ #
+ # By default no formatter is used.
+ #
+ # See `DefaultFormatter`.
+ var default_formatter: nullable Formatter = null is optional, writable
+
+ # Output a message with `level` severity
+ #
+ # Only output messages with `level` severity greater than of equal to `self.level`.
+ #
+ # ~~~
+ # var stderr = new StringWriter
+ # var logger = new Logger(warn_level, stderr, null)
+ #
+ # # This message will be displayed:
+ # assert logger.warn("This is a warning.")
+ # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+ #
+ # # This message will not:
+ # assert not logger.info("This is some info.")
+ # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+ # ~~~
+ #
+ # Each logger can be given a default formatter used to format the messages
+ # before outputting them:
+ #
+ # ~~~
+ # var formatter = new DefaultFormatter(no_color = true)
+ # logger = new Logger(warn_level, stderr, formatter)
+ # logger.warn("This is a warning.")
+ # assert stderr.to_s.trim.split("\n").last == "Warning: This is a warning."
+ # ~~~
+ #
+ # Optionally, a `Formatter` can be given to replace the `default_formatter`
+ # used by default.
+ #
+ # ~~~
+ # # Create a formatter with no default decorator
+ # logger = new Logger(warn_level, stderr, null)
+ #
+ # # Display a message without any formatter
+ # logger.warn("This is a warning.")
+ # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+ #
+ # # Display a message with a custom formatter
+ # logger.warn("This is a warning.", formatter)
+ # assert stderr.to_s.trim.split("\n").last == "Warning: This is a warning."
+ # ~~~
+ fun add(level: Int, message: Writable, formatter: nullable Formatter): Bool do
+ var format = formatter or else default_formatter
+ if format == null then
+ return add_raw(level, message)
+ end
+ return add_raw(level, format.format(level, message))
+ end
+
+ # Output a message with `level` severity without formatting it
+ #
+ # Only output messages with `level` severity greater than of equal to `self.level`.
+ #
+ # ~~~
+ # var stderr = new StringWriter
+ # var logger = new Logger(warn_level, stderr, null)
+ #
+ # # This message will be displayed:
+ # assert logger.add_raw(warn_level, "This is a warning.")
+ # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+ #
+ # # This message will not:
+ # assert not logger.add_raw(info_level, "This is some info.")
+ # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+ # ~~~
+ fun add_raw(level: Int, message: Writable): Bool do
+ if level < self.level then return false
+ out.write(message.write_to_string)
+ out.write("\n")
+ return true
+ end
+
+ # Output a message with `unknown_level` severity
+ #
+ # Unkown severity messages are always outputted.
+ fun unknown(message: String, formatter: nullable Formatter): Bool do
+ return add(unknown_level, message, formatter)
+ end
+
+ # Output a message with `fatal_level` severity
+ fun fatal(message: String, formatter: nullable Formatter): Bool do
+ return add(fatal_level, message, formatter)
+ end
+
+ # Output a message with `error_level` severity
+ fun error(message: String, formatter: nullable Formatter): Bool do
+ return add(error_level, message, formatter)
+ end
+
+ # Output a message with `warn_level` severity
+ fun warn(message: String, formatter: nullable Formatter): Bool do
+ return add(warn_level, message, formatter)
+ end
+
+ # Output a message with `info_level` severity
+ fun info(message: String, formatter: nullable Formatter): Bool do
+ return add(info_level, message, formatter)
+ end
+
+ # Output a message with `debug` severity
+ fun debug(message: String, formatter: nullable Formatter): Bool do
+ return add(debug_level, message, formatter)
+ end
+end
+
+# Log messages to a file
+#
+# ~~~
+# var log_file = "my_file.log"
+# var logger = new FileLogger(warn_level, log_file, append = false)
+# logger.error("An error")
+# logger.info("Some info")
+# logger.close
+# assert log_file.to_path.read_all == "An error\n"
+#
+# logger = new FileLogger(warn_level, log_file, append = true)
+# logger.error("Another error")
+# logger.close
+# assert log_file.to_path.read_all == "An error\nAnother error\n"
+#
+# log_file.to_path.delete
+# ~~~
+class FileLogger
+ super Logger
+ autoinit level, file, append, default_formatter
+
+ redef type OUT: FileWriter
+
+ # File where messages will be written
+ var file: String
+
+ # Append messages to `file`
+ #
+ # If `append` is `false`, the `file` will be overwritten.
+ var append: Bool = true is optional
+
+ init do
+ var old = null
+ if append then
+ old = file.to_path.read_all
+ end
+ out = new FileWriter.open(file)
+ out.set_buffering_mode(0, buffer_mode_line)
+ if old != null then
+ out.write(old)
+ end
+ end
+
+ # Close the logger and its `file`
+ fun close do out.close
+end
+
+# Format messages before outputing them
+#
+# A `Logger` can use a `Formatter` to format the messages before outputting them.
+#
+# See `DefaultFormatter`.
+interface Formatter
+
+ # Format `message` depending of its severity `level`
+ fun format(level: Int, message: Writable): Writable do return message
+end
+
+# Default `Logger` formatter
+#
+# The default formatter decorates the messages with severity labels and colors.
+class DefaultFormatter
+ super Formatter
+
+ # Do not decorate messages with colors
+ #
+ # ~~~
+ # var formatter = new DefaultFormatter(no_color = true)
+ # assert formatter.format(error_level, "My message.") == "Error: My message."
+ # ~~~
+ var no_color = false is optional, writable
+
+ redef fun format(level, message) do
+ var string = message.write_to_string
+
+ if level == fatal_level then
+ string = "Fatal: {string}"
+ else if level == error_level then
+ string = "Error: {string}"
+ else if level == warn_level then
+ string = "Warning: {string}"
+ else if level == info_level then
+ string = "Info: {string}"
+ else if level == debug_level then
+ string = "Debug: {string}"
+ end
+
+ if no_color then return string
+
+ if level == fatal_level then
+ return string.red
+ else if level == error_level then
+ return string.red
+ else if level == warn_level then
+ return string.yellow
+ else if level == info_level then
+ return string.purple
+ else if level == debug_level then
+ return string.blue
+ end
+
+ return string
+ end
+end
+
+redef class Sys
+
+ # Unknown severity level
+ #
+ # These messages are always displayed.
+ #
+ # See `Logger`.
+ var unknown_level = 5
+
+ # Fatal severity level
+ #
+ # See `Logger`.
+ var fatal_level = 4
+
+ # Error severity level
+ #
+ # See `Logger`.
+ var error_level = 3
+
+ # Warning severity level
+ #
+ # See `Logger`.
+ var warn_level = 2
+
+ # Info severity level
+ #
+ # See `Logger`.
+ var info_level = 1
+
+ # Debug severity level
+ #
+ # See `Logger`.
+ var debug_level = 0
+end