Log - Crystal 1.14.0

# class Log

- Log
- Reference
- Object

##
Overview

The `Log` class provides a logging utility that you can use to output messages.

The messages, or `Log::Entry` have associated levels, such as `Info` or `Error` that indicate their importance. See `Log::Severity`.

To log a message use the `#trace`, `#debug`, `#info`, `#notice`, `#warn`, `#error`, and `#fatal` methods. They expect a block that will evaluate to the message of the entry:

NOTE To use `Log`, you must explicitly import it with `require "log"`

    require "log"

    Log.info { "Program started" }

Data can be associated with a log entry via the `Log::Emitter` yielded in the logging methods.

    Log.info &.emit("User logged in", user_id: 42)

If you want to log an exception, you can indicate it in the `exception:` named argument.

    Log.warn(exception: e) { "Oh no!" }
    Log.warn exception: e, &.emit("Oh no!", user_id: 42)

The block is only evaluated if the current message is to be emitted to some `Log::Backend`.

To add structured information to the message you can use the `Log::Context`.

When creating log messages they belong to a source. If the top-level `Log` is used as in the above examples its source is the empty string.

The source can be used to identify the module or part of the application that is logging. You can configure for each source a different level to filter the messages.

A recommended pattern is to declare a `Log` constant in the namespace of your shard or module as follows:

    module DB
      Log = ::Log.for("db") # Log for db source

      def do_something
        Log.info { "this is logged in db source" }
      end
    end

    DB::Log.info { "this is also logged in db source" }
    Log.for("db").info { "this is also logged in db source" }
    Log.info { "this is logged in top-level source" }

That way, any `Log.info` call within the `DB` module will use the `db` source. And not the top-level `::Log.info`.

Sources can be nested. Continuing the last example, to declare a `Log` constant `db.pool` source you can do as follows:

    class DB::Pool
      Log = DB::Log.for("pool") # Log for db.pool source
    end

A `Log` will emit the messages to the `Log::Backend`s attached to it as long as the configured severity filter `#level` permits it.

Logs can also be created from a type directly. For the type `DB::Pool` the source `db.pool` will be used. For generic types as `Foo::Bar(Baz)` the source `foo.bar` will be used (i.e. without generic arguments).

    module DB
      Log = ::Log.for(self) # Log for db source
    end

###
Default logging configuration

By default entries from all sources with `Info` and above severity will be logged to `STDOUT` using the `Log::IOBackend`.

If you need to change the default level, backend or sources call `Log.setup` upon startup.

NOTE Calling `.setup` will override previous `.setup` calls.

    Log.setup(:debug)                     # Log debug and above for all sources to STDOUT
    Log.setup("myapp.*, http.*", :notice) # Log notice and above for myapp.* and http.* sources only, and log nothing for any other source.
    backend_with_formatter = Log::IOBackend.new(formatter: custom_formatter)
    Log.setup(:debug, backend_with_formatter) # Log debug and above for all sources to using a custom backend

###
Configure logging explicitly in the code

Use `Log.setup` methods to indicate which sources should go to which backends.

You can indicate actual sources or patterns.

- the empty string matches only the top-level source
- `*` matches all the sources
- `foo.bar.*` matches `foo.bar` and every nested source
- `foo.bar` matches `foo.bar`, but not its nested sources
- Any comma separated combination of the above

The following configuration will setup for all sources to emit warnings (or higher) to `STDOUT`, allow any of the `db.*` and nested source to emit debug (or higher), and to also emit for all sources errors (or higher) to an elasticsearch backend.

    Log.setup do |c|
      backend = Log::IOBackend.new

      c.bind "*", :warn, backend
      c.bind "db.*", :debug, backend
      c.bind "*", :error, ElasticSearchBackend.new("http://localhost:9200")
    end

###
Configure logging from environment variables

Include the following line to allow configuration from environment variables.

    Log.setup_from_env

The environment variable `LOG_LEVEL` is used to indicate which severity level to emit. By default entries from all sources with `Info` and above severity will be logged to `STDOUT` using the `Log::IOBackend`.

To change the level and sources change the environment variable value:

    $ LOG_LEVEL=DEBUG ./bin/app

You can tweak the default values (used when `LOG_LEVEL` variable is not defined):

    Log.setup_from_env(default_level: :error)

##
Defined in:

log.cr
log/dispatch.cr
log/format.cr
log/log.cr
log/main.cr
log/setup.cr
log/spec.cr

##
Constructors

- .for(source : String, level : Severity | Nil = nil) : Log

  Creates a `Log` for the given source.

- .for(type : Class, level : Severity | Nil = nil) : Log

  Creates a `Log` for the given type.

##
Class Method Summary

- .builder : Log::Builder

  Returns the default `Log::Builder` used for `Log.for` calls.

- .capture(source : String = "*", level : Severity = Log::Severity::Trace, *, builder = Log.builder, &)

  Returns and yields an `EntriesChecker` that allows checking specific log entries were emitted.

- .capture(level : Log::Severity = Log::Severity::Trace, *, builder : Log::Builder = Log.builder, &)

  Returns and yields an `EntriesChecker` that allows checking specific log entries were emitted.

- .context : Log::Context

  Returns the current fiber logging context.

- .context=(value : Log::Metadata)

  Sets the current fiber logging context.

- .context=(value : Log::Context)

  Sets the current fiber logging context.

- .debug(*, exception : Exception | Nil = nil, &)

  See `Log#debug`.

- .error(*, exception : Exception | Nil = nil, &)

  See `Log#error`.

- .fatal(*, exception : Exception | Nil = nil, &)

  See `Log#fatal`.

- .info(*, exception : Exception | Nil = nil, &)

  See `Log#info`.

- .notice(*, exception : Exception | Nil = nil, &)

  See `Log#notice`.

- .progname

  The program name used for log entries

- .progname=(progname : String)

  The program name used for log entries

- .setup(*, builder : Log::Builder = Log.builder, &)

  Setups logging bindings discarding all previous configurations.

- .setup(sources : String = "*", level : Log::Severity = Log::Severity::Info, backend : Log::Backend = IOBackend.new, *, builder : Log::Builder = Log.builder)

  Setups logging for sources using the specified level, backend.

- .setup(level : Log::Severity = Log::Severity::Info, backend : Log::Backend = IOBackend.new, *, builder : Log::Builder = Log.builder)

  Setups logging for all sources using the specified level, backend.

- .setup_from_env(*, builder : Log::Builder = Log.builder, default_level : Log::Severity = Log::Severity::Info, default_sources = "*", log_level_env = "LOG_LEVEL", backend = Log::IOBackend.new)

  Setups logging based on `LOG_LEVEL` environment variable.

- .trace(*, exception : Exception | Nil = nil, &)

  See `Log#trace`.

- .warn(*, exception : Exception | Nil = nil, &)

  See `Log#warn`.

- .with_context(values, &)

  Method to save and restore the current logging context.

- .with_context(**kwargs, &)

  Method to save and restore the current logging context.

##
Macro Summary

- define_formatter(name, pattern)

  Generate subclasses of `Log::StaticFormatter` from a string with interpolations

##
Instance Method Summary

- #backend : Backend | Nil
- #context : Log::Context

  Returns the current fiber logging context.

- #context=(value : Log::Metadata | Log::Context)

  Sets the current fiber logging context.

- #debug(*, exception : Exception | Nil = nil, &)

  Logs a message if the logger's current severity is lower than or equal to `Severity::Debug`.

- #error(*, exception : Exception | Nil = nil, &)

  Logs a message if the logger's current severity is lower than or equal to `Severity::Error`.

- #fatal(*, exception : Exception | Nil = nil, &)

  Logs a message if the logger's current severity is lower than or equal to `Severity::Fatal`.

- #finalize : Nil
- #for(child_source : String, level : Severity | Nil = nil) : Log

  Creates a `Log` for the given nested source.

- #for(type : Class, level : Severity | Nil = nil) : Log

  Creates a `Log` for the given type.

- #info(*, exception : Exception | Nil = nil, &)

  Logs a message if the logger's current severity is lower than or equal to `Severity::Info`.

- #level : Severity
- #level=(value : Severity)

  Change this log severity level filter.

- #notice(*, exception : Exception | Nil = nil, &)

  Logs a message if the logger's current severity is lower than or equal to `Severity::Notice`.

- #source : String
- #trace(*, exception : Exception | Nil = nil, &)

  Logs a message if the logger's current severity is lower than or equal to `Severity::Trace`.

- #warn(*, exception : Exception | Nil = nil, &)

  Logs a message if the logger's current severity is lower than or equal to `Severity::Warn`.

- #with_context(values, &)

  Method to save and restore the current logging context.

- #with_context(**kwargs, &)

  Method to save and restore the current logging context.

### Instance methods inherited from class `Reference`

==(other : self)
==(other : JSON::Any)
==(other : YAML::Any)
==(other) ==, dup dup, hash(hasher) hash, initialize initialize, inspect(io : IO) : Nil inspect, object_id : UInt64 object_id, pretty_print(pp) : Nil pretty_print, same?(other : Reference) : Bool
same?(other : Nil) same?, to_s(io : IO) : Nil to_s

### Constructor methods inherited from class `Reference`

new new, unsafe_construct(address : Pointer, *args, **opts) : self unsafe_construct

### Class methods inherited from class `Reference`

pre_initialize(address : Pointer) pre_initialize

### Instance methods inherited from class `Object`

! : Bool !, !=(other) !=, !~(other) !~, ==(other) ==, ===(other : JSON::Any)
===(other : YAML::Any)
===(other) ===, =~(other) =~, as(type : Class) as, as?(type : Class) as?, class class, dup dup, hash(hasher)
hash hash, in?(collection : Object) : Bool
in?(*values : Object) : Bool in?, inspect(io : IO) : Nil
inspect : String inspect, is_a?(type : Class) : Bool is_a?, itself itself, nil? : Bool nil?, not_nil!(message)
not_nil! not_nil!, pretty_inspect(width = 79, newline = "\n", indent = 0) : String pretty_inspect, pretty_print(pp : PrettyPrint) : Nil pretty_print, responds_to?(name : Symbol) : Bool responds_to?, tap(&) tap, to_json(io : IO) : Nil
to_json : String to_json, to_pretty_json(indent : String = " ") : String
to_pretty_json(io : IO, indent : String = " ") : Nil to_pretty_json, to_s(io : IO) : Nil
to_s : String to_s, to_yaml(io : IO) : Nil
to_yaml : String to_yaml, try(&) try, unsafe_as(type : T.class) forall T unsafe_as

### Class methods inherited from class `Object`

from_json(string_or_io, root : String)
from_json(string_or_io) from_json, from_yaml(string_or_io : String | IO) from_yaml

### Macros inherited from class `Object`

class_getter(*names, &block) class_getter, class_getter!(*names) class_getter!, class_getter?(*names, &block) class_getter?, class_property(*names, &block) class_property, class_property!(*names) class_property!, class_property?(*names, &block) class_property?, class_setter(*names) class_setter, def_clone def_clone, def_equals(*fields) def_equals, def_equals_and_hash(*fields) def_equals_and_hash, def_hash(*fields) def_hash, delegate(*methods, to object) delegate, forward_missing_to(delegate) forward_missing_to, getter(*names, &block) getter, getter!(*names) getter!, getter?(*names, &block) getter?, property(*names, &block) property, property!(*names) property!, property?(*names, &block) property?, setter(*names) setter

##
Constructor Detail

def self.for(source : String, level : Severity | Nil = nil) : Log #

Creates a `Log` for the given source. If level is given, it will override the configuration.

def self.for(type : Class, level : Severity | Nil = nil) : Log #

Creates a `Log` for the given type. A type `Foo::Bar(Baz)` corresponds to the source `foo.bar`. If level is given, it will override the configuration.

##
Class Method Detail

def self.builder : Log::Builder #

Returns the default `Log::Builder` used for `Log.for` calls.

def self.capture(source : String = "*", level : Severity = Log::Severity::Trace, *, builder = Log.builder, &) #

Returns and yields an `EntriesChecker` that allows checking specific log entries were emitted.

This capture will even work if there are currently no backends configured, effectively adding a temporary backend.

    require "spec"
    require "log"
    require "log/spec"

    Log.setup(:none)

    def greet(name)
      Log.info { "Greeting #{name}" }
    end

    it "greets" do
      Log.capture do |logs|
        greet("Harry")
        greet("Hermione")
        greet("Ron")

        logs.check(:info, /greeting harry/i)
        logs.next(:info, /greeting hermione/i)
      end
    end

By default logs of all sources and severities will be captured.

Use level to only capture of the given severity or above.

Use source to narrow which source are captured. Values that represent single pattern like `http.*` are allowed.

The `EntriesChecker` will hold a list of emitted entries.

`EntriesChecker#check` will find the next entry which matches the level and message. `EntriesChecker#next` will validate that the following entry in the list matches the given level and message. `EntriesChecker#clear` will clear the emitted and captured entries.

With these methods it is possible to express expected traces in either a strict or loose way, while checking ordering.

`EntriesChecker#entry` returns the last matched `Entry`. Useful to check additional entry properties other than the message.

`EntriesChecker#empty` validates there are no pending entries to match.

Using the yielded `EntriesChecker` allows clearing the entries between statements.

Invocations can be nested in order to capture each source in their own `EntriesChecker`.

def self.capture(level : Log::Severity = Log::Severity::Trace, *, builder : Log::Builder = Log.builder, &) #

Returns and yields an `EntriesChecker` that allows checking specific log entries were emitted.

This capture will even work if there are currently no backends configured, effectively adding a temporary backend.

    require "spec"
    require "log"
    require "log/spec"

    Log.setup(:none)

    def greet(name)
      Log.info { "Greeting #{name}" }
    end

    it "greets" do
      Log.capture do |logs|
        greet("Harry")
        greet("Hermione")
        greet("Ron")

        logs.check(:info, /greeting harry/i)
        logs.next(:info, /greeting hermione/i)
      end
    end

By default logs of all sources and severities will be captured.

Use level to only capture of the given severity or above.

Use source to narrow which source are captured. Values that represent single pattern like `http.*` are allowed.

The `EntriesChecker` will hold a list of emitted entries.

`EntriesChecker#check` will find the next entry which matches the level and message. `EntriesChecker#next` will validate that the following entry in the list matches the given level and message. `EntriesChecker#clear` will clear the emitted and captured entries.

With these methods it is possible to express expected traces in either a strict or loose way, while checking ordering.

`EntriesChecker#entry` returns the last matched `Entry`. Useful to check additional entry properties other than the message.

`EntriesChecker#empty` validates there are no pending entries to match.

Using the yielded `EntriesChecker` allows clearing the entries between statements.

Invocations can be nested in order to capture each source in their own `EntriesChecker`.

def self.context : Log::Context #

Returns the current fiber logging context.

def self.context=(value : Log::Metadata) #

Sets the current fiber logging context.

def self.context=(value : Log::Context) #

Sets the current fiber logging context.

def self.debug(*, exception : Exception | Nil = nil, &) #

See `Log#debug`.

def self.error(*, exception : Exception | Nil = nil, &) #

See `Log#error`.

def self.fatal(*, exception : Exception | Nil = nil, &) #

See `Log#fatal`.

def self.info(*, exception : Exception | Nil = nil, &) #

See `Log#info`.

def self.notice(*, exception : Exception | Nil = nil, &) #

See `Log#notice`.

def self.progname #

The program name used for log entries

Defaults to the executable name

def self.progname=(progname : String) #

The program name used for log entries

Defaults to the executable name

def self.setup(*, builder : Log::Builder = Log.builder, &) #

Setups logging bindings discarding all previous configurations.

def self.setup(sources : String = "*", level : Log::Severity = Log::Severity::Info, backend : Log::Backend = IOBackend.new, *, builder : Log::Builder = Log.builder) #

Setups logging for sources using the specified level, backend.

def self.setup(level : Log::Severity = Log::Severity::Info, backend : Log::Backend = IOBackend.new, *, builder : Log::Builder = Log.builder) #

Setups logging for all sources using the specified level, backend.

def self.setup_from_env(*, builder : Log::Builder = Log.builder, default_level : Log::Severity = Log::Severity::Info, default_sources = "*", log_level_env = "LOG_LEVEL", backend = Log::IOBackend.new) #

Setups logging based on `LOG_LEVEL` environment variable.

def self.trace(*, exception : Exception | Nil = nil, &) #

See `Log#trace`.

def self.warn(*, exception : Exception | Nil = nil, &) #

See `Log#warn`.

def self.with_context(values, &) #

Method to save and restore the current logging context. Temporary context for the duration of the block can be set via arguments.

    Log.context.set a: 1
    Log.info { %(message with {"a" => 1} context) }
    Log.with_context(b: 2) do
      Log.context.set c: 3
      Log.info { %(message with {"a" => 1, "b" => 2, "c" => 3} context) }
    end
    Log.info { %(message with {"a" => 1} context) }

def self.with_context(**kwargs, &) #

Method to save and restore the current logging context. Temporary context for the duration of the block can be set via arguments.

    Log.context.set a: 1
    Log.info { %(message with {"a" => 1} context) }
    Log.with_context(b: 2) do
      Log.context.set c: 3
      Log.info { %(message with {"a" => 1, "b" => 2, "c" => 3} context) }
    end
    Log.info { %(message with {"a" => 1} context) }

##
Macro Detail

macro define_formatter(name, pattern) #

Generate subclasses of `Log::StaticFormatter` from a string with interpolations

Example:

    Log.define_formatter MyFormat, "- #{severity}: #{message}"

See `Log::StaticFormatter` for the available methods that can be called within the interpolations.

##
Instance Method Detail

def backend : Backend | Nil #
def context : Log::Context #

Returns the current fiber logging context.

def context=(value : Log::Metadata | Log::Context) #

Sets the current fiber logging context.

def debug(*, exception : Exception | Nil = nil, &) #

Logs a message if the logger's current severity is lower than or equal to `Severity::Debug`.

The block is not called unless the current severity level would emit a message.

Blocks which return `nil` do not emit anything:

    Log.debug do
      if false
        "Nothing will be logged."
      end
    end

def error(*, exception : Exception | Nil = nil, &) #

Logs a message if the logger's current severity is lower than or equal to `Severity::Error`.

The block is not called unless the current severity level would emit a message.

Blocks which return `nil` do not emit anything:

    Log.error do
      if false
        "Nothing will be logged."
      end
    end

def fatal(*, exception : Exception | Nil = nil, &) #

Logs a message if the logger's current severity is lower than or equal to `Severity::Fatal`.

The block is not called unless the current severity level would emit a message.

Blocks which return `nil` do not emit anything:

    Log.fatal do
      if false
        "Nothing will be logged."
      end
    end

def finalize : Nil #
def for(child_source : String, level : Severity | Nil = nil) : Log #

Creates a `Log` for the given nested source. If level is given, it will override the configuration.

def for(type : Class, level : Severity | Nil = nil) : Log #

Creates a `Log` for the given type. A type `Foo::Bar(Baz)` corresponds to the source `foo.bar`. If level is given, it will override the configuration.

def info(*, exception : Exception | Nil = nil, &) #

Logs a message if the logger's current severity is lower than or equal to `Severity::Info`.

The block is not called unless the current severity level would emit a message.

Blocks which return `nil` do not emit anything:

    Log.info do
      if false
        "Nothing will be logged."
      end
    end

def level : Severity #
def level=(value : Severity) #

Change this log severity level filter.

def notice(*, exception : Exception | Nil = nil, &) #

Logs a message if the logger's current severity is lower than or equal to `Severity::Notice`.

The block is not called unless the current severity level would emit a message.

Blocks which return `nil` do not emit anything:

    Log.notice do
      if false
        "Nothing will be logged."
      end
    end

def source : String #
def trace(*, exception : Exception | Nil = nil, &) #

Logs a message if the logger's current severity is lower than or equal to `Severity::Trace`.

The block is not called unless the current severity level would emit a message.

Blocks which return `nil` do not emit anything:

    Log.trace do
      if false
        "Nothing will be logged."
      end
    end

def warn(*, exception : Exception | Nil = nil, &) #

Logs a message if the logger's current severity is lower than or equal to `Severity::Warn`.

The block is not called unless the current severity level would emit a message.

Blocks which return `nil` do not emit anything:

    Log.warn do
      if false
        "Nothing will be logged."
      end
    end

def with_context(values, &) #

Method to save and restore the current logging context. Temporary context for the duration of the block can be set via arguments.

    Log.context.set a: 1
    Log.info { %(message with {"a" => 1} context) }
    Log.with_context(b: 2) do
      Log.context.set c: 3
      Log.info { %(message with {"a" => 1, "b" => 2, "c" => 3} context) }
    end
    Log.info { %(message with {"a" => 1} context) }

def with_context(**kwargs, &) #

Method to save and restore the current logging context. Temporary context for the duration of the block can be set via arguments.

    Log.context.set a: 1
    Log.info { %(message with {"a" => 1} context) }
    Log.with_context(b: 2) do
      Log.context.set c: 3
      Log.info { %(message with {"a" => 1, "b" => 2, "c" => 3} context) }
    end
    Log.info { %(message with {"a" => 1} context) }