使用 logrotate 定期整理 Rails Log 檔案

(2017/5) linux 日志定时轮询流程详解 這篇解釋的更清楚

不像 Apache 預設已經設定好了,會定期整理成 access.log.1, access.log.2.gz, access.log.3.gz 等,如果你沒特別處理,Rails 底下的 log 檔案可是越長越肥。

這個系統工具是 logrotate,它的設定檔在 /etc/logrotate.conf,設定的方式還真是簡單 (參考自 Rotating Rails Log Files):


# Rotate Rails application logs
/path/to/your/rails/current/log/*.log {
  daily
  dateext
  missingok
  rotate 65535
  compress
  delaycompress
  notifempty
  copytruncate
}

其中 daily 表示每天整理,也可以改成 weekly 或 monthly
dateext 表示檔案補上 rotate 的日期
missingok 表示如果找不到 log 檔也沒關係
rotate 7 表示保留65535份
compress 表示壓縮起來,預設用 gzip。不過如果硬碟空間多,不壓也沒關係。
delaycompress 表示延後壓縮直到下一次 rotate
notifempty 表示如果 log 檔是空的,就不 rotate
copytruncate 先複製 log 檔的內容後,在清空的作法,因為有些程式一定 log 在本來的檔名,例如 rails。另一種方法是 create。

設定好之後,可以等明天,或是執行 /usr/sbin/logrotate -f /etc/logrotate.conf 看看。

傳參數到 Rake 中

傳統作法是用 rake blah foo=1 這樣的指令,於是就可以透過環境變數拿到:


  task :blah do
    puts ENV['foo']
  end

但是,最近看到新的 API 使用中括號的用法 (也不新了,從 0.8.2 開始支援),覺得挺有趣的:


  desc "passing 1 parameter to rake task"
  task :blah1, [:a] do |t,args|
    puts args.inspect
  end
   

執行 rake blah1[9] 會輸出 {:a=>"9"},注意到傳進來的變數值是字串。



  desc "passing 2 parameters to rake task"
  task :blah2, [:a, :b] do |t,args|
    puts args.inspect
  end

 

執行 rake blah2[foo,bar] 會輸出 {:a=>"foo", :b=>"bar"}

如果要有預設值,可以這樣做:

 
  desc "passing parameters with default values to rake task"
  task :blah3, [:a, :b] do |t,args|
    args.with_defaults(:a => 'foobar', :b => 1)
    puts args.inspect
  end    
  

此時執行 rake blah3 則是輸出 {:a=>"foobar", :b=>1}

對了,好奇 t 是什麼? 那是 Rake::Task 物件。

如何正確發送(大量) Email 信件

Update(2011/5): 推薦 Amazon SES 服務

Update(2010/5): So You’d Like to Send Some Email (Through Code) 也可以一看

Update(2011/7): 推薦 Postmark,也有 Rails plugin。

在眾多客戶需求中,我最害怕的其中一條”順便”要做的功能就是,在後台可以寄信給”全部的”使用者。

寄 “email” 而已,不是非常簡單嗎?

寄給幾個人是很簡單,但是要寄給”一群”人,那就不是件簡單的事情了,在這 spam 肆虐的年頭, 信寄出去不一定就能順利到達使用者的收件夾。

Engine Yard 的這兩篇 How To Ensure Your Email Gets DeliveredMaking Sure Your Email Gets Delivered 點出了寄 Email 要注意的事項:

  1. 處理退信

    Bonuce mail 是你寄出去的信件,但是因為某些理由(地址不對、對方信箱滿了)而被對方 mail sever 退信,這些 email 你必須要處理。如果你忽略它還一直寄,你就長得蠻像發垃圾信的傢伙,而會被列出黑名單之中。

  2. 與主要的 Email 服務商設定意見反應機制(Feedback Loop)

    Feedback Loop 是一項協助處理當你的 email 被使用者按下 “垃圾信” 的服務。透過主動接觸主要的 Email 服務提供商,去建立用戶意見反應機制。例如 台灣Y!Yahoo! Complaint Feedback Loopmsn等,減少被寄件者檢舉成垃圾信的次數。

  3. 建立自已的 email 清單

    建立你自己的 mail 清單。如果你的清單是買來的,不但收件人沒有同意要收到你的信件,也會有很高的機會是 bonuce mail。寄出大量的非允許郵件,終究會讓你的 IP 被列出黑名單。

    標準的作法是,要在使用者註冊後,且他們也確認收到註冊的認證信(透過email上的認證連結),如此便可以確保這個 email 的正確性,而不會變成 bonuce mail。你也應該避免寄出跟你服務無關的email,減少被檢舉成垃圾信的機會。

  4. 不要使用 100% 以圖片為主的內容

    減少使用圖片,像 Gmail 預設就不會讀取圖片,重要的資訊使用圖片可能會讓使用者預設就看不到。充滿圖片的的 email 也容易判讀成垃圾信。

  5. 使用垃圾信判讀工作測試

    收信的 mail server 通常會使用如 Spam Assassin 的工具來判讀是否是垃圾信,而你也應該用這類的工作檢查你寄出去的信件。www.brandonchecketts.com/emailtest.php 是一個線上的檢查工作,如果你的分數太低,顯然很可能被判讀成 spam。至於為什麼分數低,可以參考這篇文章

  6. 驗證 HTML

    如果你寄的是 HTML 格式,你應該檢查 HTML 格式是否正確。一封畸形的信件也容易變成 spam。

  7. 模擬測試終端使用者環境

    使用不同 email clients 實際測試,例如 litmusapp.com 這個工具。不同的 client 可能會顯示不同的結果。

  8. 專屬 IP

    是否有專屬的 IP。如果你的 email sever 是跟人共用的,很可能別人被 spam 了,跟著害到你被列入黑名單。

  9. 設定 SPF Validation

    SPF (Sender Policy Framework) 是一項 e-mail 協定來確認 return-path address 的正確性,用以防止垃圾信件。設定 SPF 可以改進你的信件發送成功率,特別是 hotmail(MSN)。

  10. 設定 Domain Keys Verification

    Domain Keys 驗證是另一種防止垃圾信件的協定。

  11. 設定 Reverse DNS 反查

    設定反向查詢的 DNS 記錄,如果你寄信的 IP 無法反查,可能根本就寄不到。

  12. 驗證寄信者地址

    你的寄信人 email 位址也要是正確存在的。許多 email 服務商會先檢查寄件人的地址是正確的,才會收信。

接下來麻煩的是,如果你終究還是不小心被列成黑名單,該怎麼辦? 這篇文章也列出一些常見的原因。

Anyway,我的結論是,還是交給專業的來吧~ 自己架設/管理 email server 不但辛苦又會被 blocked。如果信件量一天低於 500 封,我會建議採用免錢 Gmail 來寄信;超過的話,則有一些第三方服務可以採用,例如 Amazon SESSendgridAuthsmtp。 如果需要比較多的行銷功能,則可以考慮 MadmimiCampaignmonitorMailchimp 等服務。

BTW,既然提到了 Campaign Monitor,如果你有心做 Email marketing,他們家有不少值得一讀的資源,例如 Guide to CSS support in email clients 就十分有用。

如何有效率地分頁?

Efficient Pagination Using MySQL 是一份針對分頁這件事情如何更快的 PDF。

傳統的分頁方式,正如 will_paginate plugin 所做的:顯示所有頁數跟連結。有個不 scale 的問題是,它用了 OFFSET 跟 COUNT,當資料量越來越多,分頁的速度就會越來越慢 :(


 SELECT count(*) FROM messages
 SELECT * FROM messages ORDER BY id DESC LIMIT 0, 20

這裡提出的解決方案如同 Twitter 所採用的,只讓使用者點下一頁:

不要顯示頁數、不要顯示總數量,可以搭配使用 Ajax 換下一頁。如此就可避免使用到 OFFSET 只用 LIMIT,也減少了一次 COUNT query。


page1 = SELECT * FROM messages LIMIT 10 WHERE id > 0 ASC id
page2_min_id = page1.last.id
page2 = SELECT * FROM messages LIMIT 10 WHERE id > page2_min_id ASC id

當然,有個缺點就是使用者沒辦法知道總共有多少資料了。

Moneta: 提供 key/value stores 的統一介面

今天我想特別介紹這一套 Ruby library: Moneta

它提供了 key-value stores 標準共通的使用介面,讓你可以用相同的程式,但是可以輕易轉換底層實際使用的儲存庫,包括:

  • Basic File Store
  • BerkeleyDB
  • CouchDB
  • DataMapper
  • File store for xattr
  • In-memory store
  • Memcache store
  • Redis
  • S3
  • SDBM
  • Tokyo
  • Xattrs in a file system

它的用法其實跟 Hash API 差不多,非常容易使用。我認為最實際的用途是用在撰寫快取程式了。

因為不同環境可以輕易切換 store 的這個好處,我在 local 的 development 環境中,可以使用 In-memory store 或 File Store (前者重開 server 快取資料就會消失,後者則會留著),不需要多裝東西就可以寫快取。更重要的是,團隊中其他人也不需要費心設定 (叫每個人都去裝 Memcached 也太辛苦了)。而在 production 正式環境中,則可以輕易設定成使用 Memcached

我自己也用了 Moneta 介面寫了一個小程式 Headcache,提供了 get_and_set 功能:


data = Handcache.get_and_set("data-123", :expires_in => 60 ) do
                "cached-foobar"
            end

如果快取存在,就回傳快取資料;如果不存在,就執行 code block,並將最後的運算值回傳並快取起來。

如果是在 View 中搭配 partial 使用也非常簡單:


<%= Handcache.get_and_set( dom_id(@post), :expires_in => 60 ) do
               render :partial => "post"
           end %>

設定的方法則可以是:


# config/environments/development.rb
begin
  # for developer has tokyo cabinet
  Handcache = Moneta::Tyrant.new( :host => 'localhost', :port => 1978 )
rescue
  # for developer has not tokyo cabinet
  Handcache = Moneta::BasicFile.new( :path => "tmp" )
end

關於快取這件事情的學問,可以參考我之前的 Memcached 文章:一是如何清除過期的快取資料(expire),二是注意快取 Key 的命名。