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:

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:

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.!!!"

Introduced classes

class DefaultFormatter

logger :: DefaultFormatter

Default Logger formatter
class FileLogger

logger :: FileLogger

Log messages to a file
interface Formatter

logger :: Formatter

Format messages before outputing them
class Logger

logger :: Logger

A simple logging utility

Redefined classes

redef class Sys

logger :: logger $ Sys

The main class of the program.

All class definitions

class DefaultFormatter

logger $ DefaultFormatter

Default Logger formatter
class FileLogger

logger $ FileLogger

Log messages to a file
interface Formatter

logger $ Formatter

Format messages before outputing them
class Logger

logger $ Logger

A simple logging utility
redef class Sys

logger :: logger $ Sys

The main class of the program.
package_diagram logger::logger logger console console logger::logger->console core core console->core ...core ... ...core->core github::wallet wallet github::wallet->logger::logger popcorn::pop_logging pop_logging popcorn::pop_logging->logger::logger github::loader loader github::loader->github::wallet github::loader->popcorn::pop_logging github::loader... ... github::loader...->github::loader popcorn::popcorn popcorn popcorn::popcorn->popcorn::pop_logging popcorn::popcorn... ... popcorn::popcorn...->popcorn::popcorn

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module array

core :: array

This module introduces the standard array structure.
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module native

core :: native

Native structures for text and bytes
module numeric

core :: numeric

Advanced services for Numeric types
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O

Parents

module console

console :: console

Defines some ANSI Terminal Control Escape Sequences.

Children

module wallet

github :: wallet

Github OAuth tokens management

Descendants

# 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
lib/logger/logger.nit:15,1--402,3