くじら、重複ツイートのエラー処理をしていましたが、もうひとつnet/httpのエラー処理が必要と知りました。
Net::HTTPは内部でTimeoutを利用しているけども、これのエラー補足までの面倒は見てくれない。
なので必ずrescueで補足してあげる必要がある。
Timeout::ErrorはInterruptのサブクラス(引用者註:StandardErrorとそのサブクラスではない)。
なので、Net::HTTPの例外補足は、きちんと明示するかrescue Timeout::Errorより広く補足しないといけない。
rescue Exception
いま使っているrescue処理
('13/1/14更新)
Loggerを別途設定しており、TwitterのAPI設定は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:めでたくないって…