aws-logs 0.5.1 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb2a53707e319b0a83c603739a7d8b48a94da7052b5f044e5676fcd4959beaba
4
- data.tar.gz: c9e6a5e5917fc548faca36de1e8a698b43b3e66516c7b95267e35422f7a007ee
3
+ metadata.gz: a20d3f0309f68ff523f15344158eef8a4de19249864b9aa84fe1b8e92e4de5d3
4
+ data.tar.gz: c1f87e3d3d0cf69b56c416c4766c2c09e8ed7b3b5d1372d5b9d28057770e930b
5
5
  SHA512:
6
- metadata.gz: 8e2a815bcd5619a47934491858819da1b14a165fcc87dc33c42d490a59f083fc12a213dc43525d55e553da0f9af8ec90bcf80f401c06661e91c6a7749f2c9440
7
- data.tar.gz: 9418a975605185afcafbca5cbd64ddbb8234d907f154da4a0d4e66532860140f6b93c8851a3905ea0643e1395d6f1603f3b465e3dff97539c80fed5b85cf9962
6
+ metadata.gz: 488378c0430bb39dc08e6250da811393abb2010f53ecbc65e20e72c32274f66d5b338db46350e90d2d355c500b3ebbe98fcd64c6ecd6527b0b999158cde48ce1
7
+ data.tar.gz: 2f66c7a229ee4e697d07badd4deb0e4a2fafca700a79efe8aeea5224885106a39ff21377571a381f4be3a7f608c10b69ae48bf3bbdafc5d459f0f350b3dce621
data/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
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
+
6
14
  ## [0.5.1] - 2023-11-22
7
15
  - [#6](https://github.com/tongueroo/aws-logs/pull/6) fix eager load
8
16
 
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.1"
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.1
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