I moved Tanga over to Amazon S3. That was quite the adventure.

I hope it works well.

DNS

I recently switched Tanga’s DNS over to DNS Made Easy. I highly recommend them.

I love making DNS records. Don’t ask me why.

What’s cool about them is that you can set up http redirections easily—so that http://stats.tanga.com takes you to our Google Analytics page.

Tanga.com is now using Spread for logging. Right now, only mongrel / rails and lighttpd are logging to spread. It’s cool because:

  1. The database and application servers don’t need to waste time logging files to disk, they just make one function call that mutlicasts the log data to spread and that’s it.
  2. One or more dedicated (or semi-dedicated) machines can listen to the spread network and capture the log data and write it to the log files.
  3. Unified logging mechanism.
  4. Should scale up very well.

One thing I’m curious about—it would be neat to somehow extend syslog to be able to write to the spread network.

I had to write two small pieces of software to get the logging to spread.

  1. For lighttpd, here’s a patch against 1.4.16
  2. For mongrel / rails, below is a small Logger class that’s a dropin replacement for the standard logger. It’s largely stolen from Eric Hodel’s SyslogLogger. You’ll need to have the rb_spread library as well.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
require 'logger'
require 'spread'

class SpreadLogger

  LOGGER_MAP = {
    :unknown => :alert,
    :fatal   => :err,
    :error   => :warning,
    :warn    => :notice,
    :info    => :info,
    :debug   => :debug,
  }


  LOGGER_LEVEL_MAP = {}

  LOGGER_MAP.each_key do |key|
    LOGGER_LEVEL_MAP[key] = Logger.const_get key.to_s.upcase
  end

  LEVEL_LOGGER_MAP = {}

  LOGGER_LEVEL_MAP.invert.each do |level, severity|
    LEVEL_LOGGER_MAP[level] = LOGGER_MAP[severity]
  end


  def self.make_methods(meth)
    eval <<-EOM, nil, __FILE__, __LINE__ + 1
      def #{meth}(message = nil)
    begin
        return true if #{LOGGER_LEVEL_MAP[meth]} < 1
        log_message(#{LOGGER_LEVEL_MAP[meth]}, message)
    rescue Exception => e
    end
        return true
      end

      def #{meth}?
        @level <= Logger::#{meth.to_s.upcase}
      end
    EOM
  end

  LOGGER_MAP.each_key do |level|
    make_methods level
  end

  attr_accessor :level

  def log_message severity, msg
    begin
      initialize_spread
      msg = clean(msg)
      msg = "#{Time.now.strftime("%b %d %H:%M:%S")} #{Socket.gethostname.split('.').first} rails[#{$PID}]: #{msg.gsub(/\n/, '').lstrip} \n"
      @spread.multicast(msg, @group, 1)  if @spread
    rescue Exception => e
      @spread = nil
    end
  end



  def initialize_spread 
    @spread ||= Spread.new @server, @my_name 
  rescue Exception => e
    puts 'couldnt initialize spread'
    puts e.inspect
  end

  def initialize(server, group)
    @level = Logger::DEBUG
    @my_name = `hostname`.chomp
    @group = group
    @server = server
    initialize_spread
  end

  def add(severity, message = nil, progname = nil, &block)
    severity ||= Logger::DEBUG
    @progname = progname || "tanga-mongrel"
    return true if severity < @level
    return true
  end


  def silence(temporary_level = Logger::ERROR)
    old_logger_level = @level
    @level = temporary_level
    yield
  ensure
    @level = old_logger_level
  end

  private


  def clean(message)
    message = message.to_s.dup
    message.strip!
    message.gsub!(/%/, '%%') 
    message.gsub!(/\e\[[^m]*m/, '') # remove useless ansi color codes
    return message
  end

end

If you want a paper version of the excellent Postgresql online manual, check out lulu.com.

$56.75 shipped for the entire thing. Not a bad deal.