cartodb-4.42/doc/logging.md
2024-04-06 05:25:13 +00:00

142 lines
4.3 KiB
Markdown

# Logging
General guidelines to be applied project-wide are described in our [internal wiki](http://doc-internal.cartodb.net/platform/guidelines.html#structured-logging), 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](https://github.com/CartoDB/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:
```ruby
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:
```ruby
log_error(message: 'Foo', table_id: 123)
```
Rather than:
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
invalid_user.save
log_warning(message: 'Unable to save user', current_user: invalid_user, error_detail: invalid_user.errors.full_messages.join(','))
```