td-logger 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog ADDED
@@ -0,0 +1,5 @@
1
+
2
+ Release 0.1.0 - 2011/08/21
3
+
4
+ * First release
5
+
data/README.rdoc ADDED
@@ -0,0 +1,60 @@
1
+ = Treasure Data logging library for Rails
2
+
3
+ == Getting Started
4
+
5
+ Add the following line to your Gemfile:
6
+
7
+ gem 'td-logger'
8
+
9
+ For Rails 2.x (not tested) without Bundler,
10
+ edit +environment.rb+ and add to the initalizer block:
11
+
12
+ config.gem "td-logger"
13
+
14
+ And then add +config/treasure_data.yml+ file as following:
15
+
16
+ # logging to Treasure Data directry
17
+ development:
18
+ apikey: "YOUR_API_KEY"
19
+ database: myapp
20
+ auto_create_table: true
21
+
22
+ # logging via td-agent
23
+ production:
24
+ agent: "localhost:24224"
25
+ tag: myapp
26
+
27
+ # disable logging
28
+ test:
29
+
30
+ === Logging actions
31
+
32
+ Add 'add_td_tracer' line to your controller classes:
33
+
34
+ class YourController < ApplicationController
35
+ def myaction
36
+ # ...
37
+ end
38
+ add_td_tracer :myaction, 'mytable'
39
+ end
40
+
41
+ It logs {"controller":"your","action":"myaction"}. Additionally, routing parameters are included automatically.
42
+
43
+ You can add environment variables by adding 'extra' option as follows:
44
+
45
+ # logs client address on the 'addr' column
46
+ add_td_tracer :myaction, 'mytable', :extra=>{:REMOTE_ADDR=>:addr}
47
+
48
+ # logs referer and path info
49
+ add_td_tracer :myaction, 'mytable', :extra=>{:HTTP_REFERER=>:referer, :PATH_INFO=>:path}
50
+
51
+ Add 'static' option to add constant key-value pairs:
52
+
53
+ add_td_tracer :myaction, 'mytable', :static=>{:version=>1}
54
+
55
+
56
+ == Copyright
57
+
58
+ Copyright:: Copyright (c) 2011 Treasure Data Inc.
59
+ License:: Apache License, Version 2.0
60
+
@@ -0,0 +1,187 @@
1
+
2
+ module TreasureData
3
+ module Logger
4
+
5
+ # TODO shutdown handler
6
+
7
+ class TreasureDataLogger < Fluent::Logger::LoggerBase
8
+ def initialize(apikey, tag, auto_create_table)
9
+ require 'thread'
10
+ require 'stringio'
11
+ require 'zlib'
12
+ require 'msgpack'
13
+ require 'time'
14
+ require 'net/http'
15
+ require 'cgi'
16
+ require 'logger'
17
+
18
+ @tag = tag
19
+ @auto_create_table = auto_create_table
20
+ @logger = ::Logger.new(STDOUT)
21
+
22
+ # TODO
23
+ gem 'td'
24
+ require 'td/client'
25
+ @client = TreasureData::Client.new(apikey)
26
+
27
+ @mutex = Mutex.new
28
+ @cond = ConditionVariable.new
29
+ @map = {} # (db,table) => buffer:String
30
+ @queue = []
31
+
32
+ @chunk_limit = 8*1024*1024
33
+ @flush_interval = 60
34
+ @retry_wait = 1.0
35
+ @retry_limit = 8
36
+
37
+ @finish = false
38
+ @next_time = Time.now.to_i + @flush_interval
39
+ @error_count = 0
40
+ @upload_thread = Thread.new(&method(:upload_main))
41
+ end
42
+
43
+ attr_accessor :logger
44
+
45
+ def close
46
+ @finish = true
47
+ @mutex.synchronize {
48
+ @flush_now = true
49
+ @cond.signal
50
+ }
51
+ end
52
+
53
+ def post(tag, record)
54
+ tag = "#{@tag}.#{tag}"
55
+ db, table = tag.split('.')[-2, 2]
56
+
57
+ record['time'] ||= Time.now.to_i
58
+
59
+ key = [db, table]
60
+ @mutex.synchronize do
61
+ buffer = (@map[key] ||= '')
62
+ record.to_msgpack(buffer)
63
+
64
+ if buffer.size > @chunk_limit
65
+ @queue << [db, table, buffer]
66
+ @map.delete(key)
67
+ @cond.signal
68
+ end
69
+ end
70
+
71
+ nil
72
+ end
73
+
74
+ def upload_main
75
+ @mutex.lock
76
+ until @finish
77
+ now = Time.now.to_i
78
+
79
+ if @next_time <= now || (@flush_now && @error_count == 0)
80
+ @mutex.unlock
81
+ begin
82
+ try_flush
83
+ ensure
84
+ @mutex.lock
85
+ end
86
+ @flush_now = false
87
+ end
88
+
89
+ if @error_count == 0
90
+ next_wait = @flush_interval
91
+ else
92
+ next_wait = @retry_wait * (2 ** (@error_count-1))
93
+ end
94
+ @next_time = next_wait + now
95
+
96
+ cond_wait(next_wait)
97
+ end
98
+
99
+ rescue
100
+ @logger.error "Unexpected error: #{$!}"
101
+ $!.backtrace.each {|bt|
102
+ @logger.info bt
103
+ }
104
+ ensure
105
+ @mutex.unlock
106
+ end
107
+
108
+ private
109
+ def try_flush
110
+ @mutex.synchronize do
111
+ if @queue.empty?
112
+ @map.reject! {|(db,table),buffer|
113
+ @queue << [db, table, buffer]
114
+ }
115
+ end
116
+ end
117
+
118
+ until @queue.empty?
119
+ tuple = @queue.first
120
+
121
+ begin
122
+ upload(*tuple)
123
+ @queue.shift
124
+ @error_count = 0
125
+ rescue
126
+ if @error_count < @retry_limit
127
+ @logger.error "Failed to upload logs to Treasure Data, retrying: #{$!}"
128
+ @error_count += 1
129
+ else
130
+ @logger.error "Failed to upload logs to Treasure Data, trashed: #{$!}"
131
+ $!.backtrace.each {|bt|
132
+ @logger.info bt
133
+ }
134
+ @error_count = 0
135
+ @queue.clear
136
+ end
137
+ return
138
+ end
139
+ end
140
+ end
141
+
142
+ def upload(db, table, buffer)
143
+ out = StringIO.new
144
+ Zlib::GzipWriter.wrap(out) {|gz| gz.write buffer }
145
+ stream = StringIO.new(out.string)
146
+
147
+ begin
148
+ @client.import(db, table, "msgpack.gz", stream, stream.size)
149
+ rescue TreasureData::NotFoundError
150
+ unless @auto_create_table
151
+ raise $!
152
+ end
153
+ @logger.info "Creating table #{db}.#{table} on TreasureData"
154
+ begin
155
+ @client.create_log_table(db, table)
156
+ rescue TreasureData::NotFoundError
157
+ @client.create_database(db)
158
+ @client.create_log_table(db, table)
159
+ end
160
+ retry
161
+ end
162
+ end
163
+
164
+ def e(s)
165
+ CGI.escape(s.to_s)
166
+ end
167
+
168
+ if ConditionVariable.new.method(:wait).arity == 1
169
+ #$log.warn "WARNING: Running on Ruby 1.8. Ruby 1.9 is recommended."
170
+ require 'timeout'
171
+ def cond_wait(sec)
172
+ Timeout.timeout(sec) {
173
+ @cond.wait(@mutex)
174
+ }
175
+ rescue Timeout::Error
176
+ end
177
+ else
178
+ def cond_wait(sec)
179
+ @cond.wait(@mutex, sec)
180
+ end
181
+ end
182
+ end
183
+
184
+
185
+ end
186
+ end
187
+
@@ -0,0 +1,7 @@
1
+ module TreasureData
2
+ module Logger
3
+
4
+ VERSION = '0.1.0'
5
+
6
+ end
7
+ end
data/lib/td/logger.rb ADDED
@@ -0,0 +1,266 @@
1
+
2
+ module TreasureData
3
+ module Logger
4
+
5
+
6
+ class Config
7
+ def initialize(conf)
8
+ if agent = conf['agent']
9
+ host, port = agent.split(':',2)
10
+ port = (port || 24224).to_i
11
+ @agent_host = host
12
+ @agent_port = port
13
+
14
+ @tag = conf['tag']
15
+ @tag ||= conf['database']
16
+ raise "'tag' nor 'database' options are not set" unless @tag
17
+
18
+ else
19
+ @apikey = conf['apikey']
20
+ raise "'apikey' option is not set" unless @apikey
21
+
22
+ @database = conf['database']
23
+ raise "'database' option is not set" unless @database
24
+
25
+ @auto_create_table = !!conf['auto_create_table']
26
+ end
27
+ end
28
+
29
+ attr_reader :agent_host, :agent_port, :tag
30
+ attr_reader :apikey, :database, :auto_create_table
31
+
32
+ def agent_mode?
33
+ @agent_host != nil
34
+ end
35
+ end
36
+
37
+
38
+ end
39
+ end
40
+
41
+
42
+ module TreasureData
43
+
44
+
45
+ def self.log(tag, record)
46
+ record['time'] ||= Time.now.to_i
47
+ Fluent::Logger.post(tag, record)
48
+ end
49
+
50
+
51
+ module Logger
52
+ module RailsAgent
53
+
54
+ CONFIG_SAMPLE = <<EOF
55
+ defaults: &defaults
56
+ apikey: "YOUR_API_KEY"
57
+ database: myapp
58
+ table: access
59
+
60
+ test:
61
+ <<: *defaults
62
+
63
+ development:
64
+ <<: *defaults
65
+
66
+ production:
67
+ <<: *defaults
68
+ EOF
69
+
70
+ CONFIG_PATH = 'config/treasure_data.yml'
71
+
72
+ def self.init(rails)
73
+ c = read_config(rails)
74
+ return unless c
75
+
76
+ require 'fluent/logger'
77
+ if c.agent_mode?
78
+ Fluent::Logger::FluentLogger.open(c.tag, c.agent_host, c.agent_port)
79
+ else
80
+ require 'td/logger/tdlog'
81
+ TreasureDataLogger.open(c.apikey, c.database, c.auto_create_table)
82
+ end
83
+
84
+ rails.middleware.use Middleware
85
+ ActionController::Base.class_eval do
86
+ include ::TreasureData::Logger::RailsAgent::ControllerLogger
87
+ end
88
+ end
89
+
90
+ def self.read_config(rails)
91
+ logger = Rails.logger || ::Logger.new(STDOUT)
92
+ begin
93
+ yaml = YAML.load_file("#{RAILS_ROOT}/#{CONFIG_PATH}")
94
+ rescue
95
+ logger.warn "Can't load #{CONFIG_PATH} file."
96
+ logger.warn " #{$!}"
97
+ logger.warn "Put the following file:"
98
+ logger.warn sample
99
+ return
100
+ end
101
+
102
+ conf = yaml[RAILS_ENV]
103
+ unless conf
104
+ logger.warn "#{CONFIG_PATH} doesn't include setting for current environment (#{RAILS_ENV})."
105
+ logger.warn "Disabling Treasure Data logger."
106
+ return
107
+ end
108
+
109
+ begin
110
+ return Config.new(conf)
111
+ rescue
112
+ logger.warn "#{CONFIG_PATH}: #{$!}."
113
+ logger.warn "Disabling Treasure Data logger."
114
+ return
115
+ end
116
+ end
117
+
118
+ class Middleware
119
+ def initialize(app, options={})
120
+ @app = app
121
+ end
122
+
123
+ def call(env)
124
+ r = @app.call(env)
125
+
126
+ if m = env['treasure_data.log_method']
127
+ m.call(env)
128
+ end
129
+
130
+ r
131
+ end
132
+
133
+ def self.set_log_method(env, method)
134
+ env['treasure_data.log_method'] = method
135
+ end
136
+ end
137
+
138
+ module ControllerLogger
139
+ def self.included(mod)
140
+ mod.extend(ModuleMethods)
141
+ end
142
+
143
+ class ActionLogger
144
+ PARAM_KEY = if defined? Rails
145
+ if Rails.respond_to?(:version) && Rails.version =~ /^3/
146
+ # Rails 3
147
+ 'action_dispatch.request.path_parameters'
148
+ else
149
+ # Rails 2
150
+ 'action_controller.request.path_parameters'
151
+ end
152
+ else
153
+ # Rack default
154
+ 'rack.routing_args'
155
+ end
156
+
157
+ def initialize(method, tag, options)
158
+ @method = method
159
+ @tag = tag
160
+
161
+ @only = nil
162
+ @except = nil
163
+ @extra = nil
164
+ @static = {}
165
+
166
+ if o = options[:only_params]
167
+ @only = case o
168
+ when Array
169
+ o
170
+ else
171
+ [o]
172
+ end.map {|e| e.to_s }
173
+ end
174
+
175
+ if o = options[:except_params]
176
+ @except = case o
177
+ when Array
178
+ o
179
+ else
180
+ [o]
181
+ end.map {|e| e.to_s }
182
+ end
183
+
184
+ if o = options[:extra]
185
+ @extra = case o
186
+ when Hash
187
+ m = {}
188
+ o.each_pair {|k,v| m[k.to_s] = v.to_s }
189
+ m
190
+ when Array
191
+ o.map {|e|
192
+ case e
193
+ when Hash
194
+ m = {}
195
+ e.each_pair {|k,v| m[k.to_s] = v.to_s }
196
+ m
197
+ else
198
+ {e.to_s => e.to_s}
199
+ end
200
+ }.inject({}) {|r,e| r.merge!(e) }
201
+ else
202
+ {o.to_s => o.to_s}
203
+ end
204
+ end
205
+
206
+ if o = options[:static]
207
+ o.each_pair {|k,v| @static[k] = v }
208
+ end
209
+ end
210
+
211
+ def call(env)
212
+ m = env[PARAM_KEY].dup || {}
213
+
214
+ if @only
215
+ m.reject! {|k,v| !@only.include?(k) }
216
+ end
217
+ if @except
218
+ m.reject! {|k,v| @except.include?(k) }
219
+ end
220
+ if @extra
221
+ @extra.each_pair {|k,v| m[v] = env[k] }
222
+ end
223
+
224
+ m.merge!(@static)
225
+
226
+ ::TreasureData.log(@tag, m)
227
+ end
228
+ end
229
+
230
+ module ModuleMethods
231
+ def add_td_tracer(method, tag, options={})
232
+ al = ActionLogger.new(method, tag, options)
233
+ module_eval <<-EOF
234
+ def #{method}_td_action_tracer_(*args, &block)
235
+ ::TreasureData::Logger::RailsAgent::Middleware.set_log_method(request.env, method(:#{method}_td_action_trace_))
236
+ #{method}_td_action_tracer_orig_(*args, &block)
237
+ end
238
+ EOF
239
+ module_eval do
240
+ define_method(:"#{method}_td_action_trace_", &al.method(:call))
241
+ end
242
+ alias_method "#{method}_td_action_tracer_orig_", method
243
+ alias_method method, "#{method}_td_action_tracer_"
244
+ end
245
+ end
246
+ end
247
+
248
+ end
249
+ end
250
+
251
+ end
252
+
253
+ if defined? Rails
254
+ if Rails.respond_to?(:version) && Rails.version =~ /^3/
255
+ module TreasureData
256
+ class Railtie < Rails::Railtie
257
+ initializer "treasure_data_agent.start_plugin" do |app|
258
+ TreasureData::Logger::RailsAgent.init(app.config)
259
+ end
260
+ end
261
+ end
262
+ else
263
+ TreasureData::Logger::RailsAgent.init(Rails.configuration)
264
+ end
265
+ end
266
+
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: td-logger
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Sadayuki Furuhashi
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-21 00:00:00 +09:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: msgpack
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 0
32
+ - 4
33
+ - 4
34
+ version: 0.4.4
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: td
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 9
46
+ segments:
47
+ - 0
48
+ - 7
49
+ - 5
50
+ version: 0.7.5
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: fluent-logger
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 19
62
+ segments:
63
+ - 0
64
+ - 3
65
+ - 0
66
+ version: 0.3.0
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ description:
70
+ email:
71
+ executables: []
72
+
73
+ extensions: []
74
+
75
+ extra_rdoc_files:
76
+ - ChangeLog
77
+ - README.rdoc
78
+ files:
79
+ - lib/td/logger.rb
80
+ - lib/td/logger/tdlog.rb
81
+ - lib/td/logger/version.rb
82
+ - ChangeLog
83
+ - README.rdoc
84
+ has_rdoc: true
85
+ homepage:
86
+ licenses: []
87
+
88
+ post_install_message:
89
+ rdoc_options:
90
+ - --charset=UTF-8
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ requirements: []
112
+
113
+ rubyforge_project:
114
+ rubygems_version: 1.3.7
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: Treasure Data command line tool
118
+ test_files: []
119
+