cloudwatchlogger 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +79 -0
- data/cloudwatchlogger.gemspec +27 -0
- data/lib/cloudwatchlogger.rb +23 -0
- data/lib/cloudwatchlogger/client.rb +89 -0
- data/lib/cloudwatchlogger/client/aws_sdk.rb +26 -0
- data/lib/cloudwatchlogger/client/aws_sdk/threaded.rb +122 -0
- data/lib/cloudwatchlogger/version.rb +5 -0
- metadata +94 -0
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
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
|
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: []
|