Module: PostgresExtension
- Defined in:
- lib/instrumentation/postgres.rb
Constant Summary collapse
- SQL_COMMANDS =
A list of SQL commands, from: www.postgresql.org/docs/current/sql-commands.html Commands are truncated to their first word, and all duplicates are removed, This favors brevity and low-cardinality over descriptiveness.
%w[ ABORT ALTER ANALYZE BEGIN CALL CHECKPOINT CLOSE CLUSTER COMMENT COMMIT COPY CREATE DEALLOCATE DECLARE DELETE DISCARD DO DROP END EXECUTE EXPLAIN FETCH GRANT IMPORT INSERT LISTEN LOAD LOCK MOVE NOTIFY PREPARE PREPARE REASSIGN REFRESH REINDEX RELEASE RESET REVOKE ROLLBACK SAVEPOINT SECURITY SELECT SELECT SET SHOW START TRUNCATE UNLISTEN UPDATE VACUUM VALUES ].freeze
- COMPONENTS_REGEX_MAP =
{ single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/, dollar_quotes: /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/, uuids: /\{?(?:[0-9a-fA-F]\-*){32}\}?/, numeric_literals: /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/, boolean_literals: /\b(?:true|false|null)\b/i, comments: /(?:#|--).*?(?=\r|\n|$)/i, multi_line_comments: %r{\/\*(?:[^\/]|\/[^*])*?(?:\*\/|\/\*.*)} }.freeze
- POSTGRES_COMPONENTS =
%i[ single_quotes dollar_quotes uuids numeric_literals boolean_literals comments multi_line_comments ].freeze
- UNMATCHED_PAIRS_REGEX =
%r{'|\/\*|\*\/|\$(?!\?)}.freeze
- EXEC_ISH_METHODS =
These are all alike in that they will have a SQL statement as the first parameter. That statement may possibly be parameterized, but we can still use it - the obfuscation code will just transform $1 -> $? in that case (which is fine enough).
%i[ exec query sync_exec async_exec exec_params async_exec_params sync_exec_params ].freeze
- PREPARE_ISH_METHODS =
The following methods all take a statement name as the first parameter, and a SQL statement as the second - and possibly further parameters after that. We can trace them all alike.
%i[ prepare async_prepare sync_prepare ].freeze
- EXEC_PREPARED_ISH_METHODS =
The following methods take a prepared statement name as their first parameter - everything after that is either potentially quite sensitive (an array of bind params) or not useful to us. We trace them all alike.
%i[ exec_prepared async_exec_prepared sync_exec_prepared ].freeze
Instance Method Summary collapse
- #client_attributes ⇒ Object
- #config ⇒ Object
- #database_name ⇒ Object
- #extract_operation(sql) ⇒ Object
- #generated_postgres_regex ⇒ Object
- #lru_cache ⇒ Object
-
#span_attrs(kind, *args) ⇒ Object
Rubocop is complaining about 19.31/18 for Metrics/AbcSize.
- #table_name(sql) ⇒ Object
- #tracer ⇒ Object
- #transport_attrs ⇒ Object
- #validated_operation(operation) ⇒ Object
Instance Method Details
#client_attributes ⇒ Object
221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/instrumentation/postgres.rb', line 221 def client_attributes attributes = { 'db.system' => 'postgresql', 'db.user' => conninfo_hash[:user]&.to_s, 'db.name' => database_name, 'net.peer.name' => conninfo_hash[:host]&.to_s } # attributes['peer.service'] = config[:peer_service] # if config[:peer_service] attributes.merge(transport_attrs).reject { |_, v| v.nil? } end |
#config ⇒ Object
142 143 144 |
# File 'lib/instrumentation/postgres.rb', line 142 def config EpsagonPostgresInstrumentation.instance.config end |
#database_name ⇒ Object
217 218 219 |
# File 'lib/instrumentation/postgres.rb', line 217 def database_name conninfo_hash[:dbname]&.to_s end |
#extract_operation(sql) ⇒ Object
208 209 210 211 |
# File 'lib/instrumentation/postgres.rb', line 208 def extract_operation(sql) # From: https://github.com/open-telemetry/opentelemetry-js-contrib/blob/9244a08a8d014afe26b82b91cf86e407c2599d73/plugins/node/opentelemetry-instrumentation-pg/src/utils.ts#L35 sql.to_s.split[0].to_s.upcase end |
#generated_postgres_regex ⇒ Object
213 214 215 |
# File 'lib/instrumentation/postgres.rb', line 213 def generated_postgres_regex @generated_postgres_regex ||= Regexp.union(PostgresExtension::POSTGRES_COMPONENTS.map { |component| PostgresExtension::COMPONENTS_REGEX_MAP[component] }) end |
#lru_cache ⇒ Object
150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/instrumentation/postgres.rb', line 150 def lru_cache # When SQL is being sanitized, we know that this cache will # never be more than 50 entries * 2000 characters (so, presumably # 100k bytes - or 97k). When not sanitizing SQL, then this cache # could grow much larger - but the small cache size should otherwise # help contain memory growth. The intended use here is to cache # prepared SQL statements, so that we can attach a reasonable # `db.sql.statement` value to spans when those prepared statements # are executed later on. @lru_cache ||= LruCache.new(50) end |
#span_attrs(kind, *args) ⇒ Object
Rubocop is complaining about 19.31/18 for Metrics/AbcSize. But, getting that metric in line would force us over the module size limit! We can’t win here unless we want to start abstracting things into a million pieces.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/instrumentation/postgres.rb', line 166 def span_attrs(kind, *args) # rubocop:disable Metrics/AbcSize if kind == :query operation = extract_operation(args[0]) sql = args[0] else statement_name = args[0] if kind == :prepare sql = args[1] lru_cache[statement_name] = sql operation = 'PREPARE' else sql = lru_cache[statement_name] operation = 'EXECUTE' end end attrs = { 'db.operation' => validated_operation(operation), 'db.postgresql.prepared_statement_name' => statement_name } attrs['db.statement'] = sql if config[:epsagon][:metadata_only] == false attrs['db.sql.table'] = table_name(sql) attrs.reject! { |_, v| v.nil? } [database_name, client_attributes.merge(attrs)] end |
#table_name(sql) ⇒ Object
191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/instrumentation/postgres.rb', line 191 def table_name(sql) return '' if sql.nil? parsed_query = PgQuery.parse(sql) if parsed_query.tables.length == 0 '' else parsed_query.tables[0] end rescue PgQuery::ParseError '' end |
#tracer ⇒ Object
146 147 148 |
# File 'lib/instrumentation/postgres.rb', line 146 def tracer EpsagonPostgresInstrumentation.instance.tracer end |
#transport_attrs ⇒ Object
233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/instrumentation/postgres.rb', line 233 def transport_attrs if conninfo_hash[:host]&.start_with?('/') { 'net.transport' => 'Unix' } else { 'net.transport' => 'IP.TCP', 'net.peer.ip' => conninfo_hash[:hostaddr]&.to_s, 'net.peer.port' => conninfo_hash[:port]&.to_s } end end |
#validated_operation(operation) ⇒ Object
204 205 206 |
# File 'lib/instrumentation/postgres.rb', line 204 def validated_operation(operation) operation if PostgresExtension::SQL_COMMANDS.include?(operation) end |