From d35375683356c45e07209b7364fb746151be382b Mon Sep 17 00:00:00 2001 From: "oleh.motrunych" Date: Wed, 10 Sep 2025 13:28:17 +0200 Subject: [PATCH 1/4] fixed isseu with hostname and redis connection issue --- lambda_function.rb | 27 ++++++----- lib/slowlog_check/redis.rb | 98 ++++++++++++++++++++++++-------------- 2 files changed, 76 insertions(+), 49 deletions(-) diff --git a/lambda_function.rb b/lambda_function.rb index d12dada..79875a1 100755 --- a/lambda_function.rb +++ b/lambda_function.rb @@ -1,8 +1,6 @@ #!/usr/bin/env ruby # frozen_string_literal: true -# Copyright 2020 Scribd, Inc. - require 'logger' require 'date' require 'dogapi' @@ -28,8 +26,8 @@ def lambda_handler(event: {}, context: {}) @event = event log_context - ssm_path = ENV.fetch('SSM_PATH', false) - if ssm_path + # Optionally hydrate env from SSM + if (ssm_path = ENV.fetch('SSM_PATH', false)) require 'aws-sdk-ssm' resp = Aws::SSM::Client.new.get_parameters_by_path( path: "/#{ssm_path}/", @@ -43,15 +41,19 @@ def lambda_handler(event: {}, context: {}) end end - unless defined? @slowlog_check + unless defined?(@slowlog_check) + # Give Dogapi a stable hostname so it won't call the `hostname` binary + dd_hostname = ENV['HOSTNAME'] || "slowlog-check-#{ENV.fetch('ENV', 'env')}-#{ENV.fetch('NAMESPACE', 'ns')}" + + dd_client = Dogapi::Client.new( + ENV.fetch('DATADOG_API_KEY'), + ENV.fetch('DATADOG_APP_KEY'), + dd_hostname # 3rd arg = host + ) + @slowlog_check = SlowlogCheck.new( - ddog: Dogapi::Client.new( - ENV.fetch('DATADOG_API_KEY'), - ENV.fetch('DATADOG_APP_KEY') - ), - redis: { - host: ENV.fetch('REDIS_HOST') - }, + ddog: dd_client, + redis: { host: ENV.fetch('REDIS_HOST') }, namespace: ENV.fetch('NAMESPACE'), env: ENV.fetch('ENV'), metricname: ENV.fetch('METRICNAME', 'elasticache.slowlog') @@ -61,7 +63,6 @@ def lambda_handler(event: {}, context: {}) end @slowlog_check.ship_slowlogs - nil end diff --git a/lib/slowlog_check/redis.rb b/lib/slowlog_check/redis.rb index e8f6734..e2aa03f 100644 --- a/lib/slowlog_check/redis.rb +++ b/lib/slowlog_check/redis.rb @@ -5,26 +5,31 @@ class SlowlogCheck class Redis - MAXLENGTH = 1_048_576 # 255 levels of recursion for # + MAXLENGTH = 1_048_576 + CONNECT_TIMEOUT = (ENV['REDIS_CONNECT_TIMEOUT'] || 5).to_i + RW_TIMEOUT = (ENV['REDIS_RW_TIMEOUT'] || 5).to_i + RECONNECT_TRIES = (ENV['REDIS_RECONNECT_ATTEMPTS'] || 2).to_i def initialize(opts) @host = opts[:host] end def params - if cluster_mode_enabled? - { - cluster: [uri], - port: port, - ssl: tls_mode? - } - else - { - host: hostname, - port: port, - ssl: tls_mode? - } - end + base = + if cluster_mode_enabled? + { cluster: [uri], port: port, ssl: tls_mode? } + else + { host: hostname, port: port, ssl: tls_mode? } + end + + password = ENV['REDIS_PASSWORD'] + base[:password] = password unless password.nil? || password.empty? + + base[:connect_timeout] = CONNECT_TIMEOUT + base[:read_timeout] = RW_TIMEOUT + base[:write_timeout] = RW_TIMEOUT + base[:reconnect_attempts] = RECONNECT_TRIES + base end def redis_rb @@ -39,10 +44,19 @@ def replication_group end end + # Always returns an Array (possibly empty). Never raises to the caller. def slowlog_get(length = 128) - resp = redis_rb.slowlog('get', length) + resp = begin + redis_rb.slowlog('get', length) + rescue ::Redis::BaseError, StandardError => e + LOGGER&.warn("SLOWLOG GET failed (Redis): #{e.class}: #{e.message}") rescue nil + [] + end + + resp = [] unless resp.is_a?(Array) + resp = resp.select { |e| e.is_a?(Array) } - return resp if length > MAXLENGTH + return resp if length >= MAXLENGTH return resp if did_i_get_it_all?(resp) slowlog_get(length * 2) @@ -51,51 +65,63 @@ def slowlog_get(length = 128) private def cluster_mode_enabled? + return false if matches.nil? if tls_mode? matches[:first] == 'clustercfg' else - matches[:third] == '' + matches[:third].to_s == '' end end + # Nil/shape safe def did_i_get_it_all?(slowlog) - slowlog[-1][0].zero? + entries = Array(slowlog).select { |e| e.is_a?(Array) } + return true if entries.empty? + id = entries[-1][0] rescue nil + return true if id.nil? + id.to_i.zero? end def hostname - URI.parse(@host).hostname or - @host + URI.parse(@host).hostname || @host + rescue URI::InvalidURIError + @host end def matches - redis_uri_regex.match(@host) + @matches ||= redis_uri_regex.match(@host) end def port - regex_port = matches[:port].to_i - if regex_port.positive? - regex_port - else - 6379 - end + p = matches && matches[:port].to_i + p.positive? ? p : 6379 end def uri - 'redis' + - -> { tls_mode? ? 's' : '' }.call + - '://' + - hostname + - ':' + - port.to_s + scheme = tls_mode? ? 'rediss' : 'redis' + "#{scheme}://#{hostname}:#{port}" end def redis_uri_regex - %r{((?redi[s]+)\://){0,1}(?[0-9A-Za-z_-]+)\.(?[0-9A-Za-z_-]+)\.{0,1}(?[0-9A-Za-z_]*)\.(?[0-9A-Za-z_-]+)\.cache\.amazonaws\.com:{0,1}(?[0-9]*)} + %r{ + ((?redi[s]+)\://){0,1} + (?[0-9A-Za-z_-]+)\. + (?[0-9A-Za-z_-]+)\.{0,1} + (?[0-9A-Za-z_]*)\. + (?[0-9A-Za-z_-]+)\.cache\.amazonaws\.com + :{0,1}(?[0-9]*) + }x end + # TLS required when: + # - env REDIS_TLS=true, OR + # - scheme is rediss://, OR + # - endpoint starts with master./clustercfg. (ElastiCache with in-transit encryption required) def tls_mode? - matches[:scheme] == 'rediss' or - %w[master clustercfg].include?(matches[:first]) + return true if ENV['REDIS_TLS'].to_s.downcase == 'true' + m = matches + return false if m.nil? + m[:scheme] == 'rediss' || %w[master clustercfg].include?(m[:first]) end end end From a54d1ecaf74dbcc0675f9374f97d3351fc95f7bc Mon Sep 17 00:00:00 2001 From: "oleh.motrunych" Date: Wed, 10 Sep 2025 13:31:02 +0200 Subject: [PATCH 2/4] fixed isseu with hostname and redis connection issue --- lib/slowlog_check/redis.rb | 98 ++++++++++++++------------------------ 1 file changed, 36 insertions(+), 62 deletions(-) diff --git a/lib/slowlog_check/redis.rb b/lib/slowlog_check/redis.rb index e2aa03f..e8f6734 100644 --- a/lib/slowlog_check/redis.rb +++ b/lib/slowlog_check/redis.rb @@ -5,31 +5,26 @@ class SlowlogCheck class Redis - MAXLENGTH = 1_048_576 - CONNECT_TIMEOUT = (ENV['REDIS_CONNECT_TIMEOUT'] || 5).to_i - RW_TIMEOUT = (ENV['REDIS_RW_TIMEOUT'] || 5).to_i - RECONNECT_TRIES = (ENV['REDIS_RECONNECT_ATTEMPTS'] || 2).to_i + MAXLENGTH = 1_048_576 # 255 levels of recursion for # def initialize(opts) @host = opts[:host] end def params - base = - if cluster_mode_enabled? - { cluster: [uri], port: port, ssl: tls_mode? } - else - { host: hostname, port: port, ssl: tls_mode? } - end - - password = ENV['REDIS_PASSWORD'] - base[:password] = password unless password.nil? || password.empty? - - base[:connect_timeout] = CONNECT_TIMEOUT - base[:read_timeout] = RW_TIMEOUT - base[:write_timeout] = RW_TIMEOUT - base[:reconnect_attempts] = RECONNECT_TRIES - base + if cluster_mode_enabled? + { + cluster: [uri], + port: port, + ssl: tls_mode? + } + else + { + host: hostname, + port: port, + ssl: tls_mode? + } + end end def redis_rb @@ -44,19 +39,10 @@ def replication_group end end - # Always returns an Array (possibly empty). Never raises to the caller. def slowlog_get(length = 128) - resp = begin - redis_rb.slowlog('get', length) - rescue ::Redis::BaseError, StandardError => e - LOGGER&.warn("SLOWLOG GET failed (Redis): #{e.class}: #{e.message}") rescue nil - [] - end - - resp = [] unless resp.is_a?(Array) - resp = resp.select { |e| e.is_a?(Array) } + resp = redis_rb.slowlog('get', length) - return resp if length >= MAXLENGTH + return resp if length > MAXLENGTH return resp if did_i_get_it_all?(resp) slowlog_get(length * 2) @@ -65,63 +51,51 @@ def slowlog_get(length = 128) private def cluster_mode_enabled? - return false if matches.nil? if tls_mode? matches[:first] == 'clustercfg' else - matches[:third].to_s == '' + matches[:third] == '' end end - # Nil/shape safe def did_i_get_it_all?(slowlog) - entries = Array(slowlog).select { |e| e.is_a?(Array) } - return true if entries.empty? - id = entries[-1][0] rescue nil - return true if id.nil? - id.to_i.zero? + slowlog[-1][0].zero? end def hostname - URI.parse(@host).hostname || @host - rescue URI::InvalidURIError - @host + URI.parse(@host).hostname or + @host end def matches - @matches ||= redis_uri_regex.match(@host) + redis_uri_regex.match(@host) end def port - p = matches && matches[:port].to_i - p.positive? ? p : 6379 + regex_port = matches[:port].to_i + if regex_port.positive? + regex_port + else + 6379 + end end def uri - scheme = tls_mode? ? 'rediss' : 'redis' - "#{scheme}://#{hostname}:#{port}" + 'redis' + + -> { tls_mode? ? 's' : '' }.call + + '://' + + hostname + + ':' + + port.to_s end def redis_uri_regex - %r{ - ((?redi[s]+)\://){0,1} - (?[0-9A-Za-z_-]+)\. - (?[0-9A-Za-z_-]+)\.{0,1} - (?[0-9A-Za-z_]*)\. - (?[0-9A-Za-z_-]+)\.cache\.amazonaws\.com - :{0,1}(?[0-9]*) - }x + %r{((?redi[s]+)\://){0,1}(?[0-9A-Za-z_-]+)\.(?[0-9A-Za-z_-]+)\.{0,1}(?[0-9A-Za-z_]*)\.(?[0-9A-Za-z_-]+)\.cache\.amazonaws\.com:{0,1}(?[0-9]*)} end - # TLS required when: - # - env REDIS_TLS=true, OR - # - scheme is rediss://, OR - # - endpoint starts with master./clustercfg. (ElastiCache with in-transit encryption required) def tls_mode? - return true if ENV['REDIS_TLS'].to_s.downcase == 'true' - m = matches - return false if m.nil? - m[:scheme] == 'rediss' || %w[master clustercfg].include?(m[:first]) + matches[:scheme] == 'rediss' or + %w[master clustercfg].include?(matches[:first]) end end end From 3789980a73e361a344816466bdc662230aba915c Mon Sep 17 00:00:00 2001 From: "oleh.motrunych" Date: Wed, 10 Sep 2025 13:34:19 +0200 Subject: [PATCH 3/4] fixed isseu with hostname and redis connection issue --- lib/slowlog_check/redis.rb | 98 ++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 36 deletions(-) diff --git a/lib/slowlog_check/redis.rb b/lib/slowlog_check/redis.rb index e8f6734..e2aa03f 100644 --- a/lib/slowlog_check/redis.rb +++ b/lib/slowlog_check/redis.rb @@ -5,26 +5,31 @@ class SlowlogCheck class Redis - MAXLENGTH = 1_048_576 # 255 levels of recursion for # + MAXLENGTH = 1_048_576 + CONNECT_TIMEOUT = (ENV['REDIS_CONNECT_TIMEOUT'] || 5).to_i + RW_TIMEOUT = (ENV['REDIS_RW_TIMEOUT'] || 5).to_i + RECONNECT_TRIES = (ENV['REDIS_RECONNECT_ATTEMPTS'] || 2).to_i def initialize(opts) @host = opts[:host] end def params - if cluster_mode_enabled? - { - cluster: [uri], - port: port, - ssl: tls_mode? - } - else - { - host: hostname, - port: port, - ssl: tls_mode? - } - end + base = + if cluster_mode_enabled? + { cluster: [uri], port: port, ssl: tls_mode? } + else + { host: hostname, port: port, ssl: tls_mode? } + end + + password = ENV['REDIS_PASSWORD'] + base[:password] = password unless password.nil? || password.empty? + + base[:connect_timeout] = CONNECT_TIMEOUT + base[:read_timeout] = RW_TIMEOUT + base[:write_timeout] = RW_TIMEOUT + base[:reconnect_attempts] = RECONNECT_TRIES + base end def redis_rb @@ -39,10 +44,19 @@ def replication_group end end + # Always returns an Array (possibly empty). Never raises to the caller. def slowlog_get(length = 128) - resp = redis_rb.slowlog('get', length) + resp = begin + redis_rb.slowlog('get', length) + rescue ::Redis::BaseError, StandardError => e + LOGGER&.warn("SLOWLOG GET failed (Redis): #{e.class}: #{e.message}") rescue nil + [] + end + + resp = [] unless resp.is_a?(Array) + resp = resp.select { |e| e.is_a?(Array) } - return resp if length > MAXLENGTH + return resp if length >= MAXLENGTH return resp if did_i_get_it_all?(resp) slowlog_get(length * 2) @@ -51,51 +65,63 @@ def slowlog_get(length = 128) private def cluster_mode_enabled? + return false if matches.nil? if tls_mode? matches[:first] == 'clustercfg' else - matches[:third] == '' + matches[:third].to_s == '' end end + # Nil/shape safe def did_i_get_it_all?(slowlog) - slowlog[-1][0].zero? + entries = Array(slowlog).select { |e| e.is_a?(Array) } + return true if entries.empty? + id = entries[-1][0] rescue nil + return true if id.nil? + id.to_i.zero? end def hostname - URI.parse(@host).hostname or - @host + URI.parse(@host).hostname || @host + rescue URI::InvalidURIError + @host end def matches - redis_uri_regex.match(@host) + @matches ||= redis_uri_regex.match(@host) end def port - regex_port = matches[:port].to_i - if regex_port.positive? - regex_port - else - 6379 - end + p = matches && matches[:port].to_i + p.positive? ? p : 6379 end def uri - 'redis' + - -> { tls_mode? ? 's' : '' }.call + - '://' + - hostname + - ':' + - port.to_s + scheme = tls_mode? ? 'rediss' : 'redis' + "#{scheme}://#{hostname}:#{port}" end def redis_uri_regex - %r{((?redi[s]+)\://){0,1}(?[0-9A-Za-z_-]+)\.(?[0-9A-Za-z_-]+)\.{0,1}(?[0-9A-Za-z_]*)\.(?[0-9A-Za-z_-]+)\.cache\.amazonaws\.com:{0,1}(?[0-9]*)} + %r{ + ((?redi[s]+)\://){0,1} + (?[0-9A-Za-z_-]+)\. + (?[0-9A-Za-z_-]+)\.{0,1} + (?[0-9A-Za-z_]*)\. + (?[0-9A-Za-z_-]+)\.cache\.amazonaws\.com + :{0,1}(?[0-9]*) + }x end + # TLS required when: + # - env REDIS_TLS=true, OR + # - scheme is rediss://, OR + # - endpoint starts with master./clustercfg. (ElastiCache with in-transit encryption required) def tls_mode? - matches[:scheme] == 'rediss' or - %w[master clustercfg].include?(matches[:first]) + return true if ENV['REDIS_TLS'].to_s.downcase == 'true' + m = matches + return false if m.nil? + m[:scheme] == 'rediss' || %w[master clustercfg].include?(m[:first]) end end end From b0ff531fc39aec49f29ed988826b8b14abbf6bec Mon Sep 17 00:00:00 2001 From: "oleh.motrunych" Date: Wed, 10 Sep 2025 13:44:27 +0200 Subject: [PATCH 4/4] fixed isseu with hostname and redis connection issue --- lib/slowlog_check/redis.rb | 98 ++++++++++++++------------------------ 1 file changed, 36 insertions(+), 62 deletions(-) diff --git a/lib/slowlog_check/redis.rb b/lib/slowlog_check/redis.rb index e2aa03f..e8f6734 100644 --- a/lib/slowlog_check/redis.rb +++ b/lib/slowlog_check/redis.rb @@ -5,31 +5,26 @@ class SlowlogCheck class Redis - MAXLENGTH = 1_048_576 - CONNECT_TIMEOUT = (ENV['REDIS_CONNECT_TIMEOUT'] || 5).to_i - RW_TIMEOUT = (ENV['REDIS_RW_TIMEOUT'] || 5).to_i - RECONNECT_TRIES = (ENV['REDIS_RECONNECT_ATTEMPTS'] || 2).to_i + MAXLENGTH = 1_048_576 # 255 levels of recursion for # def initialize(opts) @host = opts[:host] end def params - base = - if cluster_mode_enabled? - { cluster: [uri], port: port, ssl: tls_mode? } - else - { host: hostname, port: port, ssl: tls_mode? } - end - - password = ENV['REDIS_PASSWORD'] - base[:password] = password unless password.nil? || password.empty? - - base[:connect_timeout] = CONNECT_TIMEOUT - base[:read_timeout] = RW_TIMEOUT - base[:write_timeout] = RW_TIMEOUT - base[:reconnect_attempts] = RECONNECT_TRIES - base + if cluster_mode_enabled? + { + cluster: [uri], + port: port, + ssl: tls_mode? + } + else + { + host: hostname, + port: port, + ssl: tls_mode? + } + end end def redis_rb @@ -44,19 +39,10 @@ def replication_group end end - # Always returns an Array (possibly empty). Never raises to the caller. def slowlog_get(length = 128) - resp = begin - redis_rb.slowlog('get', length) - rescue ::Redis::BaseError, StandardError => e - LOGGER&.warn("SLOWLOG GET failed (Redis): #{e.class}: #{e.message}") rescue nil - [] - end - - resp = [] unless resp.is_a?(Array) - resp = resp.select { |e| e.is_a?(Array) } + resp = redis_rb.slowlog('get', length) - return resp if length >= MAXLENGTH + return resp if length > MAXLENGTH return resp if did_i_get_it_all?(resp) slowlog_get(length * 2) @@ -65,63 +51,51 @@ def slowlog_get(length = 128) private def cluster_mode_enabled? - return false if matches.nil? if tls_mode? matches[:first] == 'clustercfg' else - matches[:third].to_s == '' + matches[:third] == '' end end - # Nil/shape safe def did_i_get_it_all?(slowlog) - entries = Array(slowlog).select { |e| e.is_a?(Array) } - return true if entries.empty? - id = entries[-1][0] rescue nil - return true if id.nil? - id.to_i.zero? + slowlog[-1][0].zero? end def hostname - URI.parse(@host).hostname || @host - rescue URI::InvalidURIError - @host + URI.parse(@host).hostname or + @host end def matches - @matches ||= redis_uri_regex.match(@host) + redis_uri_regex.match(@host) end def port - p = matches && matches[:port].to_i - p.positive? ? p : 6379 + regex_port = matches[:port].to_i + if regex_port.positive? + regex_port + else + 6379 + end end def uri - scheme = tls_mode? ? 'rediss' : 'redis' - "#{scheme}://#{hostname}:#{port}" + 'redis' + + -> { tls_mode? ? 's' : '' }.call + + '://' + + hostname + + ':' + + port.to_s end def redis_uri_regex - %r{ - ((?redi[s]+)\://){0,1} - (?[0-9A-Za-z_-]+)\. - (?[0-9A-Za-z_-]+)\.{0,1} - (?[0-9A-Za-z_]*)\. - (?[0-9A-Za-z_-]+)\.cache\.amazonaws\.com - :{0,1}(?[0-9]*) - }x + %r{((?redi[s]+)\://){0,1}(?[0-9A-Za-z_-]+)\.(?[0-9A-Za-z_-]+)\.{0,1}(?[0-9A-Za-z_]*)\.(?[0-9A-Za-z_-]+)\.cache\.amazonaws\.com:{0,1}(?[0-9]*)} end - # TLS required when: - # - env REDIS_TLS=true, OR - # - scheme is rediss://, OR - # - endpoint starts with master./clustercfg. (ElastiCache with in-transit encryption required) def tls_mode? - return true if ENV['REDIS_TLS'].to_s.downcase == 'true' - m = matches - return false if m.nil? - m[:scheme] == 'rediss' || %w[master clustercfg].include?(m[:first]) + matches[:scheme] == 'rediss' or + %w[master clustercfg].include?(matches[:first]) end end end