mysql_framework 2.1.9 → 2.3.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: 91ca6641d16e5f4d344b6749fe9f5291c8f24b23f4f8efb28ac8e76443d61750
4
- data.tar.gz: 961c49140b91c9ea461a1512d63bb89a5861ed5375386e21e21be1c0e1691f2b
3
+ metadata.gz: 2e86d1afb0cbbd571d728e469bf5fffa14ad3e0f89f268dcf951564aca98043a
4
+ data.tar.gz: be53e02e828f37e5d7c651b1526b353e8dc599df0396b6b28f50632efecbfd9f
5
5
  SHA512:
6
- metadata.gz: 3b3b3b5ebf141f85110733c54cf6c190e8c091bb681d0e9fc555643efaca73cc29f61964f7ce06142ed48fa466bb49c1f616ff48ba3d44223db12d414e6bcc81
7
- data.tar.gz: a0119d3cb52bc3d513054fe803f9627dfd6ca0c20f51dc602fcfe36897227bb6ea29933cff88599f1fbb5d7019bad8cf971aedf887b216fbd5a07c55e39f8b33
6
+ metadata.gz: 2e4fae53d9831f913be250ace9a5b4d5ad85e89433561b7be804a11b0e30f6faf434195fb34b2b4c8d31b9669ac486da07e5dd5a65ee17ca1d52d172bd13b824
7
+ data.tar.gz: 4ee13ac87b94966188ddd33a2d9b090192f7bddac0e4bf7623d14553176fa1706fa58b83728fc89746383c60e12165f4f092052013bffb51a03218895269ec21
@@ -1,133 +1,171 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'mysql_connection_pool'
4
+
3
5
  module MysqlFramework
4
6
  class Connector
7
+ attr_reader :connection_pool
8
+
9
+ # Initializes a connector instance with MySQL client options.
10
+ #
11
+ # @param options [Hash] custom MySQL client options that override defaults
12
+ # @return [void]
5
13
  def initialize(options = {})
6
14
  @options = default_options.merge(options)
7
- @mutex = Mutex.new
8
-
9
15
  Mysql2::Client.default_query_options.merge!(symbolize_keys: true, cast_booleans: true)
10
16
  end
11
17
 
12
- # This method is called to setup a pool of MySQL connections.
18
+ # Sets up the MySQL connection pool when pooling is enabled.
19
+ #
20
+ # @return [ConnectionPool, nil] configured pool, or nil when pooling is disabled
13
21
  def setup
14
22
  return unless connection_pool_enabled?
15
23
 
16
- @connection_pool = ::Queue.new
17
-
18
- start_pool_size.times { @connection_pool.push(new_client) }
19
-
20
- @created_connections = start_pool_size
24
+ @connection_pool = MysqlFramework::MysqlConnectionPool.new(@options)
25
+ @connection_pool.setup
21
26
  end
22
27
 
23
- # This method is called to close all MySQL connections in the pool and dispose of the pool itself.
28
+ # Disposes of the connection pool and closes pooled connections.
29
+ #
30
+ # @return [void]
24
31
  def dispose
25
- return if @connection_pool.nil?
26
-
27
- until @connection_pool.empty?
28
- conn = @connection_pool.pop(true)
29
- conn&.close
30
- end
32
+ return unless connection_pool_enabled?
31
33
 
34
+ @connection_pool&.dispose
32
35
  @connection_pool = nil
33
36
  end
34
37
 
35
- # This method is called to get the idle connection queue for this connector.
36
- def connections
37
- @connection_pool
38
- end
39
-
40
- # This method is called to fetch a client from the connection pool.
38
+ # Checks out a MySQL client, sanitizing it before use.
39
+ #
40
+ # @return [Mysql2::Client] checked-out client
41
+ # @raise [ConnectionSanitizationError] when sanitization repeatedly fails
42
+ # @raise [Mysql2::Error] when checkout or sanitization fails due to MySQL errors
41
43
  def check_out
42
- @mutex.synchronize do
43
- begin
44
- return new_client unless connection_pool_enabled?
45
-
46
- client = @connection_pool.pop(true)
47
-
48
- client.ping if @options[:reconnect]
44
+ return new_client unless connection_pool_enabled?
49
45
 
50
- client
51
- rescue ThreadError
52
- if @created_connections < max_pool_size
53
- client = new_client
54
- @created_connections += 1
55
- return client
56
- end
57
-
58
- MysqlFramework.logger.error { "[#{self.class}] - Database connection pool depleted." }
59
-
60
- raise 'Database connection pool depleted.'
61
- end
62
- end
46
+ @connection_pool.check_out
63
47
  end
64
48
 
65
- # This method is called to check a client back in to the connection when no longer needed.
49
+ # Returns a MySQL client back to the pool or closes it when pooling is disabled.
50
+ #
51
+ # @param client [Mysql2::Client, nil] client to return or close
52
+ # @return [void]
66
53
  def check_in(client)
67
- @mutex.synchronize do
68
- return client&.close unless connection_pool_enabled?
54
+ return client&.close unless connection_pool_enabled?
69
55
 
70
- client = new_client if client&.closed?
71
- @connection_pool.push(client)
72
- end
56
+ @connection_pool.check_in(client)
73
57
  end
74
58
 
75
- # This method is called to use a client from the connection pool.
76
- def with_client(provided = nil)
77
- client = provided || check_out
78
- yield client
79
- ensure
80
- check_in(client) unless provided
59
+ # Yields a MySQL client from the pool, or yields the provided client directly.
60
+ #
61
+ # @param provided_client [Mysql2::Client, nil] existing client to yield without pool checkout
62
+ # @param discard_current_pool_connection [Boolean] whether to discard the pooled connection after use
63
+ # @yield [client] block that performs work with a MySQL client
64
+ # @yieldparam client [Mysql2::Client]
65
+ # @return [Object] block result
66
+ # @raise [Mysql2::Error] re-raises MySQL errors from the block
67
+ def with_client(provided_client = nil, discard_current_pool_connection: false)
68
+ return yield provided_client if provided_client
69
+ return with_new_client { |c| yield c } unless connection_pool_enabled?
70
+
71
+ @connection_pool.with_client(discard_current_pool_connection:) { |c| yield c }
81
72
  end
82
73
 
83
- # This method is called to execute a prepared statement
74
+ # Executes a prepared statement.
75
+ #
76
+ # @param query [Object] query object responding to +sql+ and +params+
77
+ # @param provided_client [Mysql2::Client, nil] optional existing client
78
+ # @return [Array<Hash>, nil] query result rows
79
+ # @raise [Mysql2::Error] when statement preparation or execution fails
84
80
  #
85
- # @note Ensure we free any result and close each statement, otherwise we
86
- # can run into a 'Commands out of sync' error if multiple threads are
87
- # running different queries at the same time.
81
+ # NOTE:
82
+ # We must always free the result and close the prepared statement.
83
+ # Otherwise MySQL may raise "Commands out of sync" when the same
84
+ # connection is reused (e.g. via connection pooling).
85
+ #
86
+ # The connection itself must NOT be closed here because it is
87
+ # managed by the connection pool.
88
88
  def execute(query, provided_client = nil)
89
89
  with_client(provided_client) do |client|
90
+ statement = nil
91
+ result = nil
92
+
90
93
  begin
91
94
  statement = client.prepare(query.sql)
92
- result = statement.execute(*query.params)
93
- result&.to_a
95
+ result = statement.execute(
96
+ *query.params, symbolize_keys: true, cast_booleans: true
97
+ )
98
+ final = result&.to_a
99
+ final
94
100
  ensure
101
+ client&.abandon_results!
95
102
  result&.free
96
103
  statement&.close
97
104
  end
98
105
  end
99
106
  end
100
107
 
101
- # This method is called to execute a query
108
+ # Executes a SQL query.
109
+ #
110
+ # @param query_string [String] SQL query to execute
111
+ # @param provided_client [Mysql2::Client, nil] optional existing client
112
+ # @return [Mysql2::Result] raw MySQL result
113
+ # @raise [Mysql2::Error] when query execution fails
102
114
  def query(query_string, provided_client = nil)
103
- with_client(provided_client) { |client| client.query(query_string) }
115
+ with_client(provided_client) { |conn| conn.query(query_string) }
104
116
  end
105
117
 
106
- # This method is called to execute a query which will return multiple result sets in an array
118
+ # Executes a multi-statement SQL query and collects all result sets.
119
+ #
120
+ # @param query_string [String] multi-statement SQL query
121
+ # @param provided_client [Mysql2::Client, nil] optional existing client
122
+ # @return [Array<Array<Hash>>] list of result sets
123
+ # @raise [Mysql2::Error] when query execution or result fetching fails
107
124
  def query_multiple_results(query_string, provided_client = nil)
108
- results = with_client(provided_client) do |client|
109
- result = []
110
- result << client.query(query_string)
111
- result << client.store_result while client.next_result
112
- result.compact
125
+ results = nil
126
+
127
+ # Multiple statement query is buggy and client cannot be reused after calling next_result/store_result
128
+ # Client's state gets corrupted and leaks into next queries. The reason is unknown.
129
+ # As a result we do not return client back to the pool but instead close connection which is not optimal.
130
+ with_client(provided_client, discard_current_pool_connection: true) do |client|
131
+ raw_results = []
132
+ query_call = client.query(query_string)
133
+ raw_results << query_call&.to_a
134
+ query_call&.free
135
+
136
+ while client.more_results?
137
+ client.next_result
138
+ query_call = client.store_result
139
+ raw_results << query_call&.to_a
140
+ query_call&.free
141
+ end
142
+
143
+ results = raw_results.compact
144
+ results
145
+ ensure
146
+ client&.abandon_results!
113
147
  end
114
148
 
115
- results.map(&:to_a)
149
+ results
116
150
  end
117
151
 
118
- # This method is called to use a client within a transaction
152
+ # Executes a block within a database transaction.
153
+ #
154
+ # @yield [client] block executed between BEGIN and COMMIT
155
+ # @yieldparam client [Mysql2::Client]
156
+ # @return [Object] block result
157
+ # @raise [ArgumentError] when no block is given
158
+ # @raise [StandardError] re-raises any exception after rollback
119
159
  def transaction
120
160
  raise ArgumentError, 'No block was given' unless block_given?
121
161
 
122
162
  with_client do |client|
123
- begin
124
- client.query('BEGIN')
125
- yield client
126
- client.query('COMMIT')
127
- rescue StandardError => e
128
- client.query('ROLLBACK')
129
- raise e
130
- end
163
+ client.query('BEGIN')
164
+ yield client
165
+ client.query('COMMIT')
166
+ rescue StandardError => e
167
+ client.query('ROLLBACK')
168
+ raise e
131
169
  end
132
170
  end
133
171
 
@@ -146,20 +184,21 @@ module MysqlFramework
146
184
  }
147
185
  end
148
186
 
187
+ def with_new_client
188
+ client = new_client
189
+ yield client
190
+ ensure
191
+ client&.close
192
+ end
193
+
149
194
  def new_client
150
195
  Mysql2::Client.new(@options)
151
196
  end
152
197
 
153
198
  def connection_pool_enabled?
154
- @connection_pool_enabled ||= ENV.fetch('MYSQL_CONNECTION_POOL_ENABLED', 'true').casecmp?('true')
155
- end
156
-
157
- def start_pool_size
158
- @start_pool_size ||= Integer(ENV.fetch('MYSQL_START_POOL_SIZE', 1))
159
- end
199
+ return @connection_pool_enabled unless @connection_pool_enabled.nil?
160
200
 
161
- def max_pool_size
162
- @max_pool_size ||= Integer(ENV.fetch('MYSQL_MAX_POOL_SIZE', 5))
201
+ @connection_pool_enabled = ENV.fetch('MYSQL_CONNECTION_POOL_ENABLED', 'true').casecmp?('true')
163
202
  end
164
203
  end
165
204
  end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'connection_pool'
4
+
5
+ module MysqlFramework
6
+ class MysqlConnectionPool
7
+ class ConnectionSanitizationError < StandardError; end
8
+
9
+ CLEAN_IDLE_CONNECTIONS_THREAD_NAME = 'clean-idle-connections'
10
+
11
+ attr_reader :connections
12
+
13
+ # Initializes a connection pool instance with MySQL client options.
14
+ #
15
+ # @param options [Hash] MySQL client options passed to each pooled connection
16
+ # @return [void]
17
+ def initialize(options)
18
+ @options = options
19
+ @setup_mutex = Mutex.new
20
+ end
21
+
22
+ # Sets up the MySQL connection pool. Idempotent — safe to call more than once.
23
+ #
24
+ # @return [ConnectionPool] configured pool
25
+ def setup
26
+ @setup_mutex.synchronize do
27
+ return if connections
28
+
29
+ @connections = ConnectionPool.new(size: max_pool_size, timeout: pool_timeout) do
30
+ Mysql2::Client.new(@options)
31
+ end
32
+
33
+ start_clean_idle_connections_thread
34
+ end
35
+ end
36
+
37
+ # Disposes of the connection pool and closes pooled connections.
38
+ #
39
+ # @return [void]
40
+ def dispose
41
+ @setup_mutex.synchronize do
42
+ dispose_clean_idle_connections_thread
43
+ connections&.shutdown(&:close)
44
+ @connections = nil
45
+ end
46
+ end
47
+
48
+ # Returns key connection-pool metrics for monitoring.
49
+ #
50
+ # @return [Hash{Symbol => Integer}] pool size and availability metrics
51
+ def pool_stats
52
+ return { size: 0, available: 0, idle: 0 } if connections.nil?
53
+
54
+ {
55
+ size: connections.size,
56
+ available: connections.available,
57
+ idle: connections.idle
58
+ }
59
+ end
60
+
61
+ # Checks out a MySQL client, sanitizing it before use.
62
+ #
63
+ # @return [Mysql2::Client] checked-out client
64
+ # @raise [ConnectionSanitizationError] when sanitization repeatedly fails
65
+ # @raise [Mysql2::Error] when checkout or sanitization fails due to MySQL errors
66
+ def check_out
67
+ sanitization_retries = 0
68
+ begin
69
+ conn = connections.checkout
70
+ sanitize_connection!(conn)
71
+ conn
72
+ rescue ConnectionSanitizationError
73
+ discard_current_connection!
74
+ sanitization_retries += 1
75
+ retry if sanitization_retries <= 1
76
+ raise
77
+ rescue Mysql2::Error
78
+ discard_current_connection!
79
+ raise
80
+ end
81
+ end
82
+
83
+ # Returns a MySQL client back to the pool or closes it when pooling is disabled.
84
+ #
85
+ # @param client [Mysql2::Client, nil] client to return or close
86
+ # @return [void]
87
+ def check_in(client)
88
+ return if client.nil?
89
+
90
+ discard_current_connection! if client.closed?
91
+ connections.checkin
92
+ end
93
+
94
+ # Yields a MySQL client from the pool, or yields the provided client directly.
95
+ #
96
+ # @param provided_client [Mysql2::Client, nil] existing client to yield without pool checkout
97
+ # @param discard_current_pool_connection [Boolean] whether to discard the pooled connection after use
98
+ # @yield [client] block that performs work with a MySQL client
99
+ # @yieldparam client [Mysql2::Client]
100
+ # @return [Object] block result
101
+ # @raise [Mysql2::Error] re-raises MySQL errors from the block
102
+ def with_client(discard_current_pool_connection: false)
103
+ sanitization_retries = 0
104
+
105
+ begin
106
+ connections.with do |conn|
107
+ sanitize_connection!(conn)
108
+ yield conn
109
+ rescue ConnectionSanitizationError, Mysql2::Error
110
+ discard_current_connection!
111
+ raise
112
+ ensure
113
+ discard_current_connection! if discard_current_pool_connection
114
+ end
115
+ rescue ConnectionSanitizationError
116
+ sanitization_retries += 1
117
+ retry if sanitization_retries <= 1
118
+ raise
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def start_clean_idle_connections_thread
125
+ thread_name = "#{CLEAN_IDLE_CONNECTIONS_THREAD_NAME}-#{object_id}"
126
+ @idle_connections_thread = Thread.new do
127
+ Thread.current.name = thread_name
128
+ loop do
129
+ sleep idle_reap_loop_time
130
+ break unless Thread.current == @idle_connections_thread
131
+
132
+ connections&.reap(idle_seconds: idle_timeout, &:close)
133
+ end
134
+ end
135
+
136
+ @idle_connections_thread.abort_on_exception # = false
137
+ @idle_connections_thread
138
+ end
139
+
140
+ def dispose_clean_idle_connections_thread
141
+ @idle_connections_thread&.join(5)
142
+ @idle_connections_thread&.kill
143
+ @idle_connections_thread = nil
144
+ end
145
+
146
+ def sanitize_connection!(conn)
147
+ conn.ping
148
+ conn.abandon_results!
149
+ conn.query('ROLLBACK')
150
+ rescue Mysql2::Error => e
151
+ raise ConnectionSanitizationError, "Connection sanitization failed: #{e.message}"
152
+ end
153
+
154
+ def discard_current_connection!
155
+ connections&.discard_current_connection(&:close)
156
+ rescue StandardError
157
+ nil
158
+ end
159
+
160
+ def max_pool_size
161
+ @max_pool_size ||= Integer(ENV.fetch('MYSQL_MAX_POOL_SIZE', 5))
162
+ end
163
+
164
+ def pool_timeout
165
+ @pool_timeout ||= Integer(ENV.fetch('MYSQL_POOL_TIMEOUT', 5))
166
+ end
167
+
168
+ def idle_timeout
169
+ @idle_timeout ||= Integer(ENV.fetch('MYSQL_POOL_IDLE_TIMEOUT', 300))
170
+ end
171
+
172
+ def idle_reap_loop_time
173
+ @idle_reap_loop_time ||= Integer(ENV.fetch('MYSQL_POOL_IDLE_REAP_TIME', 60))
174
+ end
175
+ end
176
+ end
@@ -27,7 +27,7 @@ module MysqlFramework
27
27
  end
28
28
 
29
29
  def apply_by_tag(tags)
30
- lock_manager.with_lock(key: self.class) do
30
+ lock_manager.with_lock(key: self.class.name) do
31
31
  initialize_script_history
32
32
 
33
33
  mysql_connector.transaction do |client|
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-cloudwatch'
4
+ require_relative 'dimension_map'
5
+
6
+ module MysqlFramework
7
+ module Stats
8
+ class AwsMetricPublisher
9
+ THREAD_NAME = 'mysql-connector-pool-stats'
10
+ JOIN_TIMEOUT = 5 # seconds to wait for clean thread exit before force-killing
11
+ METRIC_UNIT = 'Count'
12
+ METRIC_NAME_MAP = {
13
+ size: 'MysqlConnectionPoolSize',
14
+ available: 'MysqlConnectionPoolAvailable',
15
+ idle: 'MysqlConnectionPoolIdle'
16
+ }.freeze
17
+
18
+ # Initializes AWS metric publishing dependencies.
19
+ #
20
+ # @param connector [MysqlFramework::Connector, nil] connector used to read connection-pool stats
21
+ # @param dimension_map [MysqlFramework::Stats::DimensionMap, nil] CloudWatch namespace and dimensions
22
+ # @param cloudwatch_client [Aws::CloudWatch::Client, nil] CloudWatch client instance
23
+ # @param publish_interval [Integer] metric publish interval in seconds
24
+ # @return [void]
25
+ def initialize(
26
+ connector: nil,
27
+ dimension_map: nil,
28
+ cloudwatch_client: nil,
29
+ publish_interval: 300
30
+ )
31
+ @thread = nil
32
+ @connector = connector
33
+ @cloudwatch_client = cloudwatch_client
34
+ @dimension_map = dimension_map || MysqlFramework::Stats::DimensionMap.new
35
+ @publish_interval = publish_interval
36
+ end
37
+
38
+ # Spawns the background sampling thread. Safe to call more than once –
39
+ # subsequent calls are no-ops while the thread is already running.
40
+ #
41
+ # @return [Thread, nil] reporter thread when started, or nil when already running
42
+ def start
43
+ return if running?
44
+
45
+ thread_name = "#{THREAD_NAME}-#{object_id}"
46
+ thread = Thread.new do
47
+ Thread.current.name = thread_name
48
+ loop do
49
+ sleep @publish_interval
50
+ break unless Thread.current == @thread
51
+
52
+ sample
53
+ end
54
+ end
55
+
56
+ thread.abort_on_exception = false
57
+ @thread = thread
58
+ end
59
+
60
+ # Cooperatively stops the background thread and waits up to JOIN_TIMEOUT
61
+ # seconds for it to exit before force-killing it.
62
+ #
63
+ # @return [void]
64
+ def stop
65
+ thread = @thread
66
+ @thread = nil # cooperative stop signal: loop checks this after each sleep
67
+ thread&.join(JOIN_TIMEOUT)
68
+ thread&.kill # force-kill only if still alive after timeout
69
+ end
70
+
71
+ # Returns true when the reporter thread is alive.
72
+ #
73
+ # @return [Boolean]
74
+ def running?
75
+ @thread&.alive? || false
76
+ end
77
+
78
+ private
79
+
80
+ # Reads pool stats and publishes them to CloudWatch using a low-cardinality
81
+ # dimension set so all ECS tasks for the same service aggregate together.
82
+ # Errors are swallowed and logged so that a reporting failure never
83
+ # propagates to the caller.
84
+ def sample
85
+ connection_pool = @connector&.connection_pool
86
+ return if connection_pool.nil?
87
+
88
+ stats = connection_pool.pool_stats
89
+ metric_data = build_metric_data(stats)
90
+ return if metric_data.empty?
91
+
92
+ MysqlFramework.logger.debug { "[#{self.class}] - CloudWatch/#{@dimension_map.namespace} - #{stats.inspect}" }
93
+
94
+ cloudwatch_client.put_metric_data(
95
+ namespace: @dimension_map.namespace,
96
+ metric_data: metric_data
97
+ )
98
+ rescue StandardError => e
99
+ MysqlFramework.logger.error { "[#{self.class}] - Failed to record pool stats: #{e.message}" }
100
+ end
101
+
102
+ def build_metric_data(stats)
103
+ timestamp = Time.now.utc
104
+
105
+ METRIC_NAME_MAP.filter_map do |key, metric_name|
106
+ value = stats[key]
107
+ next if value.nil?
108
+
109
+ {
110
+ metric_name: metric_name,
111
+ dimensions: @dimension_map.to_cloudwatch_dimensions,
112
+ timestamp: timestamp,
113
+ unit: METRIC_UNIT,
114
+ value: value.to_f
115
+ }
116
+ end
117
+ end
118
+
119
+ def cloudwatch_client
120
+ @cloudwatch_client ||= Aws::CloudWatch::Client.new
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MysqlFramework
4
+ module Stats
5
+ # Class to handle dimensions for AWS reporting
6
+ class DimensionMap
7
+ attr_accessor :service_name, :application, :environment, :landscape, :namespace
8
+
9
+ # Initializes dimension values used for CloudWatch metrics.
10
+ #
11
+ # @param service_name [String, nil] service dimension
12
+ # @param application [String, nil] application dimension
13
+ # @param environment [String, nil] environment dimension
14
+ # @param landscape [String, nil] landscape dimension
15
+ # @param namespace [String, nil] CloudWatch namespace override
16
+ # @return [void]
17
+ def initialize(
18
+ service_name: nil,
19
+ application: nil,
20
+ environment: nil,
21
+ landscape: nil,
22
+ namespace: nil
23
+ )
24
+ @service_name = service_name
25
+ @application = application
26
+ @environment = environment
27
+ @landscape = landscape
28
+ @namespace = namespace
29
+ end
30
+
31
+ # Builds CloudWatch dimensions from configured values or environment variables.
32
+ #
33
+ # @return [Array<Hash{Symbol => String}>] dimensions with non-nil values only
34
+ def to_cloudwatch_dimensions
35
+ [
36
+ { name: 'ServiceName', value: service_name || ENV.fetch('SERVICE_NAME', nil) },
37
+ { name: 'Application', value: application || ENV.fetch('APPLICATION', nil) },
38
+ { name: 'Environment', value: environment || ENV.fetch('ENVIRONMENT', nil) },
39
+ { name: 'Landscape', value: landscape || ENV.fetch('LANDSCAPE', nil) }
40
+ ].reject { |dimension| dimension[:value].nil? }
41
+ end
42
+
43
+ # Returns the CloudWatch namespace.
44
+ #
45
+ # @return [String] configured namespace or default namespace value
46
+ def namespace
47
+ @namespace || ENV.fetch('AWS_METRICS_NAMESPACE', 'MysqlFramework')
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MysqlFramework
4
- VERSION = '2.1.9'
4
+ VERSION = '2.3.0'
5
5
  end