Skip to content
This repository has been archived by the owner on Dec 8, 2020. It is now read-only.

Commit

Permalink
Cleanup attribute normalization & resolve string encoding normalization
Browse files Browse the repository at this point in the history
  • Loading branch information
binarylogic committed Oct 28, 2017
1 parent b714ca3 commit 5e7831d
Show file tree
Hide file tree
Showing 33 changed files with 561 additions and 344 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Fixed

- Encoding and rewind issues for file upload parameters have been resolved. Timber
improved attribute normalization across all contexts and events, ignoring binary
values like this in general.

## [2.5.1] - 2017-10-27

### Fixed
Expand Down
3 changes: 2 additions & 1 deletion lib/timber/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def call(severity, timestamp, progname, msg)
end
end

DEFAULT_HTTP_BODY_LIMIT = 2048.freeze
DEVELOPMENT_NAME = "development".freeze
PRODUCTION_NAME = "production".freeze
STAGING_NAME = "staging".freeze
Expand All @@ -36,7 +37,7 @@ def call(severity, timestamp, progname, msg)

# @private
def initialize
@http_body_limit = 2048
@http_body_limit = DEFAULT_HTTP_BODY_LIMIT
end

# Convenience method for logging debug statements to the debug logger
Expand Down
13 changes: 10 additions & 3 deletions lib/timber/contexts/custom.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,20 @@ class Custom < Context
attr_reader :type, :data

def initialize(attributes)
@type = Timber::Util::Object.try(attributes[:type], :to_sym) || raise(ArgumentError.new(":type is required"))
@data = attributes[:data] || raise(ArgumentError.new(":data is required"))
normalizer = Util::AttributeNormalizer.new(attributes)
@type = normalizer.fetch!(:type, :symbol)
@data = normalizer.fetch!(:data, :hash)
end

# Builds a hash representation containing simple objects, suitable for serialization (JSON).
def to_hash
@to_hash ||= Util::NonNilHashBuilder.build do |h|
h.add(type, data)
end
end

def as_json(options = {})
{type => data}
to_hash
end
end
end
Expand Down
30 changes: 23 additions & 7 deletions lib/timber/contexts/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,38 @@ module Contexts
# @note This context should be installed automatically through the,
# {Intregrations::Rack::HTTPContext} Rack middleware.
class HTTP < Context
HOST_MAX_BYTES = 256.freeze
METHOD_MAX_BYTES = 20.freeze
PATH_MAX_BYTES = 2048.freeze
REMOTE_ADDR_MAX_BYTES = 256.freeze
REQUEST_ID_MAX_BYTES = 256.freeze

@keyspace = :http

attr_reader :host, :method, :path, :remote_addr, :request_id

def initialize(attributes)
@host = attributes[:host]
@method = attributes[:method] || raise(ArgumentError.new(":method is required"))
@path = attributes[:path]
@remote_addr = attributes[:remote_addr]
@request_id = attributes[:request_id]
normalizer = Util::AttributeNormalizer.new(attributes)
@host = normalizer.fetch(:host, :string, :limit => HOST_MAX_BYTES)
@method = normalizer.fetch!(:method, :string, :upcase => true, :limit => METHOD_MAX_BYTES)
@path = normalizer.fetch(:path, :string, :limit => PATH_MAX_BYTES)
@remote_addr = normalizer.fetch(:remote_addr, :string, :limit => REMOTE_ADDR_MAX_BYTES)
@request_id = normalizer.fetch(:request_id, :string, :limit => REQUEST_ID_MAX_BYTES)
end

# Builds a hash representation containing simple objects, suitable for serialization (JSON).
def to_hash
@to_hash ||= Util::NonNilHashBuilder.build do |h|
h.add(:host, host)
h.add(:method, method)
h.add(:path, path)
h.add(:remote_addr, remote_addr)
h.add(:request_id, request_id)
end
end

def as_json(_options = {})
{:host => host, :method => method, :path => path, :remote_addr => remote_addr,
:request_id => request_id}
to_hash
end
end
end
Expand Down
17 changes: 14 additions & 3 deletions lib/timber/contexts/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,29 @@ module Contexts
# end
#
class Organization < Context
ID_MAX_BYTES = 256.freeze
NAME_MAX_BYTES = 256.freeze

@keyspace = :organization

attr_reader :id, :name

def initialize(attributes)
@id = Timber::Util::Object.try(attributes[:id], :to_s)
@name = attributes[:name]
normalizer = Util::AttributeNormalizer.new(attributes)
@id = normalizer.fetch(:id, :string, :limit => ID_MAX_BYTES)
@name = normalizer.fetch(:name, :string, :limit => NAME_MAX_BYTES)
end

# Builds a hash representation containing simple objects, suitable for serialization (JSON).
def to_hash
@to_hash ||= Util::NonNilHashBuilder.build do |h|
h.add(:id, id)
h.add(:name, name)
end
end

def as_json(_options = {})
{id: id, name: name}
to_hash
end
end
end
Expand Down
21 changes: 17 additions & 4 deletions lib/timber/contexts/release.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ module Contexts
#
# @note To automatically set this context, see {.from_env}.
class Release < Context
COMMIT_HASH_MAX_BYTES = 256.freeze
CREATED_AT_MAX_BYTES = 256.freeze
VERSION_MAX_BYTES = 256.freeze

@keyspace = :release

class << self
Expand Down Expand Up @@ -36,14 +40,23 @@ def from_env
attr_reader :commit_hash, :created_at, :version

def initialize(attributes)
@commit_hash = attributes[:commit_hash]
@created_at = attributes[:created_at]
@version = attributes[:version]
normalizer = Util::AttributeNormalizer.new(attributes)
@commit_hash = normalizer.fetch(:commit_hash, :string, :limit => COMMIT_HASH_MAX_BYTES)
@created_at = normalizer.fetch(:created_at, :string, :limit => CREATED_AT_MAX_BYTES)
@version = normalizer.fetch(:version, :string, :limit => VERSION_MAX_BYTES)
end

# Builds a hash representation containing simple objects, suitable for serialization (JSON).
def to_hash
@to_hash ||= Util::NonNilHashBuilder.build do |h|
h.add(:commit_hash, commit_hash)
h.add(:created_at, created_at)
h.add(:version, version)
end
end

def as_json(_options = {})
{commit_hash: commit_hash, created_at: created_at, version: version}
to_hash
end
end
end
Expand Down
37 changes: 28 additions & 9 deletions lib/timber/contexts/runtime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,43 @@ module Contexts
# origin in your code. For example, if you are debugging a specific class, you can narrow
# by that class and see only it's logs.
class Runtime < Context
APPLICATION_MAX_BYTES = 256.freeze
CLASS_NAME_MAX_BYTES = 256.freeze
FILE_MAX_BYTES = 1024.freeze
FUNCTION_MAX_BYTES = 256.freeze
MODULE_NAME_MAX_BYTES = 256.freeze
VM_PID_MAX_BYTES = 256.freeze

@keyspace = :runtime

attr_reader :application, :class_name, :file, :function, :line, :module_name, :vm_pid

def initialize(attributes)
@application = attributes[:application]
@class_name = attributes[:class_name]
@file = attributes[:file]
@function = attributes[:function]
@line = attributes[:line]
@module_name = attributes[:module_name]
@vm_pid = Timber::Util::Object.try(attributes[:vm_pid], :to_s)
normalizer = Util::AttributeNormalizer.new(attributes)
@application = normalizer.fetch(:application, :string, :limit => APPLICATION_MAX_BYTES)
@class_name = normalizer.fetch(:class_name, :string, :limit => CLASS_NAME_MAX_BYTES)
@file = normalizer.fetch(:file, :string, :limit => FILE_MAX_BYTES)
@function = normalizer.fetch(:function, :string, :limit => FUNCTION_MAX_BYTES)
@line = normalizer.fetch(:line, :integer)
@module_name = normalizer.fetch(:module_name, :string, :limit => MODULE_NAME_MAX_BYTES)
@vm_pid = normalizer.fetch(:vm_pid, :string, :limit => VM_PID_MAX_BYTES)
end

# Builds a hash representation containing simple objects, suitable for serialization (JSON).
def to_hash
@to_hash ||= Util::NonNilHashBuilder.build do |h|
h.add(:application, application)
h.add(:class_name, class_name)
h.add(:file, file)
h.add(:function, function)
h.add(:line, line)
h.add(:module_name, module_name)
h.add(:vm_pid, vm_pid)
end
end

def as_json(_options = {})
{application: application, class_name: class_name, file: file, function: function,
line: line, module_name: module_name, vm_pid: vm_pid}
to_hash
end
end
end
Expand Down
13 changes: 11 additions & 2 deletions lib/timber/contexts/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,26 @@ module Contexts
# @note This is tracked automatically with the {Integrations::Rack::SessionContext} rack
# middleware.
class Session < Context
ID_MAX_BYTES = 256.freeze

@keyspace = :session

attr_reader :id

def initialize(attributes)
@id = Timber::Util::Object.try(attributes[:id], :to_s) || raise(ArgumentError.new(":id is required"))
normalizer = Util::AttributeNormalizer.new(attributes)
@id = normalizer.fetch!(:id, :string, :limit => ID_MAX_BYTES)
end

# Builds a hash representation containing simple objects, suitable for serialization (JSON).
def to_hash
@to_hash ||= Util::NonNilHashBuilder.build do |h|
h.add(:id, id)
end
end

def as_json(_options = {})
{id: id}
to_hash
end
end
end
Expand Down
16 changes: 13 additions & 3 deletions lib/timber/contexts/system.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,28 @@ module Contexts
# @note This is tracked automatically in {CurrentContext}. When the current context
# is initialized, the system context gets added automatically.
class System < Context
HOSTNAME_MAX_BYTES = 256.freeze

@keyspace = :system

attr_reader :hostname, :pid

def initialize(attributes)
@hostname = attributes[:hostname]
@pid = Timber::Util::Object.try(attributes[:pid], :to_i)
normalizer = Util::AttributeNormalizer.new(attributes)
@hostname = normalizer.fetch(:hostname, :string, :limit => HOSTNAME_MAX_BYTES)
@pid = normalizer.fetch(:pid, :integer)
end

# Builds a hash representation containing simple objects, suitable for serialization (JSON).
def to_hash
@to_hash ||= Util::NonNilHashBuilder.build do |h|
h.add(:hostname, hostname)
h.add(:pid, pid)
end
end

def as_json(_options = {})
{hostname: hostname, pid: pid}
to_hash
end
end
end
Expand Down
28 changes: 22 additions & 6 deletions lib/timber/contexts/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,37 @@ module Contexts
# middleware for supported authentication frameworks. See {Integrations::Rack::UserContext}
# for more details.
class User < Context
ID_MAX_BYTES = 256.freeze
NAME_MAX_BYTES = 256.freeze
EMAIL_MAX_BYTES = 256.freeze
TYPE_MAX_BYTES = 256.freeze

@keyspace = :user

attr_reader :id, :name, :email, :type, :meta

def initialize(attributes)
@id = Timber::Util::Object.try(attributes[:id], :to_s)
@name = attributes[:name]
@email = attributes[:email]
@type = attributes[:type]
@meta = attributes[:meta]
normalizer = Util::AttributeNormalizer.new(attributes)
@id = normalizer.fetch(:id, :string, :limit => ID_MAX_BYTES)
@name = normalizer.fetch(:name, :string, :limit => NAME_MAX_BYTES)
@email = normalizer.fetch(:email, :string, :limit => EMAIL_MAX_BYTES)
@type = normalizer.fetch(:type, :string, :limit => TYPE_MAX_BYTES)
@meta = normalizer.fetch(:meta, :hash)
end

# Builds a hash representation containing simple objects, suitable for serialization (JSON).
def to_hash
@to_hash ||= Util::NonNilHashBuilder.build do |h|
h.add(:id, id)
h.add(:name, name)
h.add(:email, email)
h.add(:type, type)
h.add(:meta, meta)
end
end

def as_json(_options = {})
{id: id, name: name, email: email, type: type, meta: meta}
to_hash
end
end
end
Expand Down
38 changes: 14 additions & 24 deletions lib/timber/events/controller_call.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,30 @@ module Events
# Processing by PagesController#home as HTML
#
# @note This event should be installed automatically through integrations,
# such as the {Integrations::ActionController::LogSubscriber} integration.
# such as the {Integrations::ActionController} integration.
class ControllerCall < Timber::Event
ACTION_MAX_BYTES = 256.freeze
FORMAT_MAX_BYTES = 256.freeze
CONTROLLER_MAX_BYTES = 256.freeze
PARAMS_JSON_MAX_BYTES = 32_768.freeze
PASSWORD_NAME = 'password'.freeze

attr_reader :controller, :action, :params, :format

def initialize(attributes)
@controller = attributes[:controller] || raise(ArgumentError.new(":controller is required"))
@action = attributes[:action] || raise(ArgumentError.new(":action is required"))
@params = sanitize_params(attributes[:params])
@format = attributes[:format]
normalizer = Util::AttributeNormalizer.new(attributes)
@controller = normalizer.fetch!(:controller, :string, :limit => CONTROLLER_MAX_BYTES)
@action = normalizer.fetch!(:action, :string, :limit => ACTION_MAX_BYTES)
@params = normalizer.fetch(:params, :hash, :sanitize => [PASSWORD_NAME])
@format = normalizer.fetch(:format, :string, :limit => FORMAT_MAX_BYTES)
end

def to_hash
{controller: controller, action: action, params_json: params_json}
@to_hash ||= Util::NonNilHashBuilder.build do |h|
h.add(:controller, controller)
h.add(:action, action)
h.add(:params_json, params.to_json.byteslice(0, PARAMS_JSON_MAX_BYTES))
end
end
alias to_h to_hash

Expand All @@ -42,24 +50,6 @@ def message
end
message
end

private
def params_json
@params_json ||=
if params.nil? || params == {}
nil
else
params.to_json.byteslice(0, PARAMS_JSON_MAX_BYTES)
end
end

def sanitize_params(params)
if params.is_a?(::Hash)
Util::Hash.sanitize(params, [PASSWORD_NAME])
else
params
end
end
end
end
end
Loading

0 comments on commit 5e7831d

Please sign in to comment.