Project

coaster

0.0
A long-lived project that still receives updates
Ruby Core Extensions
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

>= 1.0
>= 10.0
>= 0
 Project Readme

Coaster

Object Translation

Translate by object class name.

                                  # Translation key
Coupon._translate                 #=> en.class.Coupon.self
Coupon::Discount._translate       #=> en.class.Coupon.Discount.self
Coupon._translate('.title')       #=> en.class.Coupon.title
Coupon._translate('root')         #=> en.root

Pass translation params.

class Coupon
  class << self
    def _translation_params
      {name: 'Buzz'}
    end
  end
end

Coupon._translate #=> I18n.t('en.class.Coupon.self', {name: 'Buzz'})

Inheritance fallback.

class Order
end

class CreditCard < Order
end

# in en.yml only defined Order translation.
# use superclass _translate
CreditCard._translate #=> I18n.t('en.class.CreditCard.self') not exists
                      #=> I18n.t('en.class.Order.self') returned

instance 에서도 사용할 수 있다.

Foo::Bar.new._translate            #=> return translation 'class.Foo.Bar.self'
Foo::Bar.new._translate('.title')  #=> return translation 'class.Foo.Bar.title'
Foo::Bar.new._translate('title')   #=> return translation 'title'
Foo::Bar.new._translate(:force)    #=> ignore 'message' even if message exists

그런데 다른점은 instance에 message 메서드가 존재할 경우 다음과 같이 message를 활용한다.

error = Order::Error.new('error message')
error._translate(:force) #=> "error message"

StandardError features

아래는 동일하다.

raise Order::Error, 'order error occurred'
raise Order::Error, {message: 'order error occurred'}
raise Order::Error, {msg: 'order error occurred'}
raise Order::Error, {m: 'order error occurred'}

에러에 추가로 attribute를 추가하고 싶다면 Hash에 아무거나 넣으면 된다.

ex = catch { raise Order::Error, {m: 'message', order: order} }
ex.attributes[:order] #=> order instance

Hash로 전달되는 특수한 attribute가 있다.

  1. desc, description: ex.description으로 꺼낼 수 있다. ex.message는 사용자 친화적이지 않은 메시지라서 사용자 친화적인 메시지를 넣으려면 description을 쓰면 된다. description이 없으면 message를 리턴한다.
  2. obj, object: ex.object로 꺼낼수 있다.
  3. http_status: ex.http_status로 꺼낼수 있다. 기본값은 Error Class에서 지정된 상수값.

그 외에 error instance variable로 등록되는 attribute가 있다.

  1. tags: ActiveSupport::TaggedLogging에 사용된다.
  2. level: debug, info 등등의 로깅 레벨
  3. tkey: Object Translation 에서 사용된다. 기본값은 '.self'와 동일하다.

StandardError#logging

StandardError#logging으로 로깅한다.
logger는 Coaster.logger=를 사용하며 설정이 안돼있을 경우 Rails.logger를 사용한다.
cleaner(AcitveSupport::BacktraceCleaner)는 StandardError.cleaner=, StandardError.cause_cleaner=를 사용하며 기본값은 없으며 cleaner가 없을 경우 backtrace를 출력하지 않는다.

  1. options
    1. :logger => 기본 logger를 대체할 logger
    2. :cleaner => 해당 에러를 로깅할때 사용할 cleaner
  2. before_logging_blocks, after_loggin_blocks
    1. logging 전후 처리를 추가할 수 있으며 추가된 block은 error instance 내에서 실행된다.
    2. StandardError.before_logging(:cloudwatch) do
        ReportCloudWatch.send(self) # self가 에러 자신
      end
      
  3. log 내용은 StandardError#to_detail을 사용
  4. Sentry 아래와 같이 require를 하면 logging 하기 전 sentry에 보낸다.
    require 'coaster/core_ext/standard_error/sentry'
    
    (raven.rb는 legacy, 옛날 sentry gem 이름)

StandardError#to_detail

logging 메서드에서 출력한 메시지를 만든다.

  1. error class, status, message, instance_variables(, backtrace) 순서대로 출력하며
    cause가 존재할 경우 CAUSE이후 tab indent를 하여 출력한다. cause는 최대 3 depth까지 출력한다.
  2. instance_variable
    1. StandardError.detail_vars Array에서 있는 값의 출력은 StandardError.detail_value_proc으로 출력한다.
    2. detail_vars 기본값은 %i[@attributes @tkey @fingerprint @tags @level]
    3. detail_value_proc 기본값은 Proc.new{|val| val.inspect}
    4. 나머지는 StandardError.detail_value_simpe로 처리하며 class name만 사용한다.

coaster/rails_ext/backtrace_cleaner

AcitveSupport::BacktraceCleaner에서 앞쪽은 silence!에서 제외하는(즉 모든 backtrace가 포함되는) 로직이 추가된다.
앞쪽에서 얼마나 포함할지는 cleaner.minimum_first=로 설정하며 기본값은 10이다.
minimum_first 이후 silence된 backtrace사이에 BacktraceCleaner.minimum_first ... and next silenced backtraces라인이 끼워진다.

StandardError logging example with backtrace cleaner

[Dynamoid::Errors::RecordNotUnique] status:999999
	MESSAGE: Attempted to write record #<DynamoUserIdentificationLog:0x00005592491a7378> when its key already exists
	@attributes: {}
	@fingerprint: [:default, :class]
	@inner_exception: Attempted to write record #<DynamoUserIdentificationLog:0x00005592491a7378> when its key already exists
	@level: "error"
	@original_exception: Dynamoid::Errors::ConditionalCheckFailedException
	@raven: {}
	@tags: {}
	@tkey: nil
	BACKTRACE:
		dynamoid (3.7.1) lib/dynamoid/persistence/save.rb:30:in `rescue in call'
		dynamoid (3.7.1) lib/dynamoid/persistence/save.rb:15:in `call'
		dynamoid (3.7.1) lib/dynamoid/persistence/save.rb:8:in `call'
		dynamoid (3.7.1) lib/dynamoid/persistence.rb:485:in `block (2 levels) in save'
		activesupport (6.1.4.4) lib/active_support/callbacks.rb:106:in `run_callbacks'
		dynamoid (3.7.1) lib/dynamoid/persistence.rb:484:in `block in save'
		activesupport (6.1.4.4) lib/active_support/callbacks.rb:106:in `run_callbacks'
		dynamoid (3.7.1) lib/dynamoid/persistence.rb:483:in `save'
		dynamoid (3.7.1) lib/dynamoid/dirty.rb:50:in `save'
		dynamoid (3.7.1) lib/dynamoid/validations.rb:19:in `save'
		BacktraceCleaner.minimum_first ... and next silenced backtraces
		exmaple_app/app/models/dynamo_user_identification_log.rb:39:in `record!'
		exmaple_app/app/models/sample_model.rb:156:in `record_authentication_log'
		vendor/bundle/ruby/2.7.0/bin/rspec:25:in `load'
		vendor/bundle/ruby/2.7.0/bin/rspec:25:in `<top (required)>'
		/home/circleci/.rubygems/bin/bundle:25:in `load'
		/home/circleci/.rubygems/bin/bundle:25:in `<main>'
	CAUSE: [Dynamoid::Errors::ConditionalCheckFailedException] status:999999
		MESSAGE: The conditional request failed
		@attributes: {}
		@fingerprint: [:default, :class]
		@inner_exception: Aws::DynamoDB::Errors::ConditionalCheckFailedException
		@level: "error"
		@raven: {}
		@tags: {}
		@tkey: nil
		BACKTRACE:
			dynamoid (3.7.1) lib/dynamoid/adapter_plugin/aws_sdk_v3.rb:471:in `rescue in put_item'
			dynamoid (3.7.1) lib/dynamoid/adapter_plugin/aws_sdk_v3.rb:462:in `put_item'
			dynamoid (3.7.1) lib/dynamoid/adapter.rb:153:in `block (3 levels) in <class:Adapter>'
			dynamoid (3.7.1) lib/dynamoid/adapter.rb:56:in `benchmark'
			dynamoid (3.7.1) lib/dynamoid/adapter.rb:153:in `block (2 levels) in <class:Adapter>'
			dynamoid (3.7.1) lib/dynamoid/adapter.rb:71:in `write'
			dynamoid (3.7.1) lib/dynamoid/persistence/save.rb:24:in `call'
			dynamoid (3.7.1) lib/dynamoid/persistence/save.rb:8:in `call'
			dynamoid (3.7.1) lib/dynamoid/persistence.rb:485:in `block (2 levels) in save'
			activesupport (6.1.4.4) lib/active_support/callbacks.rb:106:in `run_callbacks'
			BacktraceCleaner.minimum_first ... and next silenced backtraces
			exmaple_app/app/models/dynamo_user_identification_log.rb:39:in `record!'
			exmaple_app/app/models/sample_model.rb:156:in `record_authentication_log'
			vendor/bundle/ruby/2.7.0/bin/rspec:25:in `load'
			vendor/bundle/ruby/2.7.0/bin/rspec:25:in `<top (required)>'
			/home/circleci/.rubygems/bin/bundle:25:in `load'
			/home/circleci/.rubygems/bin/bundle:25:in `<main>'
		CAUSE: [Aws::DynamoDB::Errors::ConditionalCheckFailedException] status:999999
			MESSAGE: The conditional request failed
			@attributes: {}
			@code: ConditionalCheckFailedException
			@context: Seahorse::Client::RequestContext
			@data: Aws::DynamoDB::Types::ConditionalCheckFailedException
			@fingerprint: [:default, :class]
			@level: "error"
			@message: The conditional request failed
			@raven: {}
			@tags: {}
			@tkey: nil
			BACKTRACE:
				aws-sdk-core (3.130.0) lib/seahorse/client/plugins/raise_response_errors.rb:17:in `call'
				aws-sdk-dynamodb (1.69.0) lib/aws-sdk-dynamodb/plugins/simple_attributes.rb:119:in `call'
				aws-sdk-core (3.130.0) lib/aws-sdk-core/plugins/jsonvalue_converter.rb:22:in `call'
				aws-sdk-core (3.130.0) lib/aws-sdk-core/plugins/idempotency_token.rb:19:in `call'
				aws-sdk-core (3.130.0) lib/aws-sdk-core/plugins/param_converter.rb:26:in `call'
				aws-sdk-core (3.130.0) lib/seahorse/client/plugins/request_callback.rb:71:in `call'
				aws-sdk-core (3.130.0) lib/aws-sdk-core/plugins/response_paging.rb:12:in `call'
				aws-sdk-core (3.130.0) lib/seahorse/client/plugins/response_target.rb:24:in `call'
				aws-sdk-core (3.130.0) lib/seahorse/client/request.rb:72:in `send_request'
				aws-sdk-dynamodb (1.69.0) lib/aws-sdk-dynamodb/client.rb:4147:in `put_item'
				BacktraceCleaner.minimum_first ... and next silenced backtraces
				example_app/app/models/dynamo_user_identification_log.rb:39:in `record!'
				example_app/app/models/sample_model.rb:156:in `record_authentication_log'
				vendor/bundle/ruby/2.7.0/bin/rspec:25:in `load'
				vendor/bundle/ruby/2.7.0/bin/rspec:25:in `<top (required)>'
				/home/circleci/.rubygems/bin/bundle:25:in `load'
				/home/circleci/.rubygems/bin/bundle:25:in `<main>'