legion-data 1.6.17 → 1.6.19

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile +1 -0
  4. data/legion-data.gemspec +2 -2
  5. data/lib/legion/data/archival/policy.rb +7 -1
  6. data/lib/legion/data/archival.rb +27 -4
  7. data/lib/legion/data/archiver.rb +103 -51
  8. data/lib/legion/data/audit_record.rb +8 -5
  9. data/lib/legion/data/connection.rb +88 -17
  10. data/lib/legion/data/encryption/key_provider.rb +9 -2
  11. data/lib/legion/data/encryption/sequel_plugin.rb +126 -12
  12. data/lib/legion/data/event_store.rb +29 -10
  13. data/lib/legion/data/extract/handlers/base.rb +7 -1
  14. data/lib/legion/data/extract/handlers/csv.rb +1 -0
  15. data/lib/legion/data/extract/handlers/docx.rb +3 -1
  16. data/lib/legion/data/extract/handlers/html.rb +3 -1
  17. data/lib/legion/data/extract/handlers/json.rb +1 -0
  18. data/lib/legion/data/extract/handlers/jsonl.rb +1 -0
  19. data/lib/legion/data/extract/handlers/markdown.rb +1 -0
  20. data/lib/legion/data/extract/handlers/pdf.rb +3 -1
  21. data/lib/legion/data/extract/handlers/pptx.rb +3 -1
  22. data/lib/legion/data/extract/handlers/text.rb +1 -0
  23. data/lib/legion/data/extract/handlers/vtt.rb +1 -0
  24. data/lib/legion/data/extract/handlers/xlsx.rb +3 -1
  25. data/lib/legion/data/extract.rb +7 -0
  26. data/lib/legion/data/helper.rb +16 -6
  27. data/lib/legion/data/local.rb +53 -5
  28. data/lib/legion/data/migration.rb +6 -1
  29. data/lib/legion/data/migrations/044_expand_memory_traces.rb +4 -1
  30. data/lib/legion/data/migrations/061_add_versioning_and_expiry.rb +49 -0
  31. data/lib/legion/data/model.rb +8 -4
  32. data/lib/legion/data/models/audit_log.rb +5 -1
  33. data/lib/legion/data/models/audit_record.rb +5 -1
  34. data/lib/legion/data/models/function.rb +5 -1
  35. data/lib/legion/data/models/node.rb +6 -2
  36. data/lib/legion/data/partition_manager.rb +15 -19
  37. data/lib/legion/data/retention.rb +31 -2
  38. data/lib/legion/data/rls.rb +8 -2
  39. data/lib/legion/data/settings.rb +5 -1
  40. data/lib/legion/data/spool.rb +69 -6
  41. data/lib/legion/data/storage_tiers.rb +16 -3
  42. data/lib/legion/data/vector.rb +9 -5
  43. data/lib/legion/data/version.rb +1 -1
  44. data/lib/legion/data.rb +39 -12
  45. metadata +6 -5
@@ -32,6 +32,7 @@ module Legion
32
32
  }
33
33
  }
34
34
  rescue StandardError => e
35
+ handle_exception(e, level: :warn, handled: true, operation: :extract_vtt)
35
36
  { text: nil, error: e.message }
36
37
  end
37
38
 
@@ -25,9 +25,11 @@ module Legion
25
25
  end
26
26
  text = sheets.join("\n\n")
27
27
  { text: text, metadata: { sheets: workbook.worksheets.size } }
28
- rescue LoadError
28
+ rescue LoadError => e
29
+ handle_exception(e, level: :warn, handled: true, operation: :extract_xlsx, gem: gem_name)
29
30
  { text: nil, error: :gem_not_installed, gem: gem_name }
30
31
  rescue StandardError => e
32
+ handle_exception(e, level: :warn, handled: true, operation: :extract_xlsx)
31
33
  { text: nil, error: e.message }
32
34
  end
33
35
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
3
4
  require_relative 'extract/type_detector'
4
5
  require_relative 'extract/handlers/base'
5
6
 
@@ -7,6 +8,8 @@ module Legion
7
8
  module Data
8
9
  module Extract
9
10
  class << self
11
+ include Legion::Logging::Helper
12
+
10
13
  def extract(source, type: :auto)
11
14
  detected_type = type == :auto ? TypeDetector.detect(source) : type&.to_sym
12
15
  return { success: false, text: nil, error: :unknown_type } unless detected_type
@@ -19,13 +22,17 @@ module Legion
19
22
  gem: handler.gem_name, type: detected_type }
20
23
  end
21
24
 
25
+ log.info "Extract starting type=#{detected_type} handler=#{handler.name}"
22
26
  result = handler.extract(source)
23
27
  if result[:text]
28
+ log.info "Extract succeeded type=#{detected_type}"
24
29
  { success: true, text: result[:text], metadata: result[:metadata], type: detected_type }
25
30
  else
31
+ log.warn "Extract failed type=#{detected_type} error=#{result[:error]}"
26
32
  { success: false, text: nil, error: result[:error], type: detected_type }
27
33
  end
28
34
  rescue StandardError => e
35
+ handle_exception(e, level: :error, handled: true, operation: :extract, type: detected_type)
29
36
  { success: false, text: nil, error: e.message, type: detected_type }
30
37
  end
31
38
 
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Data
5
7
  module Helper
8
+ include Legion::Logging::Helper
9
+
6
10
  def data_path
7
11
  @data_path ||= "#{full_path}/data"
8
12
  end
@@ -39,7 +43,8 @@ module Legion
39
43
 
40
44
  def data_adapter
41
45
  Legion::Data::Connection.adapter
42
- rescue StandardError
46
+ rescue StandardError => e
47
+ handle_exception(e, level: :warn, handled: true, operation: :data_adapter)
43
48
  :unknown
44
49
  end
45
50
 
@@ -47,7 +52,8 @@ module Legion
47
52
  return {} unless data_connected?
48
53
 
49
54
  Legion::Data::Connection.pool_stats
50
- rescue StandardError
55
+ rescue StandardError => e
56
+ handle_exception(e, level: :warn, handled: true, operation: :data_pool_stats)
51
57
  {}
52
58
  end
53
59
 
@@ -55,7 +61,8 @@ module Legion
55
61
  return {} unless data_connected?
56
62
 
57
63
  Legion::Data.stats
58
- rescue StandardError
64
+ rescue StandardError => e
65
+ handle_exception(e, level: :warn, handled: true, operation: :data_stats)
59
66
  {}
60
67
  end
61
68
 
@@ -63,7 +70,8 @@ module Legion
63
70
  return {} unless local_data_connected?
64
71
 
65
72
  Legion::Data::Local.stats
66
- rescue StandardError
73
+ rescue StandardError => e
74
+ handle_exception(e, level: :warn, handled: true, operation: :local_data_stats)
67
75
  {}
68
76
  end
69
77
 
@@ -71,13 +79,15 @@ module Legion
71
79
 
72
80
  def data_can_read?(table_name)
73
81
  Legion::Data.can_read?(table_name)
74
- rescue StandardError
82
+ rescue StandardError => e
83
+ handle_exception(e, level: :warn, handled: true, operation: :data_can_read, table: table_name)
75
84
  false
76
85
  end
77
86
 
78
87
  def data_can_write?(table_name)
79
88
  Legion::Data.can_write?(table_name)
80
- rescue StandardError
89
+ rescue StandardError => e
90
+ handle_exception(e, level: :warn, handled: true, operation: :data_can_write, table: table_name)
81
91
  false
82
92
  end
83
93
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  require 'sequel'
4
6
  require 'sequel/extensions/migration'
5
7
 
@@ -7,6 +9,8 @@ module Legion
7
9
  module Data
8
10
  module Local
9
11
  class << self
12
+ include Legion::Logging::Helper
13
+
10
14
  attr_reader :connection, :db_path
11
15
 
12
16
  def setup(database: nil, **)
@@ -26,14 +30,21 @@ module Legion
26
30
  if local_settings[:query_log]
27
31
  log_path = File.join(Legion::Data::Connection::QUERY_LOG_DIR, 'data-local-query.log')
28
32
  @query_file_logger = Legion::Data::Connection::QueryFileLogger.new(log_path)
29
- opts[:logger] = @query_file_logger
30
- opts[:sql_log_level] = :debug
33
+ opts[:logger] = @query_file_logger
34
+ opts[:sql_log_level] = :debug
35
+ elsif data[:log] && defined?(Legion::Logging)
36
+ opts[:logger] = build_local_logger
37
+ opts[:sql_log_level] = resolved_sql_log_level
38
+ opts[:log_warn_duration] = resolved_log_warn_duration
31
39
  end
32
40
 
33
41
  @connection = ::Sequel.connect(opts)
34
42
  @connected = true
35
43
  run_migrations
36
- Legion::Logging.info "Legion::Data::Local connected to #{db_file}" if defined?(Legion::Logging)
44
+ log.info "Legion::Data::Local connected to #{db_file}"
45
+ rescue StandardError => e
46
+ handle_exception(e, level: :error, handled: false, operation: :local_setup, database: db_file)
47
+ raise
37
48
  end
38
49
 
39
50
  def shutdown
@@ -82,7 +93,8 @@ module Legion
82
93
  wal_autocheckpoint cache_size busy_timeout].each do |pragma|
83
94
  val = begin
84
95
  @connection.fetch("PRAGMA #{pragma}").single_value
85
- rescue StandardError
96
+ rescue StandardError => e
97
+ handle_exception(e, level: :warn, handled: true, operation: :local_stats_pragma, pragma: pragma)
86
98
  nil
87
99
  end
88
100
  stats[pragma.to_sym] = val unless val.nil?
@@ -92,6 +104,7 @@ module Legion
92
104
 
93
105
  stats
94
106
  rescue StandardError => e
107
+ handle_exception(e, level: :warn, handled: true, operation: :local_stats, database: @db_path)
95
108
  { connected: connected?, error: e.message }
96
109
  end
97
110
 
@@ -119,7 +132,7 @@ module Legion
119
132
  table = :"schema_migrations_#{name}"
120
133
  ::Sequel::TimestampMigrator.new(@connection, path, table: table).run
121
134
  rescue StandardError => e
122
- Legion::Logging.warn "Local migration failed for #{path}: #{e.message}" if defined?(Legion::Logging)
135
+ handle_exception(e, level: :warn, handled: true, operation: :local_migration, name: name, path: path)
123
136
  end
124
137
 
125
138
  def local_settings
@@ -127,6 +140,41 @@ module Legion
127
140
 
128
141
  Legion::Settings[:data]&.dig(:local) || {}
129
142
  end
143
+
144
+ def build_local_logger
145
+ tagged = if defined?(Legion::Logging::TaggedLogger) && respond_to?(:tagged_logger_settings, true)
146
+ Legion::Logging::TaggedLogger.new(
147
+ segments: %w[data local],
148
+ **send(:tagged_logger_settings)
149
+ )
150
+ else
151
+ Legion::Data::Connection::SegmentedTaggedLogger.new(segments: %w[data local])
152
+ end
153
+ Legion::Data::Connection::SlowQueryLogger.new(tagged)
154
+ rescue StandardError => e
155
+ if respond_to?(:handle_exception, true)
156
+ handle_exception(e, level: :warn, handled: true, operation: :build_local_logger)
157
+ else
158
+ log.warn("build_local_logger failed: #{e.class}: #{e.message}")
159
+ end
160
+ Legion::Data::Connection::SlowQueryLogger.new(
161
+ Legion::Data::Connection::SegmentedTaggedLogger.new(segments: %w[data local], logger: log)
162
+ )
163
+ end
164
+
165
+ def resolved_sql_log_level
166
+ (local_settings[:sql_log_level] || Legion::Settings[:data][:sql_log_level] || 'debug').to_sym
167
+ rescue StandardError => e
168
+ handle_exception(e, level: :warn, handled: true, operation: :resolved_sql_log_level)
169
+ :debug
170
+ end
171
+
172
+ def resolved_log_warn_duration
173
+ local_settings[:log_warn_duration] || Legion::Settings[:data][:log_warn_duration]
174
+ rescue StandardError => e
175
+ handle_exception(e, level: :warn, handled: true, operation: :resolved_log_warn_duration)
176
+ nil
177
+ end
130
178
  end
131
179
  end
132
180
  end
@@ -1,16 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  require 'sequel/extensions/migration'
4
6
 
5
7
  module Legion
6
8
  module Data
7
9
  module Migration
8
10
  class << self
11
+ include Legion::Logging::Helper
12
+
9
13
  def migrate(connection = Legion::Data.connection, path = "#{__dir__}/migrations", **)
10
14
  Legion::Settings[:data][:migrations][:version] = Sequel::Migrator.run(connection, path, **)
11
- Legion::Logging.info("Legion::Data::Migration ran successfully to version #{Legion::Settings[:data][:migrations][:version]}")
15
+ log.info("Legion::Data::Migration ran successfully to version #{Legion::Settings[:data][:migrations][:version]}")
12
16
  Legion::Settings[:data][:migrations][:ran] = true
13
17
  rescue Sequel::DatabaseError => e
18
+ handle_exception(e, level: :error, handled: false, operation: :migrate, path: path)
14
19
  if e.message.include?('InsufficientPrivilege') || e.message.include?('permission denied')
15
20
  raise Sequel::DatabaseError,
16
21
  "#{e.message}\n Hint: the database user lacks CREATE on schema public " \
@@ -29,7 +29,10 @@ Sequel.migration do
29
29
 
30
30
  indexes = begin
31
31
  db.indexes(:memory_traces).keys
32
- rescue StandardError
32
+ rescue StandardError => e
33
+ if defined?(Legion::Data) && Legion::Data.respond_to?(:handle_exception)
34
+ Legion::Data.handle_exception(e, level: :warn, handled: true, operation: :migration_044_indexes)
35
+ end
33
36
  []
34
37
  end
35
38
 
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ next unless adapter_scheme == :postgres
6
+ next unless table_exists?(:apollo_entries)
7
+
8
+ existing_columns = schema(:apollo_entries).map(&:first)
9
+
10
+ alter_table(:apollo_entries) do
11
+ add_column :parent_knowledge_id, :uuid, null: true unless existing_columns.include?(:parent_knowledge_id)
12
+ add_column :is_latest, :boolean, null: false, default: true unless existing_columns.include?(:is_latest)
13
+ add_column :supersession_type, String, size: 20, null: true unless existing_columns.include?(:supersession_type)
14
+ add_column :expires_at, :timestamptz, null: true unless existing_columns.include?(:expires_at)
15
+ add_column :forget_reason, String, size: 255, null: true unless existing_columns.include?(:forget_reason)
16
+ add_column :is_inference, :boolean, null: false, default: false unless existing_columns.include?(:is_inference)
17
+ end
18
+
19
+ add_index :apollo_entries, :parent_knowledge_id, name: :idx_apollo_parent_knowledge, if_not_exists: true
20
+ add_index :apollo_entries, %i[parent_knowledge_id is_latest],
21
+ name: :idx_apollo_version_chain,
22
+ where: Sequel.lit('is_latest = true'),
23
+ if_not_exists: true
24
+ add_index :apollo_entries, :expires_at,
25
+ name: :idx_apollo_expiry,
26
+ where: Sequel.lit("expires_at IS NOT NULL AND status != 'archived'"),
27
+ if_not_exists: true
28
+ add_index :apollo_entries, :is_inference,
29
+ name: :idx_apollo_inference,
30
+ where: Sequel.lit('is_inference = true'),
31
+ if_not_exists: true
32
+ end
33
+
34
+ down do
35
+ next unless adapter_scheme == :postgres
36
+ next unless table_exists?(:apollo_entries)
37
+
38
+ existing_columns = schema(:apollo_entries).map(&:first)
39
+
40
+ alter_table(:apollo_entries) do
41
+ drop_column :parent_knowledge_id if existing_columns.include?(:parent_knowledge_id)
42
+ drop_column :is_latest if existing_columns.include?(:is_latest)
43
+ drop_column :supersession_type if existing_columns.include?(:supersession_type)
44
+ drop_column :expires_at if existing_columns.include?(:expires_at)
45
+ drop_column :forget_reason if existing_columns.include?(:forget_reason)
46
+ drop_column :is_inference if existing_columns.include?(:is_inference)
47
+ end
48
+ end
49
+ end
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Data
5
7
  module Models
6
8
  class << self
9
+ include Legion::Logging::Helper
10
+
7
11
  attr_reader :loaded_models
8
12
 
9
13
  def models
@@ -13,7 +17,7 @@ module Legion
13
17
  end
14
18
 
15
19
  def load
16
- Legion::Logging.info 'Loading Legion::Data::Models'
20
+ log.info 'Loading Legion::Data::Models'
17
21
  @loaded_models ||= []
18
22
  require_sequel_models(models)
19
23
  Legion::Settings[:data][:models][:loaded] = true
@@ -25,13 +29,13 @@ module Legion
25
29
  end
26
30
 
27
31
  def load_sequel_model(model)
28
- Legion::Logging.debug("Trying to load #{model}.rb")
32
+ log.debug("Trying to load #{model}.rb")
29
33
  require_relative "models/#{model}"
30
34
  @loaded_models << model
31
- Legion::Logging.debug("Successfully loaded #{model}")
35
+ log.debug("Successfully loaded #{model}")
32
36
  model
33
37
  rescue LoadError => e
34
- Legion::Logging.fatal("Failed to load #{model}")
38
+ handle_exception(e, level: :fatal, operation: :load_sequel_model, model: model)
35
39
  raise e unless Legion::Settings[:data][:models][:continue_on_fail]
36
40
  end
37
41
  end
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Data
5
7
  module Model
6
8
  class AuditLog < Sequel::Model(:audit_log)
9
+ include Legion::Logging::Helper
10
+
7
11
  VALID_EVENT_TYPES = %w[runner_execution lifecycle_transition].freeze
8
12
  VALID_STATUSES = %w[success failure denied].freeze
9
13
 
@@ -18,7 +22,7 @@ module Legion
18
22
 
19
23
  Legion::JSON.load(detail)
20
24
  rescue StandardError => e
21
- Legion::Logging.warn("AuditLog#parsed_detail JSON parse failed: #{e.message}") if defined?(Legion::Logging)
25
+ handle_exception(e, level: :warn, handled: true, operation: :parsed_detail, id: self[:id])
22
26
  nil
23
27
  end
24
28
 
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Data
5
7
  module Model
6
8
  class AuditRecord < Sequel::Model(:audit_records)
9
+ include Legion::Logging::Helper
10
+
7
11
  # Enforce append-only semantics at the application layer.
8
12
  # PostgreSQL enforces this at the DB layer via rules (migration 058);
9
13
  # the application guard covers SQLite and MySQL.
@@ -21,7 +25,7 @@ module Legion
21
25
 
22
26
  Legion::JSON.load(metadata)
23
27
  rescue StandardError => e
24
- Legion::Logging.warn "AuditRecord#parsed_metadata failed: #{e.message}" if defined?(Legion::Logging)
28
+ handle_exception(e, level: :warn, handled: true, operation: :parsed_metadata, id: self[:id])
25
29
  {}
26
30
  end
27
31
  end
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Data
5
7
  module Model
6
8
  class Function < Sequel::Model
9
+ include Legion::Logging::Helper
10
+
7
11
  many_to_one :runner
8
12
  one_to_many :trigger_relationships, class: 'Legion::Data::Model::Relationship', key: :trigger_id
9
13
  one_to_many :action_relationships, class: 'Legion::Data::Model::Relationship', key: :action_id
@@ -13,7 +17,7 @@ module Legion
13
17
 
14
18
  ::JSON.parse(embedding)
15
19
  rescue ::JSON::ParserError => e
16
- Legion::Logging.debug("Function#embedding_vector JSON parse failed: #{e.message}") if defined?(Legion::Logging)
20
+ handle_exception(e, level: :debug, handled: true, operation: :embedding_vector, id: self[:id])
17
21
  nil
18
22
  end
19
23
 
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Data
5
7
  module Model
6
8
  class Node < Sequel::Model
9
+ include Legion::Logging::Helper
10
+
7
11
  # one_to_many :task_log
8
12
 
9
13
  def parsed_metrics
@@ -11,7 +15,7 @@ module Legion
11
15
 
12
16
  Legion::JSON.load(metrics)
13
17
  rescue StandardError => e
14
- Legion::Logging.debug("Node#parsed_metrics JSON parse failed: #{e.message}") if defined?(Legion::Logging)
18
+ handle_exception(e, level: :debug, handled: true, operation: :parsed_metrics, id: self[:id])
15
19
  nil
16
20
  end
17
21
 
@@ -20,7 +24,7 @@ module Legion
20
24
 
21
25
  Legion::JSON.load(hosted_worker_ids)
22
26
  rescue StandardError => e
23
- Legion::Logging.debug("Node#parsed_hosted_worker_ids JSON parse failed: #{e.message}") if defined?(Legion::Logging)
27
+ handle_exception(e, level: :debug, handled: true, operation: :parsed_hosted_worker_ids, id: self[:id])
24
28
  []
25
29
  end
26
30
  end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Data
5
7
  module PartitionManager
6
8
  NOT_POSTGRES = { skipped: true, reason: 'not_postgres' }.freeze
7
9
 
8
10
  class << self
11
+ include Legion::Logging::Helper
12
+
9
13
  def ensure_partitions(table:, months_ahead: 3)
10
14
  return NOT_POSTGRES unless postgres?
11
15
 
@@ -28,16 +32,17 @@ module Legion
28
32
  after_count = partition_names_for(table).size
29
33
 
30
34
  if after_count > before_count
31
- log_info("Created partition #{partition}") if logging?
35
+ log.info("Created partition #{partition}")
32
36
  created << partition
33
37
  else
34
38
  existing << partition
35
39
  end
36
40
  end
37
41
 
42
+ log.info "PartitionManager ensure_partitions table=#{table} created=#{created.size} existing=#{existing.size}"
38
43
  { created: created, existing: existing }
39
44
  rescue StandardError => e
40
- log_warn("ensure_partitions failed for #{table}: #{e.message}") if logging?
45
+ handle_exception(e, level: :warn, handled: true, operation: :ensure_partitions, table: table, months_ahead: months_ahead)
41
46
  { created: [], existing: [], error: e.message }
42
47
  end
43
48
 
@@ -54,16 +59,17 @@ module Legion
54
59
 
55
60
  if part_date < cutoff
56
61
  Legion::Data.connection.run("DROP TABLE #{part}")
57
- log_info("Dropped partition #{part}") if logging?
62
+ log.info("Dropped partition #{part}")
58
63
  dropped << part
59
64
  else
60
65
  retained << part
61
66
  end
62
67
  end
63
68
 
69
+ log.info "PartitionManager drop_old_partitions table=#{table} dropped=#{dropped.size} retained=#{retained.size}"
64
70
  { dropped: dropped, retained: retained }
65
71
  rescue StandardError => e
66
- log_warn("drop_old_partitions failed for #{table}: #{e.message}") if logging?
72
+ handle_exception(e, level: :warn, handled: true, operation: :drop_old_partitions, table: table, retention_months: retention_months)
67
73
  { dropped: [], retained: [], error: e.message }
68
74
  end
69
75
 
@@ -80,12 +86,14 @@ module Legion
80
86
  ORDER BY c.relname
81
87
  SQL
82
88
 
83
- Legion::Data.connection.fetch(sql).map do |row|
89
+ partitions = Legion::Data.connection.fetch(sql).map do |row|
84
90
  from_val, to_val = parse_bound(row[:bound])
85
91
  { name: row[:name], from: from_val, to: to_val }
86
92
  end
93
+ log.info "PartitionManager list_partitions table=#{table} count=#{partitions.size}"
94
+ partitions
87
95
  rescue StandardError => e
88
- log_warn("list_partitions failed for #{table}: #{e.message}") if logging?
96
+ handle_exception(e, level: :warn, handled: true, operation: :list_partitions, table: table)
89
97
  []
90
98
  end
91
99
 
@@ -95,18 +103,6 @@ module Legion
95
103
  Legion::Data::Connection.adapter == :postgres
96
104
  end
97
105
 
98
- def logging?
99
- defined?(Legion::Logging)
100
- end
101
-
102
- def log_info(msg)
103
- Legion::Logging.info(msg)
104
- end
105
-
106
- def log_warn(msg)
107
- Legion::Logging.warn(msg)
108
- end
109
-
110
106
  def partition_name(table, date)
111
107
  "#{table}_y#{date.strftime('%Y')}m#{date.strftime('%m')}"
112
108
  end
@@ -136,7 +132,7 @@ module Legion
136
132
 
137
133
  Legion::Data.connection.fetch(sql).map { |row| row[:name] }
138
134
  rescue StandardError => e
139
- log_warn("partition_names_for #{table} failed: #{e.message}") if logging?
135
+ handle_exception(e, level: :warn, handled: true, operation: :partition_names_for, table: table)
140
136
  []
141
137
  end
142
138
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
3
4
  require_relative 'archival/policy'
4
5
 
5
6
  module Legion
@@ -9,6 +10,8 @@ module Legion
9
10
  DEFAULT_ARCHIVE_AFTER_DAYS = 90
10
11
 
11
12
  class << self
13
+ include Legion::Logging::Helper
14
+
12
15
  def archive_old_records(table:, date_column: nil, archive_after_days: DEFAULT_ARCHIVE_AFTER_DAYS)
13
16
  db = Legion::Data.connection
14
17
  return { archived: 0, table: table } unless db
@@ -29,8 +32,19 @@ module Legion
29
32
  end
30
33
  end
31
34
 
32
- Legion::Logging.info "Archived #{count} row(s) from #{table}" if defined?(Legion::Logging) && count.positive?
35
+ log.info "Archived #{count} row(s) from #{table}" if count.positive?
33
36
  { archived: count, table: table }
37
+ rescue StandardError => e
38
+ handle_exception(
39
+ e,
40
+ level: :error,
41
+ handled: false,
42
+ operation: :archive_old_records,
43
+ table: table,
44
+ date_column: date_column,
45
+ archive_after_days: archive_after_days
46
+ )
47
+ raise
34
48
  end
35
49
 
36
50
  def purge_expired_records(table:, date_column: nil, retention_years: DEFAULT_RETENTION_YEARS)
@@ -43,9 +57,20 @@ module Legion
43
57
  expired = db[archive_table].where(Sequel.identifier(date_column) < cutoff)
44
58
  count = expired.count
45
59
  expired.delete if count.positive?
46
- Legion::Logging.info "Purged #{count} expired row(s) from #{archive_table}" if defined?(Legion::Logging) && count.positive?
60
+ log.info "Purged #{count} expired row(s) from #{archive_table}" if count.positive?
47
61
 
48
62
  { purged: count, table: table }
63
+ rescue StandardError => e
64
+ handle_exception(
65
+ e,
66
+ level: :error,
67
+ handled: false,
68
+ operation: :purge_expired_records,
69
+ table: table,
70
+ date_column: date_column,
71
+ retention_years: retention_years
72
+ )
73
+ raise
49
74
  end
50
75
 
51
76
  def retention_status(table:, date_column: nil)
@@ -67,6 +92,9 @@ module Legion
67
92
  oldest_active: oldest_active,
68
93
  oldest_archived: oldest_archived
69
94
  }
95
+ rescue StandardError => e
96
+ handle_exception(e, level: :warn, handled: false, operation: :retention_status, table: table, date_column: date_column)
97
+ raise
70
98
  end
71
99
 
72
100
  def archive_table_name(table)
@@ -90,6 +118,7 @@ module Legion
90
118
 
91
119
  source_schema = db.schema(source_table).to_h
92
120
 
121
+ log.info "Creating archive table #{archive_table} from #{source_table}"
93
122
  db.create_table(archive_table) do
94
123
  source_schema.each do |col_name, col_info|
95
124
  column col_name, col_info[:db_type]
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Data
5
7
  module Rls
8
+ extend Legion::Logging::Helper
9
+
6
10
  RLS_TABLES = %i[
7
11
  tasks digital_workers audit_log memory_traces extensions
8
12
  functions runners nodes settings value_metrics
@@ -14,7 +18,8 @@ module Legion
14
18
  return false unless Legion::Settings[:data][:connected]
15
19
 
16
20
  Legion::Data.connection.adapter_scheme == :postgres
17
- rescue StandardError
21
+ rescue StandardError => e
22
+ handle_exception(e, level: :warn, handled: true, operation: :rls_enabled)
18
23
  false
19
24
  end
20
25
 
@@ -30,7 +35,8 @@ module Legion
30
35
  return nil unless rls_enabled?
31
36
 
32
37
  Legion::Data.connection.fetch('SHOW app.current_tenant').first&.values&.first
33
- rescue Sequel::DatabaseError
38
+ rescue Sequel::DatabaseError => e
39
+ handle_exception(e, level: :warn, handled: true, operation: :current_tenant)
34
40
  nil
35
41
  end
36
42