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 +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/aws_logs/cli.rb +2 -2
- data/lib/aws_logs/tail.rb +82 -31
- data/lib/aws_logs/version.rb +1 -1
- data/lib/aws_logs.rb +5 -3
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a20d3f0309f68ff523f15344158eef8a4de19249864b9aa84fe1b8e92e4de5d3
|
4
|
+
data.tar.gz: c1f87e3d3d0cf69b56c416c4766c2c09e8ed7b3b5d1372d5b9d28057770e930b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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:
|
7
|
-
option :format, default: "detailed", desc: "The format to display the logs. IE: detailed
|
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
|
-
|
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] ||
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
74
|
-
|
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
|
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 == @
|
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
|
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
|
-
|
144
|
+
|
145
|
+
filtered = show_if? ? show_if(e) : true
|
146
|
+
say line.join(" ") if !@options[:silence] && filtered
|
124
147
|
end
|
125
|
-
@
|
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
|
-
|
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 :
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
218
|
+
Time.now.to_i * 1000 # now in milliseconds
|
169
219
|
end
|
170
220
|
|
171
221
|
def end_loop?
|
172
|
-
return true if @@
|
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
|
|
data/lib/aws_logs/version.rb
CHANGED
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("
|
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.
|
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:
|
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.
|
231
|
+
rubygems_version: 3.4.19
|
232
232
|
signing_key:
|
233
233
|
specification_version: 4
|
234
234
|
summary: Tail AWS CloudWatch Logs
|