aws-logs 0.5.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f269c1e0f5e4f2ce2c783602017351445cc9f4849e132005c2de67735531d782
4
- data.tar.gz: e43326f8b20d3dad93ab0f61d6bb2b6e14409619093827f6fa3a6f2eced8d369
3
+ metadata.gz: a20d3f0309f68ff523f15344158eef8a4de19249864b9aa84fe1b8e92e4de5d3
4
+ data.tar.gz: c1f87e3d3d0cf69b56c416c4766c2c09e8ed7b3b5d1372d5b9d28057770e930b
5
5
  SHA512:
6
- metadata.gz: 1d3126451461293fa32cd56eb3fe567516b30188387c3aabee1112c03e6d5ceae92dc31e7175c4b20d954c90c5a9464e72f64bc86252f2150692759e59aef85b
7
- data.tar.gz: 6499a4bdcd957f85b56e83ff2b159cd4086eab79e04e8de89f61845699a086ff86b2ffd789e7b2c98e896a49af40d70116cb9e1680e663bca0cdd8cf894cd523
6
+ metadata.gz: 488378c0430bb39dc08e6250da811393abb2010f53ecbc65e20e72c32274f66d5b338db46350e90d2d355c500b3ebbe98fcd64c6ecd6527b0b999158cde48ce1
7
+ data.tar.gz: 2f66c7a229ee4e697d07badd4deb0e4a2fafca700a79efe8aeea5224885106a39ff21377571a381f4be3a7f608c10b69ae48bf3bbdafc5d459f0f350b3dce621
data/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5
5
 
6
+ ## [1.0.0] - 2024-04-16
7
+ - [#7](https://github.com/tongueroo/aws-logs/pull/7) show if filter, plain default, thread-safe improvements
8
+ - change follow to false by default
9
+ - custom formatter for puts behavior
10
+ - plain format support and fix sliding window
11
+ - show_if filter to customize output
12
+ - thread-safe logger file option
13
+
14
+ ## [0.5.1] - 2023-11-22
15
+ - [#6](https://github.com/tongueroo/aws-logs/pull/6) fix eager load
16
+
6
17
  ## [0.5.0] - 2023-11-22
7
18
  - [#5](https://github.com/tongueroo/aws-logs/pull/5) add options: refresh_rate and wait_exists
8
19
  - alias File.exists?
@@ -13,8 +13,10 @@ module AwsLogs
13
13
  def setup
14
14
  loader = Zeitwerk::Loader.new
15
15
  loader.inflector = Inflector.new
16
- loader.push_dir(File.dirname(__dir__)) # lib
17
- loader.ignore("#{File.dirname(__dir__)}/aws-logs.rb")
16
+ lib = File.dirname(__dir__) # lib
17
+ loader.push_dir(lib)
18
+ loader.do_not_eager_load("#{lib}/aws_logs/core_ext")
19
+ loader.ignore("#{lib}/aws-logs.rb")
18
20
  loader.setup
19
21
  end
20
22
  end
data/lib/aws_logs/cli.rb CHANGED
@@ -3,8 +3,8 @@ module AwsLogs
3
3
  desc "tail LOG_GROUP", "Tail the CloudWatch log group."
4
4
  long_desc Help.text(:tail)
5
5
  option :since, desc: "From what time to begin displaying logs. By default, logs will be displayed starting from 10m in the past. The value provided can be an ISO 8601 timestamp or a relative time. Examples: 10m 2d 2w"
6
- option :follow, default: true, type: :boolean, desc: " Whether to continuously poll for new logs. To exit from this mode, use Control-C."
7
- option :format, default: "detailed", desc: "The format to display the logs. IE: detailed or short. With detailed, the log stream name is also shown."
6
+ option :follow, aliases: :f, default: false, type: :boolean, desc: "Whether to continuously poll for new logs. To exit from this mode, use Control-C."
7
+ option :format, default: "detailed", desc: "The format to display the logs. IE: detailed, short, plain. With detailed, the log stream name is also shown. Plain is the simplest andd does not show the timestamp or log stream name."
8
8
  option :log_stream_names, type: :array, desc: "Filters the results to only logs from the log streams. Can only use log_stream_names or log_stream_name_prefix but not both."
9
9
  option :log_stream_name_prefix, desc: "Filters the results to include only events from log streams that have names starting with this prefix. Can only use log_stream_names or log_stream_name_prefix but not both."
10
10
  option :filter_pattern, desc: "The filter pattern to use. If not provided, all the events are matched"
data/lib/aws_logs/tail.rb CHANGED
@@ -2,13 +2,15 @@ require "json"
2
2
 
3
3
  module AwsLogs
4
4
  class Tail < Base
5
- def initialize(options={})
5
+ attr_reader :logger
6
+ def initialize(options = {})
6
7
  super
7
8
  # Setting to ensure matches default CLI option
8
9
  @follow = @options[:follow].nil? ? true : @options[:follow]
9
- @refresh_rate = @options[:refresh_rate] || 5
10
+ @refresh_rate = @options[:refresh_rate] || 2
10
11
  @wait_exists = @options[:wait_exists]
11
12
  @wait_exists_retries = @options[:wait_exists_retries]
13
+ @logger = @options[:logger] || default_logger # separate logger instance for thread-safety
12
14
 
13
15
  @loop_count = 0
14
16
  @output = [] # for specs
@@ -16,12 +18,25 @@ module AwsLogs
16
18
  set_trap
17
19
  end
18
20
 
19
- def data(since="1d", quiet_not_found=false)
21
+ def default_logger
22
+ logger = ActiveSupport::Logger.new($stdout)
23
+ # The ActiveSupport::Logger::SimpleFormatter always adds extra lines to the output,
24
+ # unlike puts, which only adds a newline if it's needed.
25
+ # We want the simpler puts behavior.
26
+ logger.formatter = proc { |severity, timestamp, progname, msg|
27
+ msg = "#{msg}\n" unless msg.end_with?("\n")
28
+ "#{msg}"
29
+ }
30
+ logger.level = ENV["AWS_LOGS_LOG_LEVEL"] || :info
31
+ logger
32
+ end
33
+
34
+ def data(since = "24h", quiet_not_found = false)
20
35
  since, now = Since.new(since).to_i, current_now
21
36
  resp = filter_log_events(since, now)
22
37
  resp.events
23
38
  rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException => e
24
- puts "WARN: #{e.class}: #{e.message}" unless quiet_not_found
39
+ logger.info "WARN: #{e.class}: #{e.message}" unless quiet_not_found
25
40
  []
26
41
  end
27
42
 
@@ -39,21 +54,21 @@ module AwsLogs
39
54
  # It's a sliding window of time we're using.
40
55
  #
41
56
  def run
42
- if ENV['AWS_LOGS_NOOP']
43
- puts "Noop test"
44
- return
45
- end
46
-
47
57
  # We overlap the sliding window because CloudWatch logs can receive or send the logs out of order.
48
58
  # For example, a bunch of logs can all come in at the same second, but they haven't registered to CloudWatch logs
49
59
  # yet. If we don't overlap the sliding window then we'll miss the logs that were delayed in registering.
50
- overlap = 60*1000 # overlap the sliding window by a minute
60
+ overlap = 60 * 1000 # overlap the sliding window by a minute
51
61
  since, now = initial_since, current_now
52
62
  @wait_retries ||= 0
53
63
  until end_loop?
54
64
  refresh_events(since, now)
55
65
  display
56
- since, now = now-overlap, current_now
66
+
67
+ # @last_shown_event.timestamp changes and creates a "sliding window"
68
+ # The overlap is a just in case buffer
69
+ since = @last_shown_event ? @last_shown_event.timestamp - overlap : initial_since
70
+ now = current_now
71
+
57
72
  loop_count!
58
73
  sleep @refresh_rate if @follow && !ENV["AWS_LOGS_TEST"]
59
74
  end
@@ -62,17 +77,23 @@ module AwsLogs
62
77
  display
63
78
  rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException => e
64
79
  if @wait_exists
65
- seconds = 5
66
- puts "Waiting for log group #{@log_group_name} to exist. Waiting #{seconds} seconds."
80
+ seconds = Integer(@options[:wait_exists_seconds] || 5)
81
+ unless @@waiting_already_shown
82
+ logger.info "Waiting for log group to exist: #{@log_group_name}"
83
+ @@waiting_already_shown = true
84
+ end
67
85
  sleep seconds
68
86
  @wait_retries += 1
87
+ logger.info "Waiting #{seconds} seconds. #{@wait_retries} of #{@wait_exists_retries} retries"
69
88
  if !@wait_exists_retries || @wait_retries < @wait_exists_retries
70
89
  retry
71
90
  end
91
+ logger.info "Giving up waiting for log group to exist"
72
92
  end
73
- puts "ERROR: #{e.class}: #{e.message}".color(:red)
74
- puts "Log group #{@log_group_name} not found."
93
+ logger.info "ERROR: #{e.class}: #{e.message}".color(:red)
94
+ logger.info "Log group #{@log_group_name} not found."
75
95
  end
96
+ @@waiting_already_shown = false
76
97
 
77
98
  # TODO: lazy Enum or else its seems stuck for long --since
78
99
  def refresh_events(start_time, end_time)
@@ -89,11 +110,11 @@ module AwsLogs
89
110
  @events
90
111
  end
91
112
 
92
- def filter_log_events(start_time, end_time, next_token=nil)
113
+ def filter_log_events(start_time, end_time, next_token = nil)
93
114
  options = {
94
115
  log_group_name: @log_group_name, # required
95
116
  start_time: start_time,
96
- end_time: end_time,
117
+ end_time: end_time
97
118
  # limit: 1000,
98
119
  # interleaved: true,
99
120
  }
@@ -107,35 +128,56 @@ module AwsLogs
107
128
  end
108
129
 
109
130
  # There can be duplicated events as events can be written to the exact same timestamp.
110
- # So also track the last_shown_event_id and prevent duplicate log lines from re-appearing.
131
+ # So also track the last_shown_event and prevent duplicate log lines from re-appearing.
111
132
  def display
112
133
  new_events = @events
113
- shown_index = new_events.find_index { |e| e.event_id == @last_shown_event_id }
134
+ shown_index = new_events.find_index { |e| e.event_id == @last_shown_event&.event_id }
114
135
  if shown_index
115
- new_events = @events[shown_index+1..-1] || []
136
+ new_events = @events[shown_index + 1..-1] || []
116
137
  end
117
138
 
118
139
  new_events.each do |e|
119
- time = Time.at(e.timestamp/1000).utc
120
- line = [time.to_s.color(:green), e.message]
140
+ time = Time.at(e.timestamp / 1000).utc.to_s.color(:green) unless @options[:format] == "plain"
141
+ line = [time, e.message].compact
121
142
  format = @options[:format] || "detailed"
122
143
  line.insert(1, e.log_stream_name.color(:purple)) if format == "detailed"
123
- say line.join(' ') unless @options[:silence]
144
+
145
+ filtered = show_if? ? show_if(e) : true
146
+ say line.join(" ") if !@options[:silence] && filtered
124
147
  end
125
- @last_shown_event_id = @events.last&.event_id
148
+ @last_shown_event = @events.last
126
149
  check_follow_until!
127
150
  end
128
151
 
152
+ def show_if?
153
+ !@options[:show_if].nil?
154
+ end
155
+
156
+ def show_if(e)
157
+ filter = @options[:show_if]
158
+ case filter
159
+ when ->(f) { f.respond_to?(:call) }
160
+ filter.call(e)
161
+ else
162
+ filter # true or false
163
+ end
164
+ end
165
+
166
+ # [Container] 2024/03/27 02:35:32.086024 Phase complete: BUILD State: SUCCEEDED
167
+ def codebuild_complete?(message)
168
+ message.starts_with?("[Container]") && message.include?("Phase complete: BUILD")
169
+ end
170
+
129
171
  def check_follow_until!
130
172
  follow_until = @options[:follow_until]
131
173
  return unless follow_until
132
174
 
133
175
  messages = @events.map(&:message)
134
- @@end_loop_signal = messages.detect { |m| m.include?(follow_until) }
176
+ @end_loop_signal = messages.detect { |m| m.include?(follow_until) }
135
177
  end
136
178
 
137
179
  def say(text)
138
- ENV["AWS_LOGS_TEST"] ? @output << text : puts(text)
180
+ ENV["AWS_LOGS_TEST"] ? @output << text : logger.info(text)
139
181
  end
140
182
 
141
183
  def output
@@ -144,6 +186,7 @@ module AwsLogs
144
186
 
145
187
  def set_trap
146
188
  Signal.trap("INT") {
189
+ # puts must be used here instead of logger.info or else get Thread-safe error
147
190
  puts "\nCtrl-C detected. Exiting..."
148
191
  exit # immediate exit
149
192
  }
@@ -152,12 +195,19 @@ module AwsLogs
152
195
  # The stop_follow! results in a little waiting because it signals to break the polling loop.
153
196
  # Since it's in the middle of the loop process, the loop will finish the sleep 5 first.
154
197
  # So it can pause from 0-5 seconds.
155
- @@end_loop_signal = false
198
+ def stop_follow!
199
+ @end_loop_signal = true
200
+ end
201
+
202
+ # For backwards compatibility. This is not thread-safe.
203
+ @@global_end_loop_signal = false
156
204
  def self.stop_follow!
157
- @@end_loop_signal = true
205
+ logger.info "WARN: AwsLogs::Tail.stop_follow! is deprecated. Use AwsLogs::Tail#stop_follow! instead which is thread-safe."
206
+ @@global_end_loop_signal = true
158
207
  end
159
208
 
160
- private
209
+ private
210
+
161
211
  def initial_since
162
212
  since = @options[:since]
163
213
  seconds = since ? Since.new(since).to_i : Since::DEFAULT
@@ -165,11 +215,12 @@ module AwsLogs
165
215
  end
166
216
 
167
217
  def current_now
168
- (Time.now.to_i) * 1000 # now in milliseconds
218
+ Time.now.to_i * 1000 # now in milliseconds
169
219
  end
170
220
 
171
221
  def end_loop?
172
- return true if @@end_loop_signal
222
+ return true if @@global_end_loop_signal
223
+ return true if @end_loop_signal
173
224
  max_loop_count && @loop_count >= max_loop_count
174
225
  end
175
226
 
@@ -1,3 +1,3 @@
1
1
  module AwsLogs
2
- VERSION = "0.5.0"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/aws_logs.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  $stdout.sync = true unless ENV["AWS_LOGS_STDOUT_SYNC"] == "0"
2
2
 
3
- $:.unshift(File.expand_path("../", __FILE__))
4
- require "aws_logs/version"
5
- require "rainbow/ext/string"
3
+ $:.unshift(File.expand_path(".", __dir__))
6
4
 
7
5
  require "aws_logs/core_ext/file"
6
+ require "aws_logs/version"
7
+ require "rainbow/ext/string"
8
+ require "active_support"
9
+ require "active_support/core_ext"
8
10
 
9
11
  require "aws_logs/autoloader"
10
12
  AwsLogs::Autoloader.setup
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-logs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tung Nguyen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-22 00:00:00.000000000 Z
11
+ date: 2024-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -228,7 +228,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
228
228
  - !ruby/object:Gem::Version
229
229
  version: '0'
230
230
  requirements: []
231
- rubygems_version: 3.4.20
231
+ rubygems_version: 3.4.19
232
232
  signing_key:
233
233
  specification_version: 4
234
234
  summary: Tail AWS CloudWatch Logs