cache_sweeper 0.1.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 +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +586 -0
- data/lib/cache_sweeper/async_worker.rb +121 -0
- data/lib/cache_sweeper/base.rb +34 -0
- data/lib/cache_sweeper/dsl.rb +32 -0
- data/lib/cache_sweeper/flush_middleware.rb +152 -0
- data/lib/cache_sweeper/loader.rb +368 -0
- data/lib/cache_sweeper/logger.rb +104 -0
- data/lib/cache_sweeper/railtie.rb +13 -0
- data/lib/cache_sweeper/version.rb +3 -0
- data/lib/cache_sweeper.rb +108 -0
- data/lib/tasks/cache_sweeper.rake +4 -0
- metadata +104 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
# lib/cache_sweeper/logger.rb
|
2
|
+
module CacheSweeper
|
3
|
+
class Logger
|
4
|
+
class << self
|
5
|
+
def log(level, message, context = {})
|
6
|
+
return unless should_log?(level)
|
7
|
+
|
8
|
+
formatted_message = format_message(message, level, context)
|
9
|
+
CacheSweeper.logger.send(level, formatted_message)
|
10
|
+
end
|
11
|
+
|
12
|
+
def debug(message, context = {})
|
13
|
+
log(:debug, message, context)
|
14
|
+
end
|
15
|
+
|
16
|
+
def info(message, context = {})
|
17
|
+
log(:info, message, context)
|
18
|
+
end
|
19
|
+
|
20
|
+
def warn(message, context = {})
|
21
|
+
log(:warn, message, context)
|
22
|
+
end
|
23
|
+
|
24
|
+
def error(message, context = {})
|
25
|
+
log(:error, message, context)
|
26
|
+
end
|
27
|
+
|
28
|
+
def log_initialization(message, context = {})
|
29
|
+
info("Initialization: #{message}", context)
|
30
|
+
end
|
31
|
+
|
32
|
+
def log_rule_execution(rule, record, result, context = {})
|
33
|
+
sweeper_name = rule[:sweeper_class]&.name || 'Unknown'
|
34
|
+
model_name = record.class.name
|
35
|
+
record_id = record.respond_to?(:id) ? record.id : 'unknown'
|
36
|
+
|
37
|
+
message = "Rule execution: #{sweeper_name} -> #{model_name}##{record_id}"
|
38
|
+
context.merge!({
|
39
|
+
sweeper: sweeper_name,
|
40
|
+
model: model_name,
|
41
|
+
record_id: record_id,
|
42
|
+
association: rule[:association],
|
43
|
+
attributes: rule[:attributes],
|
44
|
+
condition_result: result,
|
45
|
+
batching_mode: rule[:batching_mode],
|
46
|
+
async: rule[:async]
|
47
|
+
})
|
48
|
+
|
49
|
+
debug(message, context)
|
50
|
+
end
|
51
|
+
|
52
|
+
def log_performance(operation, duration, context = {})
|
53
|
+
message = "Performance: #{operation} took #{duration.round(3)}ms"
|
54
|
+
context.merge!({
|
55
|
+
operation: operation,
|
56
|
+
duration_ms: duration.round(3)
|
57
|
+
})
|
58
|
+
|
59
|
+
debug(message, context)
|
60
|
+
end
|
61
|
+
|
62
|
+
def log_error(error, context = {})
|
63
|
+
message = "Error: #{error.class.name}: #{error.message}"
|
64
|
+
context.merge!({
|
65
|
+
error_class: error.class.name,
|
66
|
+
error_message: error.message,
|
67
|
+
backtrace: error.backtrace&.first(5)
|
68
|
+
})
|
69
|
+
|
70
|
+
error(message, context)
|
71
|
+
end
|
72
|
+
|
73
|
+
def log_cache_operations(message, level = :info, context = {})
|
74
|
+
log(level, message, context)
|
75
|
+
end
|
76
|
+
|
77
|
+
def log_async_jobs(message, level = :info, context = {})
|
78
|
+
log(level, message, context)
|
79
|
+
end
|
80
|
+
|
81
|
+
def log_middleware(message, level = :info, context = {})
|
82
|
+
log(level, message, context)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def should_log?(level)
|
88
|
+
return false unless CacheSweeper.logger
|
89
|
+
|
90
|
+
level_priority = { debug: 0, info: 1, warn: 2, error: 3 }
|
91
|
+
current_level_priority = level_priority[CacheSweeper.log_level] || 1
|
92
|
+
requested_level_priority = level_priority[level] || 1
|
93
|
+
|
94
|
+
requested_level_priority >= current_level_priority
|
95
|
+
end
|
96
|
+
|
97
|
+
def format_message(message, level, context)
|
98
|
+
timestamp = Time.current.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
99
|
+
context_str = context.any? ? " #{context.inspect}" : ""
|
100
|
+
"[CacheSweeper] [#{timestamp}] [#{level.to_s.upcase}] #{message}#{context_str}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module CacheSweeper
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer "cache_sweeper.configure_defaults" do
|
4
|
+
CacheSweeper.configure_defaults
|
5
|
+
end
|
6
|
+
|
7
|
+
config.after_initialize do
|
8
|
+
CacheSweeper.validate_configuration!
|
9
|
+
CacheSweeper::Loader.load_sweepers!
|
10
|
+
CacheSweeper::Loader.hook_sweepers!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require "cache_sweeper/version"
|
2
|
+
require "cache_sweeper/dsl"
|
3
|
+
require "cache_sweeper/base"
|
4
|
+
require "cache_sweeper/logger"
|
5
|
+
require "cache_sweeper/loader"
|
6
|
+
require "cache_sweeper/async_worker"
|
7
|
+
require "cache_sweeper/flush_middleware"
|
8
|
+
|
9
|
+
module CacheSweeper
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
DEFAULTS = {
|
13
|
+
log_level: :info,
|
14
|
+
trigger: :instant,
|
15
|
+
mode: :inline,
|
16
|
+
queue: :default,
|
17
|
+
sidekiq_options: {},
|
18
|
+
delete_multi_batch_size: 100
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
class << self
|
22
|
+
attr_accessor :logger, :log_level, :trigger, :mode, :queue, :sidekiq_options, :delete_multi_batch_size
|
23
|
+
|
24
|
+
def configure
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
|
28
|
+
def configure_defaults
|
29
|
+
DEFAULTS.each { |k, v| instance_variable_set("@#{k}", v) }
|
30
|
+
|
31
|
+
if defined?(Rails)
|
32
|
+
@log_level =
|
33
|
+
if Rails.env.development?
|
34
|
+
:debug
|
35
|
+
elsif Rails.env.production?
|
36
|
+
:warn
|
37
|
+
else
|
38
|
+
:info
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def log_level=(level)
|
44
|
+
valid_levels = %i[debug info warn error]
|
45
|
+
unless valid_levels.include?(level)
|
46
|
+
raise ArgumentError,
|
47
|
+
"Invalid log level: #{level}. Must be one of: #{valid_levels.join(', ')}"
|
48
|
+
end
|
49
|
+
@log_level = level
|
50
|
+
end
|
51
|
+
|
52
|
+
def attached_sweepers
|
53
|
+
@attached_sweepers ||= []
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_configuration!
|
57
|
+
validate_async_mode(@mode, "global configuration")
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_async_mode(mode, context)
|
61
|
+
if mode == :async && !defined?(Sidekiq)
|
62
|
+
warn "CacheSweeper Warning: #{context} has mode set to :async but Sidekiq is not available. " \
|
63
|
+
"Async jobs will be executed synchronously. " \
|
64
|
+
"Add 'gem \"sidekiq\"' to your Gemfile to enable async processing."
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def delete_cache_keys(keys, context = {})
|
69
|
+
keys_array = Array(keys)
|
70
|
+
return 0 if keys_array.empty?
|
71
|
+
|
72
|
+
batch_size = @delete_multi_batch_size || 100
|
73
|
+
deleted_count = 0
|
74
|
+
failed_count = 0
|
75
|
+
|
76
|
+
keys_array.each_slice(batch_size) do |batch|
|
77
|
+
begin
|
78
|
+
Rails.cache.delete_multi(batch)
|
79
|
+
deleted_count += batch.length
|
80
|
+
CacheSweeper::Logger.log_cache_operations("Deleted batch of #{batch.length} keys", :debug, context.merge({
|
81
|
+
batch_size: batch.length,
|
82
|
+
keys: batch
|
83
|
+
}))
|
84
|
+
rescue => e
|
85
|
+
failed_count += batch.length
|
86
|
+
CacheSweeper::Logger.log_error(e, context.merge({
|
87
|
+
batch_size: batch.length,
|
88
|
+
keys: batch,
|
89
|
+
error_type: 'delete_multi_error'
|
90
|
+
}))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
CacheSweeper::Logger.log_cache_operations("Batch deletion completed", :info, context.merge({
|
95
|
+
total_keys: keys_array.length,
|
96
|
+
deleted_count: deleted_count,
|
97
|
+
failed_count: failed_count,
|
98
|
+
batch_size: batch_size
|
99
|
+
}))
|
100
|
+
|
101
|
+
deleted_count
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if defined?(Rails)
|
107
|
+
require "cache_sweeper/railtie"
|
108
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cache_sweeper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rafay Qayyum
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-09-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: request_store
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '6.0'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '8.0'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '6.0'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '8.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: sidekiq
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '6.0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '6.0'
|
61
|
+
description:
|
62
|
+
email: rafayqayyum786@gmail.com
|
63
|
+
executables: []
|
64
|
+
extensions: []
|
65
|
+
extra_rdoc_files: []
|
66
|
+
files:
|
67
|
+
- CODE_OF_CONDUCT.md
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- lib/cache_sweeper.rb
|
71
|
+
- lib/cache_sweeper/async_worker.rb
|
72
|
+
- lib/cache_sweeper/base.rb
|
73
|
+
- lib/cache_sweeper/dsl.rb
|
74
|
+
- lib/cache_sweeper/flush_middleware.rb
|
75
|
+
- lib/cache_sweeper/loader.rb
|
76
|
+
- lib/cache_sweeper/logger.rb
|
77
|
+
- lib/cache_sweeper/railtie.rb
|
78
|
+
- lib/cache_sweeper/version.rb
|
79
|
+
- lib/tasks/cache_sweeper.rake
|
80
|
+
homepage: https://github.com/rafayqayyum/cache_sweeper
|
81
|
+
licenses:
|
82
|
+
- MIT
|
83
|
+
metadata: {}
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 2.6.0
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubygems_version: 3.2.33
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: Flexible, rule-based cache invalidation for Rails with batching, async jobs,
|
103
|
+
and association support.
|
104
|
+
test_files: []
|