You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cartodb/doc/logging.md

4.3 KiB

Logging

General guidelines to be applied project-wide are described in our internal wiki, while this page focuses on specific usage from the CartoDB application.

Important context

Log messages are processed by our ELK stack. Each log message maps into a set of fields, which then are inserted in Elaticsearch. These fields have an associated type, which is automatically assigned by Elaticsearch when a new field name is received.

This has the caveat that if a subsequent log message is received with the same field name but a different type (ex: 1 vs "1"), the log message is lost because the insertion in the Elasticsearch index fails.

To mitigate this, please review existing logging messages to check the format used and existing logging conventions before writing new entries.

Logger structure

The core of the logging sytem (Carto::Common::Logger) is in our shared code repository, cartodb-common. It contains modifications of the Rails standard logger: JSON formatting and additional attributes for the default logs.

This repository wraps Carto::Common::Logger with a ::LoggerHelper class, which adds some serialization logic and provides the following methods:

log_debug(message: 'Foo')
log_info(message: 'Foo')
log_warning(message: 'Foo')
log_error(message: 'Foo')   # Reports to Rollbar
log_fatal(message: 'Foo')   # Reports to Rollbar

Guidelines

1. Be explicit about the attribute names logged

Use:

log_error(message: 'Foo', table_id: 123)

Rather than:

log_error(message: 'Foo', table: 123)

2. Reuse existing fields when possible

Field name Kibana field Kibana type Description
message event_message String Self-descriptive
current_user cdb-user String Username performing (or affected by) the action
target_user target_user String Username affected by the action. Use only when the actor of the action is not the same as the receiver
organization organization String Name of the organization
exception exception Nested JSON An Exception object
error_detail error_detail String Additional error details

3. Abstract common logger information

If the logging messages written in a class share a set of common fields, try to abstract it by defining a log_context method.

For example:

module Carto
  module Api
    class GroupsController < ::Api::ApplicationController

      def create
        # ...
      rescue StandardError => e
        log_error(exception: e)
        head 500
      end

      def update
        # ...
      rescue StandardError => e
        log_error(exception: e)
        head 500
      end

      private

      def log_context
        super.merge(group: @group, organization: @organization)
      end

    end
  end
end

Examples

Logging the current user:

log_info(message: 'Foo', current_user: Carto::User.first)
log_info(message: 'Foo', current_user: User.first)
log_info(message: 'Foo', current_user: 'some-username')
# Serialized as:
# { 'event_message': 'Foo', 'cdb-user': 'some-username' }

Logging an action User A performed on User B:

log_info(message: 'Foo', current_user: user_a, target_user: user_b)
# Serialized as:
# { 'event_message': 'Foo', 'cdb-user': 'username-a', 'target_user': 'username-b' }

Logging organizations:

log_info(message: 'Foo', organization: Carto::Organization.first)
log_info(message: 'Foo', organization: Organization.first)
log_info(message: 'Foo', organization: 'organization-name')
# Serialized as:
# { 'event_message': 'Foo', 'organization': 'organization-name' }

Logging a captured exception:

log_error(message: 'Foo', exception: StandardError.new('My error'))
# Serialized as:
# {
#   'event_message': 'Foo',
#   'exception': {
#     'class': 'StandardError',
#     'message': 'My error',
#     'backtrace_hint': ['line_1', 'line_2', 'line_3']
#   }
# }

Logging a failed ActiveRecord validation:

invalid_user.save
log_warning(message: 'Unable to save user', current_user: invalid_user, error_detail: invalid_user.errors.full_messages.join(','))