Skip to content

Commit

Permalink
rewritten. added td_access_log for controllers and td_enable_model_tr…
Browse files Browse the repository at this point in the history
…acer for models.
  • Loading branch information
frsyuki committed Aug 28, 2011
1 parent 49bce70 commit c7f175a
Show file tree
Hide file tree
Showing 8 changed files with 404 additions and 258 deletions.
67 changes: 54 additions & 13 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,85 @@ edit +environment.rb+ and add to the initalizer block:

And then add +config/treasure_data.yml+ file as following:

# logging to Treasure Data directry
# logging to Treasure Data directly
development:
apikey: "YOUR_API_KEY"
database: myapp
access_log_table: access
auto_create_table: true

# logging via td-agent
production:
agent: "localhost:24224"
tag: myapp
tag: td.myapp
access_log_table: access

# disable logging
test:

=== Logging actions
=== Logging access logs

Add 'add_td_tracer' line to your controller classes:
Following information will be logged automatically:

* controller name ('controller' column)
* action name ('action' column)
* remote host ip address ('remote_addr' column)
* http status code ('status' column)
* http referer ('refer' column)

To add information to the log, call 'td_access_log' method in a Controller:

class YourController < ApplicationController
def myaction
# ...
td_access_log[:column] = "value"
end
end

Or call 'TreasureData.access_log' method outside of a Controller:

class MyHelperModule
def mymethod
TreasureData.access_log[:column] = "value"
end
add_td_tracer :myaction, 'mytable'
end

It logs {"controller":"your","action":"myaction"}. Additionally, routing parameters are included automatically.

You can add environment variables by adding 'extra' option as follows:
=== Logging model changes

Add 'td_enable_model_tracer' line to your Model class:

class MyModel < ActiveRecord::Base
td_enable_model_tracer :mytable
end

It logs all columns when the model is changed. Use :only option to limit columns to log:

class MyModel < ActiveRecord::Base
td_enable_model_tracer :mytable, :only => [:id, :mycol]
end

# logs client address on the 'addr' column
add_td_tracer :myaction, 'mytable', :extra=>{:REMOTE_ADDR=>:addr}
Alternatively, use :except option to except columns to log:

# logs referer and path info
add_td_tracer :myaction, 'mytable', :extra=>{:HTTP_REFERER=>:referer, :PATH_INFO=>:path}
class MyModel < ActiveRecord::Base
td_enable_model_tracer :mytable, :except => [:created_at]
end

Add 'static' option to add constant key-value pairs:

add_td_tracer :myaction, 'mytable', :static=>{:version=>1}
class MyModel < ActiveRecord::Base
td_enable_model_tracer :mytable, :except => [:created_at], :static => {'model'=>'my'}
end


=== Logging other logs

You can log anytime using 'TreasureData.log' method:

class MyClass
def mymethod
TreasureData.log('mytable', {:col1=>"val1", :col2=>"val2"}
end
end


== Copyright
Expand Down
254 changes: 10 additions & 244 deletions lib/td/logger.rb
Original file line number Diff line number Diff line change
@@ -1,266 +1,32 @@
require 'fluent/logger'

module TreasureData
module Logger


class Config
def initialize(conf)
if agent = conf['agent']
host, port = agent.split(':',2)
port = (port || 24224).to_i
@agent_host = host
@agent_port = port

@tag = conf['tag']
@tag ||= conf['database']
raise "'tag' nor 'database' options are not set" unless @tag

else
@apikey = conf['apikey']
raise "'apikey' option is not set" unless @apikey

@database = conf['database']
raise "'database' option is not set" unless @database

@auto_create_table = !!conf['auto_create_table']
end
end

attr_reader :agent_host, :agent_port, :tag
attr_reader :apikey, :database, :auto_create_table

def agent_mode?
@agent_host != nil
end
def self.open(apikey, database, auto_create_table=false)
require 'td/logger/tdlog'
TreasureDataLogger.open(apikey, database, auto_create_table)
end


def self.open_agent(tag, agent_host, agent_port)
Fluent::Logger::FluentLogger.open(tag, agent_host, agent_port)
end
end


module TreasureData


def self.log(tag, record)
record['time'] ||= Time.now.to_i
Fluent::Logger.post(tag, record)
end


module Logger
module RailsAgent

CONFIG_SAMPLE = <<EOF
defaults: &defaults
apikey: "YOUR_API_KEY"
database: myapp
table: access
test:
<<: *defaults
development:
<<: *defaults
production:
<<: *defaults
EOF

CONFIG_PATH = 'config/treasure_data.yml'

def self.init(rails)
c = read_config(rails)
return unless c

require 'fluent/logger'
if c.agent_mode?
Fluent::Logger::FluentLogger.open(c.tag, c.agent_host, c.agent_port)
else
require 'td/logger/tdlog'
TreasureDataLogger.open(c.apikey, c.database, c.auto_create_table)
end

rails.middleware.use Middleware
ActionController::Base.class_eval do
include ::TreasureData::Logger::RailsAgent::ControllerLogger
end
end

def self.read_config(rails)
logger = Rails.logger || ::Logger.new(STDOUT)
begin
yaml = YAML.load_file("#{RAILS_ROOT}/#{CONFIG_PATH}")
rescue
logger.warn "Can't load #{CONFIG_PATH} file."
logger.warn " #{$!}"
logger.warn "Put the following file:"
logger.warn sample
return
end

conf = yaml[RAILS_ENV]
unless conf
logger.warn "#{CONFIG_PATH} doesn't include setting for current environment (#{RAILS_ENV})."
logger.warn "Disabling Treasure Data logger."
return
end

begin
return Config.new(conf)
rescue
logger.warn "#{CONFIG_PATH}: #{$!}."
logger.warn "Disabling Treasure Data logger."
return
end
end

class Middleware
def initialize(app, options={})
@app = app
end

def call(env)
r = @app.call(env)

if m = env['treasure_data.log_method']
m.call(env)
end

r
end

def self.set_log_method(env, method)
env['treasure_data.log_method'] = method
end
end

module ControllerLogger
def self.included(mod)
mod.extend(ModuleMethods)
end

class ActionLogger
PARAM_KEY = if defined? Rails
if Rails.respond_to?(:version) && Rails.version =~ /^3/
# Rails 3
'action_dispatch.request.path_parameters'
else
# Rails 2
'action_controller.request.path_parameters'
end
else
# Rack default
'rack.routing_args'
end

def initialize(method, tag, options)
@method = method
@tag = tag

@only = nil
@except = nil
@extra = nil
@static = {}

if o = options[:only_params]
@only = case o
when Array
o
else
[o]
end.map {|e| e.to_s }
end

if o = options[:except_params]
@except = case o
when Array
o
else
[o]
end.map {|e| e.to_s }
end

if o = options[:extra]
@extra = case o
when Hash
m = {}
o.each_pair {|k,v| m[k.to_s] = v.to_s }
m
when Array
o.map {|e|
case e
when Hash
m = {}
e.each_pair {|k,v| m[k.to_s] = v.to_s }
m
else
{e.to_s => e.to_s}
end
}.inject({}) {|r,e| r.merge!(e) }
else
{o.to_s => o.to_s}
end
end

if o = options[:static]
o.each_pair {|k,v| @static[k] = v }
end
end

def call(env)
m = env[PARAM_KEY].dup || {}

if @only
m.reject! {|k,v| !@only.include?(k) }
end
if @except
m.reject! {|k,v| @except.include?(k) }
end
if @extra
@extra.each_pair {|k,v| m[v] = env[k] }
end

m.merge!(@static)

::TreasureData.log(@tag, m)
end
end

module ModuleMethods
def add_td_tracer(method, tag, options={})
al = ActionLogger.new(method, tag, options)
module_eval <<-EOF
def #{method}_td_action_tracer_(*args, &block)
::TreasureData::Logger::RailsAgent::Middleware.set_log_method(request.env, method(:#{method}_td_action_trace_))
#{method}_td_action_tracer_orig_(*args, &block)
end
EOF
module_eval do
define_method(:"#{method}_td_action_trace_", &al.method(:call))
end
alias_method "#{method}_td_action_tracer_orig_", method
alias_method method, "#{method}_td_action_tracer_"
end
class Time
def to_msgpack(out = '')
to_i.to_msgpack(out)
end
end

end
end

end

if defined? Rails
if Rails.respond_to?(:version) && Rails.version =~ /^3/
module TreasureData
class Railtie < Rails::Railtie
initializer "treasure_data_agent.start_plugin" do |app|
TreasureData::Logger::RailsAgent.init(app.config)
end
end
end
else
TreasureData::Logger::RailsAgent.init(Rails.configuration)
end
require 'td/logger/agent/rails'
end

Loading

0 comments on commit c7f175a

Please sign in to comment.