Twitter gemのrescue処理

くじら、重複ツイートのエラー処理をしていましたが、もうひとつnet/httpのエラー処理が必要と知りました。

Net::HTTPは内部でTimeoutを利用しているけども、これのエラー補足までの面倒は見てくれない。
なので必ずrescueで補足してあげる必要がある。
Timeout::ErrorはInterruptのサブクラス(引用者註:StandardErrorとそのサブクラスではない)。
なので、Net::HTTPの例外補足は、きちんと明示するか

rescue Timeout::Error

より広く補足しないといけない。

rescue Exception

Net::HTTPの例外補足方法 - OVERT MEMO

いま使っているrescue処理

('13/1/14更新)
Loggerを別途設定しており、TwitterAPI設定はkeys_secrets_hashに入れていることを前提とします。
(Logger使わないならば log.error を全て puts に変更すればいいかと。)
twitter gemを使うと「Twitter::Error::ClientError(execution expired) 」エラーが頻発するので、ClientErrorのなかでもretry処理を(上限2回で)組み込みました。またConnection reset by peerとend of file reachedもretry処理するよう追加。加えて、OpenSSL系のpipe_errorもretry処理。
また、rescue処理を分離しました(twitter_rescueメソッドを使う際にtwitter apiを呼び出すメソッドのブロックを投げつけます)。
('16/1/19更新)
Twitter::Error::ServerError が多く出る状況になったので retry 制限を行うようにした。
また retry しない場合には nil を返すようにして、返値で分岐できるようにした。

twitter_rescue(header, message){
  Twitter::Client.new(keys_secrets_hash).update(tweet_text)
}
require 'logger'

def twitter_rescue(mes_header = '', incmpl_mes = '', &twitter_action)
  incmpl_full_mes = "#{mes_header} #{incmpl_mes}"
  yield
rescue Twitter::Error::Forbidden => forbidden_error
  log.error "#{mes_header}|Twitter::Error::Forbidden|#{forbidden_error.message}"
  log.error incmpl_full_mes unless incmpl_mes.empty?
  nil
rescue Twitter::Error::ServiceUnavailable => over_capacity_error
  log.warn "#{mes_header}|(__-){ Twitter is over capacity. tweet retry..."
  sleep 3
  retry
rescue Twitter::Error::ClientError => client_error
  clienterror = "Twitter::Error::ClientError"
  clienterror_regexp = /execution expired|Connection reset by peer|end of file reached/
  if client_error.message[clienterror_regexp] && (count = (count || 0) + 1) && (count < 3)
    log.error "#{mes_header}#{clienterror}#{client_error.message} retry..."
    sleep 2
    retry
  else
    log.error "#{mes_header}#{clienterror}#{client_error.message}"
    log.error incmpl_full_mes unless incmpl_mes.empty?
    nil
  end
rescue Twitter::Error::ServerError => server_error
  if (server_error_count = (server_error_count || 0) + 1) && (server_error_count < 3)
    log.warn "#{mes_header}|Twitter::Error::ServerError|#{server_error.message} retry..."
    sleep 3
    retry
  else
    log.warn "#{mes_header}|Twitter::Error::ServerError|#{server_error.message}"
    log.error incmpl_full_mes unless incmpl_mes.empty?
    nil
  end
rescue Errno::EPIPE => pipe_error
  log.error "#{mes_header}|Errno::EPIPE|#{pipe_error.message} retry..."
  sleep 3
  retry
rescue Timeout::Error => execution_expired
  log.error "#{mes_header}|Timeout::Error|#{execution_expired.message} retry..."
  sleep 2
  retry
rescue => ex
  log.error "#{mes_header}#{ex}"
  log.error incmpl_full_mes unless incmpl_mes.empty?
  nil
end

おまけ情報:アカウント凍結を受けた場合('14/3/3更新)

某アカウントで凍結されましたorz
このときにTwitter gemを使うと

Your account is suspended and is not permitted to access this feature. (Twitter::Error::Forbidden)

というエラーが返されました。
ので、凍結も上記のrescue処理で対応できます。めでたしめでたし*1


失敗版:duplicate errorが出たときに無限ループに陥ったorz

http://rubydoc.info/gems/twitter/Twitter/Error/Forbidden
を確認すれば分かったのだが、twitter gemは

Twitter::Error::Forbidden < Twitter::Error::ClientError < Twitter::Error < StandardError

と継承しています。ので以下のrescueだとretry無限ループに…
retryを入れるときには注意しましょう。

def tweet(tweet_text)
  Twitter::Client.new(keys_secrets_hash).update(tweet_text)
rescue Twitter::Error::ServiceUnavailable => over_capacity_error
  log.warn "(__-){ Twitter is over capacity. tweet retry..."
  sleep 3
  retry
rescue Twitter::Error::ClientError => client_error
  log.error "#{client_error.message} retry..."
  sleep 2
  retry
rescue Timeout::Error => timeout_error
  log.error "#{timeout_error.message} retry..."
  sleep 2
  retry
rescue Twitter::Error::Forbidden => duplicate_error
  log.error duplicate_error.message 
rescue => ex
  log.error ex
end

ClientError全般もretryすると危なそうなのでretryせずに済ませましょう。

retry無限ループの失敗をして分かったこと

  • "Status is a duplicate."エラーを繰り返すと"User is over daily status update limit."エラーになる

まぁ当然と言えば当然ですが。

  • "Status is a duplicate."エラーも"User is over daily status update limit."エラーも同じ403 Forbiddenエラー。

参照:
エラーログの内容 - キャラボット設定の参考になればいいなログ
https://dev.twitter.com/docs/error-codes-responses

*1:めでたくないって…