cm-cloudwatchlogger 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0fcfddd765a3ef7b8613f58504a1849fba5786a572ee3692432bc5985f56e023
4
+ data.tar.gz: a1197b067bdf6b6e86f62a8a505ca6a5f2beee8aa4e8f4b391ec1180a2014dd8
5
+ SHA512:
6
+ metadata.gz: d3d28307ebf7aa1d3c5b51f5a3950ea11a4b85307289a5dcce229385a14fb527516f3ac288c2e745fddfb674e96defd9ad07aee15f40c9558c6d60787dad415d
7
+ data.tar.gz: 861e199cd13f9bc4b1fac54ca9f7ee33f1f9e09d3f5065673b5ea753769fe5f540061983ebea824becc5748770d82f2fa05d41266ec932a68ae9f2a1d2bd28e2
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) 2018 Zane Shannon
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,91 @@
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 (<https://github.com/zshannon/cloudwatchlogger>).
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
+ The region will default to the value of the environment variable `AWS_REGION`. In case you need to pass different region or group's different Log Stream name:
24
+
25
+ ```ruby
26
+ log = CloudWatchLogger.new({
27
+ access_key_id: 'YOUR_ACCESS_KEY_ID',
28
+ secret_access_key: 'YOUR_SECRET_ACCESS_KEY'
29
+ }, 'YOUR_CLOUDWATCH_LOG_GROUP', 'YOUR_CLOUDWATCH_LOG_STREAM', region: 'YOUR_CLOUDWATCH_REGION' )
30
+ ```
31
+
32
+ Provding an empty hash instead of credentials will cause the AWS SDK to search the default credential provider chain for credentials, namely:
33
+
34
+ 1. Environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
35
+ 1. Amazon ECS container credentials (task role)
36
+ 1. Instance profile credentials (IAM role)
37
+
38
+ ### With Rails
39
+
40
+ config/environments/production.rb
41
+ ```ruby
42
+ RailsApplication::Application.configure do
43
+ config.logger = CloudWatchLogger.new({access_key_id: 'YOUR_ACCESS_KEY_ID', secret_access_key: 'YOUR_SECRET_ACCESS_KEY'}, 'YOUR_CLOUDWATCH_LOG_GROUP', 'YOUR_CLOUDWATCH_LOG_STREAM', 'YOUR_CLOUDWATCH_REGION')
44
+ end
45
+ ```
46
+
47
+
48
+ Logging
49
+ -------
50
+
51
+ CloudWatchLogger.new returns a ruby Logger object, so take a look at:
52
+
53
+ http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/
54
+
55
+ The Logger's logdev has some special format handling though.
56
+
57
+ ### Logging a string
58
+
59
+ ```ruby
60
+ log.warn "test"
61
+ ```
62
+
63
+ Will produce the following log message in CloudWatch Logs:
64
+
65
+ ```
66
+ "<Date> severity=WARN, test"
67
+ ```
68
+
69
+ ### Logging a Hash
70
+
71
+ ```ruby
72
+ log.warn :boom => :box, :bar => :soap
73
+ ```
74
+
75
+ Will produce the following log message in CloudWatch Logs:
76
+
77
+ ```
78
+ "<Date> severity=WARN, boom=box, bar=soap"
79
+ ```
80
+
81
+ Releasing
82
+ -----
83
+
84
+ `rake release`
85
+
86
+ Bugs
87
+ -----
88
+
89
+ https://github.com/commutatus/cloudwatchlogger/issues
90
+
91
+ 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 = 'cm-cloudwatchlogger'
6
+ s.version = CloudWatchLogger::VERSION
7
+ s.date = Time.now
8
+ s.summary = 'Amazon CloudWatch Logs compatiable logger for ruby.'
9
+ s.description = 'Logger => CloudWatchLogs'
10
+
11
+ s.license = "MIT"
12
+
13
+ s.authors = ["Zane Shannon", "Manikandan"]
14
+ s.email = ['z@zcs.me', 'manikandan0603@gmail.com']
15
+ s.homepage = 'http://github.com/commutatus/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 'securerandom', '~> 0.1'
25
+ s.add_runtime_dependency 'multi_json', '~> 1'
26
+ s.add_runtime_dependency 'aws-sdk-cloudwatchlogs', '~> 1'
27
+ end
@@ -0,0 +1,118 @@
1
+ require 'aws-sdk-cloudwatchlogs'
2
+ require 'thread'
3
+
4
+ module CloudWatchLogger
5
+ module Client
6
+ class AWS_SDK
7
+ # Used by the Threaded client to manage the delivery thread
8
+ # recreating it if is lost due to a fork.
9
+ #
10
+ class DeliveryThreadManager
11
+ def initialize(credentials, log_group_name, log_stream_name, opts = {})
12
+ @credentials = credentials
13
+ @log_group_name = log_group_name
14
+ @log_stream_name = log_stream_name
15
+ @opts = opts
16
+ start_thread
17
+ end
18
+
19
+ # Pushes a message to the delivery thread, starting one if necessary
20
+ def deliver(message)
21
+ start_thread unless @thread.alive?
22
+ @thread.deliver(message)
23
+ # Race condition? Sometimes we need to rescue this and start a new thread
24
+ rescue NoMethodError
25
+ @thread.kill # Try not to leak threads, should already be dead anyway
26
+ start_thread
27
+ retry
28
+ end
29
+
30
+ private
31
+
32
+ def start_thread
33
+ @thread = DeliveryThread.new(@credentials, @log_group_name, @log_stream_name, @opts)
34
+ end
35
+ end
36
+
37
+ class DeliveryThread < Thread
38
+ def initialize(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
+ @credentials = credentials
42
+ @log_group_name = log_group_name
43
+ @log_stream_name = log_stream_name
44
+ @opts = opts
45
+
46
+ @queue = Queue.new
47
+ @exiting = false
48
+
49
+ super do
50
+ loop do
51
+ connect!(opts) if @client.nil?
52
+
53
+ message_object = @queue.pop
54
+ break if message_object == :__delivery_thread_exit_signal__
55
+
56
+ begin
57
+ event = {
58
+ log_group_name: @log_group_name,
59
+ log_stream_name: @log_stream_name,
60
+ log_events: [{
61
+ timestamp: message_object[:epoch_time],
62
+ message: message_object[:message]
63
+ }]
64
+ }
65
+ event[:sequence_token] = @sequence_token if @sequence_token
66
+ response = @client.put_log_events(event)
67
+ unless response.rejected_log_events_info.nil?
68
+ raise CloudWatchLogger::LogEventRejected
69
+ end
70
+ @sequence_token = response.next_sequence_token
71
+ rescue Aws::CloudWatchLogs::Errors::InvalidSequenceTokenException => err
72
+ @sequence_token = err.message.split(' ').last
73
+ retry
74
+ end
75
+ end
76
+ end
77
+
78
+ at_exit do
79
+ exit!
80
+ join
81
+ end
82
+ end
83
+
84
+ # Signals the queue that we're exiting
85
+ def exit!
86
+ @exiting = true
87
+ @queue.push :__delivery_thread_exit_signal__
88
+ end
89
+
90
+ # Pushes a message onto the internal queue
91
+ def deliver(message)
92
+ @queue.push(message)
93
+ end
94
+
95
+ def connect!(opts = {})
96
+ args = { http_open_timeout: opts[:open_timeout], http_read_timeout: opts[:read_timeout] }
97
+ args[:region] = @opts[:region] if @opts[:region]
98
+ args.merge!( @credentials.key?(:access_key_id) ? { access_key_id: @credentials[:access_key_id], secret_access_key: @credentials[:secret_access_key] } : {} )
99
+
100
+ @client = Aws::CloudWatchLogs::Client.new(args)
101
+ begin
102
+ @client.create_log_stream(
103
+ log_group_name: @log_group_name,
104
+ log_stream_name: @log_stream_name
105
+ )
106
+ rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException
107
+ @client.create_log_group(
108
+ log_group_name: @log_group_name
109
+ )
110
+ retry
111
+ rescue Aws::CloudWatchLogs::Errors::ResourceAlreadyExistsException,
112
+ Aws::CloudWatchLogs::Errors::AccessDeniedException
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -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,95 @@
1
+ require 'multi_json'
2
+ require 'socket'
3
+ require 'thread'
4
+ require 'securerandom'
5
+
6
+ module CloudWatchLogger
7
+ module Client
8
+ def self.new(credentials, log_group_name, log_stream_name = nil, opts = {})
9
+ unless log_group_name
10
+ raise LogGroupNameRequired, 'log_group_name is required'
11
+ end
12
+
13
+ CloudWatchLogger::Client::AWS_SDK.new(credentials, log_group_name, log_stream_name, opts)
14
+ end
15
+
16
+ module InstanceMethods
17
+ def masherize_key(prefix, key)
18
+ [prefix, key.to_s].compact.join('.')
19
+ end
20
+
21
+ def masher(hash, prefix = nil)
22
+ hash.map do |v|
23
+ if v[1].is_a?(Hash)
24
+ masher(v[1], masherize_key(prefix, v[0]))
25
+ else
26
+ "#{masherize_key(prefix, v[0])}=" << case v[1]
27
+ when Symbol
28
+ v[1].to_s
29
+ else
30
+ v[1].inspect
31
+ end
32
+ end
33
+ end.join(', ')
34
+ end
35
+
36
+ def formatter(format=nil)
37
+ proc do |severity, datetime, progname, msg|
38
+ processid = Process.pid
39
+ if format == :json && msg.is_a?(Hash)
40
+ message = MultiJson.dump(msg.merge(severity: severity,
41
+ datetime: datetime,
42
+ progname: progname,
43
+ pid: processid))
44
+ else
45
+ message = "#{datetime} "
46
+ message << massage_message(msg, severity, processid)
47
+ end
48
+
49
+ {
50
+ message: message,
51
+ epoch_time: epoch_from(datetime)
52
+ }
53
+ end
54
+ end
55
+
56
+ def massage_message(incoming_message, severity, processid)
57
+ outgoing_message = ''
58
+
59
+ outgoing_message << "pid=#{processid}, thread=#{Thread.current.object_id}, severity=#{severity}, "
60
+
61
+ outgoing_message << case incoming_message
62
+ when Hash
63
+ masher(incoming_message)
64
+ when String
65
+ incoming_message
66
+ else
67
+ incoming_message.inspect
68
+ end
69
+ outgoing_message
70
+ end
71
+
72
+ def setup_credentials(credentials)
73
+ @credentials = credentials
74
+ end
75
+
76
+ def setup_log_group_name(name)
77
+ @log_group_name = name
78
+ end
79
+
80
+ def setup_log_stream_name(name)
81
+ @log_stream_name = name || default_log_stream_name
82
+ end
83
+
84
+ def default_log_stream_name
85
+ @log_stream_name ||= "#{Socket.gethostname}-#{SecureRandom.uuid}"
86
+ end
87
+
88
+ def epoch_from(datetime)
89
+ (datetime.utc.to_f.round(3) * 1000).to_i
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ require File.join(File.dirname(__FILE__), 'client', 'aws_sdk')
@@ -0,0 +1,3 @@
1
+ module CloudWatchLogger
2
+ VERSION = '0.4.0'.freeze
3
+ end
@@ -0,0 +1,21 @@
1
+ require File.join(File.dirname(__FILE__), 'cloudwatchlogger', 'client')
2
+
3
+ require 'logger'
4
+
5
+ module CloudWatchLogger
6
+ class LogGroupNameRequired < ArgumentError; end
7
+ class LogEventRejected < ArgumentError; end
8
+
9
+ def self.new(credentials, log_group_name, log_stream_name = nil, opts = {})
10
+ client = CloudWatchLogger::Client.new(credentials, log_group_name, log_stream_name, opts)
11
+ logger = Logger.new(client)
12
+
13
+ if client.respond_to?(:formatter)
14
+ logger.formatter = client.formatter(opts[:format])
15
+ elsif client.respond_to?(:datetime_format)
16
+ logger.datetime_format = client.datetime_format
17
+ end
18
+
19
+ logger
20
+ end
21
+ end
@@ -0,0 +1 @@
1
+ require 'cloudwatchlogger'
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cm-cloudwatchlogger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Zane Shannon
8
+ - Manikandan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2022-11-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: securerandom
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.1'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: multi_json
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1'
42
+ - !ruby/object:Gem::Dependency
43
+ name: aws-sdk-cloudwatchlogs
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1'
56
+ description: Logger => CloudWatchLogs
57
+ email:
58
+ - z@zcs.me
59
+ - manikandan0603@gmail.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - Gemfile
65
+ - LICENSE
66
+ - README.md
67
+ - cloudwatchlogger.gemspec
68
+ - lib/cloudwatchlogger.rb
69
+ - lib/cloudwatchlogger/client.rb
70
+ - lib/cloudwatchlogger/client/aws_sdk.rb
71
+ - lib/cloudwatchlogger/client/aws_sdk/threaded.rb
72
+ - lib/cloudwatchlogger/version.rb
73
+ - lib/cm-cloudwatchlogger.rb
74
+ homepage: http://github.com/commutatus/cloudwatchlogger
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 1.8.6
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 1.3.6
92
+ requirements: []
93
+ rubygems_version: 3.3.4
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Amazon CloudWatch Logs compatiable logger for ruby.
97
+ test_files: []