cloudwatchlogger 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0938bf9df510f455dee57614bedada1436b18be4
4
+ data.tar.gz: 68914c8ee7364ebfbbf9efbb64ef1c156f5d0b8c
5
+ SHA512:
6
+ metadata.gz: b4b5535c0195a478633513eb4fdb17d0855063e32ab54882674b2cf4617acde5b0b8fd290fddce35994e37801889f7a575f56e12b04f4859b98e932c1390b3c9
7
+ data.tar.gz: cb1c4a3124dbf2f9b1bee5443feee6a8dcf78ef14a6e10a3e968e6d4d1c9f815b3bf38644b190f00fa081d66fc2366947c730ab44e62e565e0837fd593bed6be
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ platforms :jruby do
6
+ gem 'jruby-openssl'
7
+ gem 'multi_json', '~> 1' # https://github.com/bundler/bundler/issues/4157
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 amvse
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ Overview
2
+ --------
3
+
4
+ Send logged messages to [AWS CloudWatch Logs](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/WhatIsCloudWatchLogs.html) using the ruby [AWS SDK](http://docs.aws.amazon.com/sdkforruby/api/index.html).
5
+
6
+ Can be used in place of Ruby's Logger
7
+ (<http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/>)
8
+
9
+ In fact, it (currently) returns an instance of Logger.
10
+
11
+ Forked from [loggiler](https://github.com/freeformz/logglier).
12
+
13
+ Usage
14
+ -----
15
+ ```ruby
16
+ require 'cloudwatchlogger'
17
+
18
+ log = CloudWatchLogger.new({access_key_id: 'YOUR_ACCESS_KEY_ID', secret_access_key: 'YOUR_SECRET_ACCESS_KEY'}, 'YOUR_CLOUDWATCH_LOG_GROUP')
19
+
20
+ log.info("Hello World from Ruby")
21
+ ```
22
+
23
+ ### With Rails
24
+
25
+ config/environments/production.rb
26
+ ```ruby
27
+ RailsApplication::Application.configure do
28
+ config.logger = CloudWatchLogger.new({access_key_id: 'YOUR_ACCESS_KEY_ID', secret_access_key: 'YOUR_SECRET_ACCESS_KEY'}, 'YOUR_CLOUDWATCH_LOG_GROUP')
29
+ end
30
+ ```
31
+
32
+
33
+ ### With Rails 4
34
+
35
+ config/initializers/cloudwatchlogger.rb
36
+ ```ruby
37
+ cloudwatchlogger = CloudWatchLogger.new({access_key_id: 'YOUR_ACCESS_KEY_ID', secret_access_key: 'YOUR_SECRET_ACCESS_KEY'}, 'YOUR_CLOUDWATCH_LOG_GROUP')
38
+ Rails.logger.extend(ActiveSupport::Logger.broadcast(cloudwatchlogger))
39
+ ```
40
+
41
+ Logging
42
+ -------
43
+
44
+ CloudWatchLogger.new returns a ruby Logger object, so take a look at:
45
+
46
+ http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/
47
+
48
+ The Logger's logdev has some special format handling though.
49
+
50
+ ### Logging a string
51
+
52
+ ```ruby
53
+ log.warn "test"
54
+ ```
55
+
56
+ Will produce the following log message in CloudWatch Logs:
57
+
58
+ ```
59
+ "<Date> severity=WARN, test"
60
+ ```
61
+
62
+ ### Logging a Hash
63
+
64
+ ```ruby
65
+ log.warn :boom => :box, :bar => :soap
66
+ ```
67
+
68
+ Will produce the following log message in CloudWatch Logs:
69
+
70
+ ```
71
+ "<Date> severity=WARN, boom=box, bar=soap"
72
+ ```
73
+
74
+ Bugs
75
+ -----
76
+
77
+ https://github.com/amvse/cloudwatchlogger/issues
78
+
79
+ Pull requests welcome.
@@ -0,0 +1,27 @@
1
+ dir = File.dirname(__FILE__)
2
+ require File.expand_path(File.join(dir, 'lib', 'cloudwatchlogger', 'version'))
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'cloudwatchlogger'
6
+ s.version = CloudWatchLogger::VERSION
7
+ s.date = Time.now
8
+ s.summary = 'AWS CloudWatchLogs compatiable logger for ruby.'
9
+ s.description = 'Logger => CloudWatchLogs'
10
+
11
+ s.license = "http://opensource.org/licenses/MIT"
12
+
13
+ s.authors = ["Zane Shannon"]
14
+ s.email = 'zcs@amvse.com'
15
+ s.homepage = 'http://github.com/amvse/cloudwatchlogger'
16
+
17
+ s.files = %w{ README.md Gemfile LICENSE cloudwatchlogger.gemspec } + Dir["lib/**/*.rb"]
18
+ s.require_paths = ['lib']
19
+ s.test_files = Dir["spec/**/*.rb"]
20
+
21
+ s.required_ruby_version = '>= 1.8.6'
22
+ s.required_rubygems_version = '>= 1.3.6'
23
+
24
+ s.add_runtime_dependency 'uuid', '~> 2'
25
+ s.add_runtime_dependency 'multi_json', '~> 1'
26
+ s.add_runtime_dependency 'aws-sdk', '~> 2'
27
+ end
@@ -0,0 +1,23 @@
1
+ require File.join(File.dirname(__FILE__), 'cloudwatchlogger', 'client')
2
+
3
+ require 'logger'
4
+
5
+ module CloudWatchLogger
6
+
7
+ class LogGroupNameRequired < ArgumentError; end
8
+ class LogEventRejected < ArgumentError; end
9
+
10
+ def self.new(credentials, log_group_name, log_stream_name=nil, opts={})
11
+ client = CloudWatchLogger::Client.new(credentials, log_group_name, log_stream_name, opts)
12
+ logger = Logger.new(client)
13
+
14
+ if client.respond_to?(:formatter)
15
+ logger.formatter = client.formatter
16
+ elsif client.respond_to?(:datetime_format)
17
+ logger.datetime_format = client.datetime_format
18
+ end
19
+
20
+ logger
21
+ end
22
+
23
+ end
@@ -0,0 +1,89 @@
1
+ require 'multi_json'
2
+ require 'socket'
3
+ require 'uuid'
4
+
5
+ module CloudWatchLogger
6
+ module Client
7
+
8
+ def self.new(credentials, log_group_name, log_stream_name=nil, opts={})
9
+ unless log_group_name
10
+ raise LogGroupNameRequired.new
11
+ end
12
+
13
+ CloudWatchLogger::Client::AWS_SDK.new(credentials, log_group_name, log_stream_name, opts)
14
+ end
15
+
16
+ module InstanceMethods
17
+
18
+ def masherize_key(prefix,key)
19
+ [prefix,key.to_s].compact.join('.')
20
+ end
21
+
22
+ def masher(hash, prefix=nil)
23
+ hash.map do |v|
24
+ if v[1].is_a?(Hash)
25
+ masher(v[1],masherize_key(prefix,v[0]))
26
+ else
27
+ "#{masherize_key(prefix,v[0])}=" << case v[1]
28
+ when Symbol
29
+ v[1].to_s
30
+ else
31
+ v[1].inspect
32
+ end
33
+ end
34
+ end.join(", ")
35
+ end
36
+
37
+ def formatter
38
+ proc do |severity, datetime, progname, msg|
39
+ processid=Process.pid
40
+ if @format == :json && msg.is_a?(Hash)
41
+ MultiJson.dump(msg.merge({ :severity => severity,
42
+ :datetime => datetime,
43
+ :progname => progname,
44
+ :pid => processid }))
45
+ else
46
+ message = "#{datetime} "
47
+ message << massage_message(msg, severity, processid)
48
+ end
49
+ end
50
+ end
51
+
52
+ def massage_message(incoming_message, severity, processid)
53
+ outgoing_message = ""
54
+
55
+ outgoing_message << "pid=#{processid}, severity=#{severity}, "
56
+
57
+ case incoming_message
58
+ when Hash
59
+ outgoing_message << masher(incoming_message)
60
+ when String
61
+ outgoing_message << incoming_message
62
+ else
63
+ outgoing_message << incoming_message.inspect
64
+ end
65
+ outgoing_message
66
+ end
67
+
68
+ def setup_credentials(credentials)
69
+ @credentials = credentials
70
+ end
71
+
72
+ def setup_log_group_name(name)
73
+ @log_group_name = name
74
+ end
75
+
76
+ def setup_log_stream_name(name)
77
+ @log_stream_name = name
78
+ if @log_stream_name.nil?
79
+ uuid = UUID.new
80
+ @log_stream_name = "#{Socket.gethostname}-#{uuid.generate}"
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
88
+
89
+ require File.join(File.dirname(__FILE__), 'client', 'aws_sdk')
@@ -0,0 +1,26 @@
1
+ require File.join(File.dirname(__FILE__), 'aws_sdk', 'threaded')
2
+
3
+ module CloudWatchLogger
4
+ module Client
5
+ class AWS_SDK
6
+ include CloudWatchLogger::Client::InstanceMethods
7
+
8
+ attr_reader :input_uri, :deliverer
9
+
10
+ def initialize(credentials, log_group_name, log_stream_name, opts={})
11
+ setup_credentials(credentials)
12
+ setup_log_group_name(log_group_name)
13
+ setup_log_stream_name(log_stream_name)
14
+ @deliverer = CloudWatchLogger::Client::AWS_SDK::DeliveryThreadManager.new(@credentials, @log_group_name, @log_stream_name, opts)
15
+ end
16
+
17
+ def write(message)
18
+ @deliverer.deliver(message)
19
+ end
20
+
21
+ def close
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,122 @@
1
+ require 'aws-sdk'
2
+ require 'thread'
3
+
4
+ module CloudWatchLogger
5
+ module Client
6
+ class AWS_SDK
7
+
8
+ # Used by the Threaded client to manage the delivery thread
9
+ # recreating it if is lost due to a fork.
10
+ #
11
+ class DeliveryThreadManager
12
+ def initialize(credentials, log_group_name, log_stream_name, opts={})
13
+ @credentials, @log_group_name, @log_stream_name, @opts = credentials, log_group_name, log_stream_name, opts
14
+ start_thread
15
+ end
16
+
17
+ # Pushes a message to the delivery thread, starting one if necessary
18
+ def deliver(message)
19
+ start_thread unless @thread.alive?
20
+ @thread.deliver(message)
21
+ #Race condition? Sometimes we need to rescue this and start a new thread
22
+ rescue NoMethodError
23
+ @thread.kill #Try not to leak threads, should already be dead anyway
24
+ start_thread
25
+ retry
26
+ end
27
+
28
+ private
29
+
30
+ def start_thread
31
+ @thread = DeliveryThread.new(@credentials, @log_group_name, @log_stream_name, @opts)
32
+ end
33
+ end
34
+
35
+ class DeliveryThread < Thread
36
+
37
+ def initialize(credentials, log_group_name, log_stream_name, opts={})
38
+ @credentials, @log_group_name, @log_stream_name, @opts = credentials, log_group_name, log_stream_name, opts
39
+ opts[:open_timeout] = opts[:open_timeout] || 120
40
+ opts[:read_timeout] = opts[:read_timeout] || 120
41
+
42
+ @queue = Queue.new
43
+ @exiting = false
44
+
45
+ super do
46
+ loop do
47
+
48
+ if @client.nil?
49
+ connect! opts
50
+ end
51
+
52
+ msg = @queue.pop
53
+ break if msg == :__delivery_thread_exit_signal__
54
+
55
+ begin
56
+ event = {
57
+ log_group_name: @log_group_name,
58
+ log_stream_name: @log_stream_name,
59
+ log_events: [{
60
+ timestamp: (Time.now.utc.to_f.round(3)*1000).to_i,
61
+ message: msg
62
+ }]
63
+ }
64
+
65
+ if token = @sequence_token
66
+ event[:sequence_token] = token
67
+ end
68
+ response = @client.put_log_events(event)
69
+ unless response.rejected_log_events_info.nil?
70
+ raise CloudWatchLogger::LogEventRejected
71
+ end
72
+ @sequence_token = response.next_sequence_token
73
+ rescue Aws::CloudWatchLogs::Errors::InvalidSequenceTokenException => err
74
+ @sequence_token = err.message.split(' ').last
75
+ retry
76
+ end
77
+ end
78
+ end
79
+
80
+ at_exit {
81
+ exit!
82
+ join
83
+ }
84
+ end
85
+
86
+ # Signals the queue that we're exiting
87
+ def exit!
88
+ @exiting = true
89
+ @queue.push :__delivery_thread_exit_signal__
90
+ end
91
+
92
+ # Pushes a message onto the internal queue
93
+ def deliver(message)
94
+ @queue.push(message)
95
+ end
96
+
97
+ def connect!(opts={})
98
+ @client = Aws::CloudWatchLogs::Client.new(
99
+ region: @opts[:region] || 'us-east-1',
100
+ access_key_id: @credentials[:access_key_id],
101
+ secret_access_key: @credentials[:secret_access_key],
102
+ http_open_timeout: opts[:open_timeout],
103
+ http_read_timeout: opts[:read_timeout]
104
+ )
105
+ begin
106
+ @client.create_log_stream(
107
+ log_group_name: @log_group_name,
108
+ log_stream_name: @log_stream_name
109
+ )
110
+ rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException => err
111
+ @client.create_log_group(
112
+ log_group_name: @log_group_name
113
+ )
114
+ retry
115
+ rescue Aws::CloudWatchLogs::Errors::ResourceAlreadyExistsException => err
116
+ end
117
+ end
118
+ end
119
+
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,5 @@
1
+ module CloudWatchLogger
2
+
3
+ VERSION = '0.1.0'
4
+
5
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloudwatchlogger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Zane Shannon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: uuid
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: multi_json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: aws-sdk
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2'
55
+ description: Logger => CloudWatchLogs
56
+ email: zcs@amvse.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - Gemfile
62
+ - LICENSE
63
+ - README.md
64
+ - cloudwatchlogger.gemspec
65
+ - lib/cloudwatchlogger.rb
66
+ - lib/cloudwatchlogger/client.rb
67
+ - lib/cloudwatchlogger/client/aws_sdk.rb
68
+ - lib/cloudwatchlogger/client/aws_sdk/threaded.rb
69
+ - lib/cloudwatchlogger/version.rb
70
+ homepage: http://github.com/amvse/cloudwatchlogger
71
+ licenses:
72
+ - http://opensource.org/licenses/MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.8.6
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: 1.3.6
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.5.1
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: AWS CloudWatchLogs compatiable logger for ruby.
94
+ test_files: []