Introduce `logger`, a simple logging utility for Nit
authorAlexandre Terrasa <alexandre@moz-code.org>
Sat, 15 Jun 2019 01:01:26 +0000 (21:01 -0400)
committerAlexandre Terrasa <alexandre@moz-code.org>
Tue, 18 Jun 2019 00:57:29 +0000 (20:57 -0400)
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

lib/logger/logger.nit [new file with mode: 0644]
lib/logger/package.ini [new file with mode: 0644]

diff --git a/lib/logger/logger.nit b/lib/logger/logger.nit
new file mode 100644 (file)
index 0000000..974ff9c
--- /dev/null
@@ -0,0 +1,402 @@
+# 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
diff --git a/lib/logger/package.ini b/lib/logger/package.ini
new file mode 100644 (file)
index 0000000..f4ba7bd
--- /dev/null
@@ -0,0 +1,12 @@
+[package]
+name=logger
+tags=logging,lib
+maintainer=Alexandre Terrasa <alexandre@moz-code.org>
+license=Apache-2.0
+desc=A simple logger for Nit
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/lib/logger/
+git=https://github.com/nitlang/nit.git
+git.directory=lib/logger/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues