class PhusionPassenger::AnalyticsLogger

Constants

NETWORK_ERRORS
RANDOM_CHARS
RETRY_SLEEP

Attributes

max_connect_tries[RW]
reconnect_timeout[RW]

Public Class Methods

new(logging_agent_address, username, password, node_name) click to toggle source
# File lib/phusion_passenger/analytics_logger.rb, line 186
def initialize(logging_agent_address, username, password, node_name)
        @server_address = logging_agent_address
        @username = username
        @password = password
        if node_name && !node_name.empty?
                @node_name = node_name
        else
                @node_name = %x`hostname`.strip
        end
        @random_dev = File.open("/dev/urandom")
        
        # This mutex protects the following instance variables, but
        # not the contents of @connection.
        @mutex = Mutex.new
        
        @connection = Connection.new(nil)
        if @server_address && local_socket_address?(@server_address)
                @max_connect_tries = 10
        else
                @max_connect_tries = 1
        end
        @reconnect_timeout = 1
        @next_reconnect_time = Time.utc(1980, 1, 1)
end
new_from_options(options) click to toggle source
# File lib/phusion_passenger/analytics_logger.rb, line 172
def self.new_from_options(options)
        if options["analytics"] && options["logging_agent_address"]
                return new(options["logging_agent_address"],
                        options["logging_agent_username"],
                        options["logging_agent_password"],
                        options["node_name"])
        else
                return nil
        end
end

Private Class Methods

current_time() click to toggle source
# File lib/phusion_passenger/analytics_logger.rb, line 473
def self.current_time
        return Time.now
end
timestamp_string(time = current_time) click to toggle source
# File lib/phusion_passenger/analytics_logger.rb, line 477
def self.timestamp_string(time = current_time)
        timestamp = time.to_i * 1_000_000 + time.usec
        return timestamp.to_s(36)
end

Public Instance Methods

clear_connection() click to toggle source
# File lib/phusion_passenger/analytics_logger.rb, line 211
def clear_connection
        @mutex.synchronize do
                @connection.synchronize do
                        @random_dev = File.open("/dev/urandom") if @random_dev.closed?
                        @connection.unref
                        @connection = Connection.new(nil)
                end
        end
end
close() click to toggle source
# File lib/phusion_passenger/analytics_logger.rb, line 221
def close
        @mutex.synchronize do
                @connection.synchronize do
                        @random_dev.close
                        @connection.unref
                        @connection = nil
                end
        end
end
continue_transaction(txn_id, group_name, category = :requests, union_station_key = "-") click to toggle source
# File lib/phusion_passenger/analytics_logger.rb, line 290
def continue_transaction(txn_id, group_name, category = :requests, union_station_key = "-")
        if !@server_address
                return Log.new
        elsif !txn_id || txn_id.empty?
                raise ArgumentError, "Transaction ID may not be empty"
        end
        
        Lock.new(@mutex).synchronize do |lock|
                if current_time < @next_reconnect_time
                        return Log.new
                end
                
                Lock.new(@connection.mutex).synchronize do |connection_lock|
                        if !@connection.connected?
                                begin
                                        connect
                                        connection_lock.reset(@connection.mutex)
                                rescue SystemCallError, IOError
                                        @connection.disconnect
                                        DebugLogging.warn("Cannot connect to the logging agent at #{@server_address}; " +
                                                "retrying in #{@reconnect_timeout} second(s).")
                                        @next_reconnect_time = current_time + @reconnect_timeout
                                        return Log.new
                                rescue Exception => e
                                        @connection.disconnect
                                        raise e
                                end
                        end
                        
                        begin
                                @connection.channel.write("openTransaction",
                                        txn_id, group_name, "", category,
                                        AnalyticsLogger.timestamp_string,
                                        union_station_key,
                                        true)
                                return Log.new(@connection, txn_id)
                        rescue SystemCallError, IOError
                                @connection.disconnect
                                DebugLogging.warn("The logging agent at #{@server_address}" <<
                                        " closed the connection; will reconnect in " <<
                                        "#{@reconnect_timeout} second(s).")
                                @next_reconnect_time = current_time + @reconnect_timeout
                                return Log.new
                        rescue Exception => e
                                @connection.disconnect
                                raise e
                        end
                end
        end
end
new_transaction(group_name, category = :requests, union_station_key = "-") click to toggle source
# File lib/phusion_passenger/analytics_logger.rb, line 231
def new_transaction(group_name, category = :requests, union_station_key = "-")
        if !@server_address
                return Log.new
        elsif !group_name || group_name.empty?
                raise ArgumentError, "Group name may not be empty"
        end
        
        txn_id = (AnalyticsLogger.current_time.to_i / 60).to_s(36)
        txn_id << "-#{random_token(11)}"
        
        Lock.new(@mutex).synchronize do |lock|
                if current_time < @next_reconnect_time
                        return Log.new
                end
                
                Lock.new(@connection.mutex).synchronize do |connection_lock|
                        if !@connection.connected?
                                begin
                                        connect
                                        connection_lock.reset(@connection.mutex)
                                rescue SystemCallError, IOError
                                        @connection.disconnect
                                        DebugLogging.warn("Cannot connect to the logging agent at #{@server_address}; " +
                                                "retrying in #{@reconnect_timeout} second(s).")
                                        @next_reconnect_time = current_time + @reconnect_timeout
                                        return Log.new
                                rescue Exception => e
                                        @connection.disconnect
                                        raise e
                                end
                        end
                        
                        begin
                                @connection.channel.write("openTransaction",
                                        txn_id, group_name, "", category,
                                        AnalyticsLogger.timestamp_string,
                                        union_station_key,
                                        true,
                                        true)
                                result = @connection.channel.read
                                if result != ["ok"]
                                        raise "Expected logging server to respond with 'ok', but got #{result.inspect} instead"
                                end
                                return Log.new(@connection, txn_id)
                        rescue SystemCallError, IOError
                                @connection.disconnect
                                DebugLogging.warn("The logging agent at #{@server_address}" <<
                                        " closed the connection; will reconnect in " <<
                                        "#{@reconnect_timeout} second(s).")
                                @next_reconnect_time = current_time + @reconnect_timeout
                                return Log.new
                        rescue Exception => e
                                @connection.disconnect
                                raise e
                        end
                end
        end
end

Private Instance Methods

connect() click to toggle source
# File lib/phusion_passenger/analytics_logger.rb, line 419
def connect
        socket  = connect_to_server(@server_address)
        channel = MessageChannel.new(socket)
        
        result = channel.read
        if result.nil?
                raise EOFError
        elsif result.size != 2 || result[0] != "version"
                raise IOError, "The logging agent didn't sent a valid version identifier"
        elsif result[1] != "1"
                raise IOError, "Unsupported logging agent protocol version #{result[1]}"
        end
        
        channel.write_scalar(@username)
        channel.write_scalar(@password)
        
        result = channel.read
        if result.nil?
                raise EOFError
        elsif result[0] != "ok"
                raise SecurityError, result[0]
        end
        
        channel.write("init", @node_name)
        args = channel.read
        if !args
                raise Errno::ECONNREFUSED, "Cannot connect to logging agent"
        elsif args.size != 1
                raise IOError, "Logging agent returned an invalid reply for the 'init' command"
        elsif args[0] == "server shutting down"
                raise Errno::ECONNREFUSED, "Cannot connect to logging agent"
        elsif args[0] != "ok"
                raise IOError, "Logging agent returned an invalid reply for the 'init' command"
        end
        
        @connection.unref
        @connection = Connection.new(socket)
rescue Exception => e
        socket.close if socket && !socket.closed?
        raise e
end
current_time() click to toggle source
# File lib/phusion_passenger/analytics_logger.rb, line 469
def current_time
        return self.class.current_time
end
random_token(length) click to toggle source
# File lib/phusion_passenger/analytics_logger.rb, line 461
def random_token(length)
        token = ""
        @random_dev.read(length).each_byte do |c|
                token << RANDOM_CHARS[c % RANDOM_CHARS.size]
        end
        return token
end