Logging

You can log messages by calling debug, info, warn, or error on Lucky.logger. You can pass in a string or a NamedTuple:

# Log formatter will receive { message: "My message" }
Lucky.logger.info("My message")
# Log formatter will receive { foo: "bar" }
Lucky.logger.info(foo: "bar")

By default, Lucky formats the log output to look like GET 200 / TIMESTAMP (27.0ยตs). This is broken down in to several parts:

Logger Options

You can change the logger configuration in config/log_handler.cr

# Show/Hide the current timestamp of the request. By default this is shown only
# in production
show_timestamps : Bool

# Customize how your log is formatted, and what information it shows. By
# default this is the `DefaultLogFormatter`
log_formatter : Lucky::LogFormatters::Base

# An option to turn on/off logging. By default logging is enabled except for
# when running tests.
enabled : Bool

Log Formatters

To customize your logging output, you can create a custom Log Formatter. Open your config/log_handler.cr file to add this option:

Lucky::LogHandler.configure do |settings|
  settings.show_timestamps = Lucky::Env.production?
  settings.log_formatter = MyCustomFormatter.new
end

Note: Be sure to require this new formatter in your stack. Typically this is done src/app.cr.

The structure for the formatters is a class that inherits from Lucky::LogFormatters::Base and defines the format(context, time, elapsed) : String method. Take a look at this example:

class MyCustomFormatter < Lucky::LogFormatters::Base
  def format(context, time, elapsed)
    case context.response.status_code
    when 200..399
      "๐Ÿ˜"
    when 400..499
      "๐Ÿ˜ฑ"
    when 500..599
      "๐Ÿ˜ญ"
    else
      "๐Ÿง"
    end
  end
end

This method gives you access to the current context : HTTP::Server::Context which contains the request and response objects, time : Time which is the current timestamp, and elapsed : Time::Span which is the amount of time the entire request took to complete.

Lucky has 3 helper methods you can use for some helpful formatting as well.

# Returns the status_code colorized with green, yellow, or red text based on the range of the code
colored_status_code(context.response.status_code)

# Formats the time to ISO_8601_DATE_TIME if the `show_timestamps` option is set to true.
timestamp(time)

# Does some fancy math to display the elapsed span in a human readable format
elapsed_text(elapsed)

Error Handling

When an exception is thrown in your code, you don’t want your user left with a blank page, so Lucky has a built in way of handling these errors.

By default Lucky returns a 500 HTML page or JSON response.

Customizing Error Handling

Let’s say you have an error class MyCustomError in your app. When this error is raised, you want to show a custom error to your users. Open up the Errors::Show in src/actions/errors/show.cr, and add your handle_error method like this.

def handle_error(e : MyCustomError)
  if html?
    render_error_page title: "Custom error message.", status: 418
  else
    json({error: "Oh no!"})
  end
end

If there is no handle_error for the exception, it will fallback to the default one that is generated with every Lucky project: handle_error(e : Exception). You can customize that method however you like!

Error handling in development

When in development, Lucky uses the ExceptionPage shard to display a helpful page with your stack trace, and exception message. The option to display the error page or not is in config/error_handler.cr.

If you need to see how the errors are handled in production (i.e. json response for an api). Set the settings.show_debug_output option to false in config/log_handler.cr.

Default response codes for Exception classes

In general this should be a last resort or for libraries that want to provide default behavior. Usually you should use handle_error methods in Errors::Show because it is far more customizable and much simpler to work with.

If you want to return a special http status code for an Exception class you can do this:

# Define your custom exception
class NotAuthorizedError < Exception
  include Lucky::HttpRespondable

  def http_error_code
    403
  end
end

When NotAuthorizedError is raised, Lucky will use the defined status code, unless you have a handle_error method that changes it.

Error Reporting

There’s many different services out there where you can ship your exceptions off to for better cataloging and searching of the errors.

In your src/actions/errors/show.cr file, there are several different handle_error methods. When an exception occurs in one of your actions, the appropriate method is called passing in the exception. This gives you a chance to report on the error however you like.

# src/actions/errors/show.cr
def handle_error(error : MyCustomError)
  ErrorReporter.report(context, error)
  render Errors::ShowPage, status: 500, title: "Oops!"
end
Next: Writing JSON APIs