def before_cursor_execute(conn, cursor, sql, parameters, context, executemany): data = dict( # TODO: Figure out how to retrieve the exact driver version. db_driver=conn.engine.driver, # Driver/framework centric information. db_framework='sqlalchemy:%s' % sqlalchemy.__version__, ) # Because sqlalchemy is a plain database connectivity module, # folks using it in a web framework such as flask will # use it in unison with flask but initialize the parts disjointly, # unlike Django which uses ORMs directly as part of the framework. data.update(get_flask_info()) # Filter down to just the requested attributes. data = {k: v for k, v in data.items() if attributes.get(k)} if with_opencensus: data.update(get_opencensus_values()) sql_comment = generate_sql_comment(**data) # TODO: Check if the database type is MySQL and figure out # if we should prepend comments because MySQL server truncates # logs greater than 1kB. # See: # * https://github.com/basecamp/marginalia/issues/61 # * https://github.com/basecamp/marginalia/pull/80 sql += sql_comment return sql, parameters
def test_opencensus_enabled(self): with mock_opencensus_tracer(): self.assertEqual( generate_sql_comment(with_opencensus=True), " /*traceparent='00-trace%%20id-span%%20id-00'," "tracestate='congo%%3Dt61rcWkgMzE%%2Crojo%%3D00f067aa0ba902b7'*/" )
def execute(self, sql, args=None): data = dict( # Psycopg2/framework information db_driver='psycopg2:%s' % psycopg2.__version__, dbapi_threadsafety=psycopg2.threadsafety, dbapi_level=psycopg2.apilevel, libpq_version=psycopg2.__libpq_version__, driver_paramstyle=psycopg2.paramstyle, ) # Because psycopg2 is a plain database connectivity module, # folks using it in a web framework such as flask will # use it in unison with flask but initialize the parts disjointly, # unlike Django which uses ORMs directly as part of the framework. data.update(get_flask_info()) # Filter down to just the requested attributes. data = {k: v for k, v in data.items() if attributes.get(k)} if with_opencensus: data.update(get_opencensus_values()) sql += generate_sql_comment(**data) return psycopg2.extensions.cursor.execute(self, sql, args)
def __call__(self, execute, sql, params, many, context): with_framework = getattr(django.conf.settings, 'SQLCOMMENTER_WITH_FRAMEWORK', True) with_controller = getattr(django.conf.settings, 'SQLCOMMENTER_WITH_CONTROLLER', True) with_route = getattr(django.conf.settings, 'SQLCOMMENTER_WITH_ROUTE', True) with_app_name = getattr(django.conf.settings, 'SQLCOMMENTER_WITH_APP_NAME', False) with_opencensus = getattr(django.conf.settings, 'SQLCOMMENTER_WITH_OPENCENSUS', False) with_opentelemetry = getattr(django.conf.settings, 'SQLCOMMENTER_WITH_OPENTELEMETRY', False) with_db_driver = getattr(django.conf.settings, 'SQLCOMMENTER_WITH_DB_DRIVER', False) if with_opencensus and with_opentelemetry: logger.warning( "SQLCOMMENTER_WITH_OPENCENSUS and SQLCOMMENTER_WITH_OPENTELEMETRY were enabled. " "Only use one to avoid unexpected behavior") db_driver = context['connection'].settings_dict.get('ENGINE', '') resolver_match = self.request.resolver_match sql_comment = generate_sql_comment( # Information about the controller. controller=resolver_match.view_name if resolver_match and with_controller else None, # route is the pattern that matched a request with a controller i.e. the regex # See https://docs.djangoproject.com/en/stable/ref/urlresolvers/#django.urls.ResolverMatch.route # getattr() because the attribute doesn't exist in Django < 2.2. route=getattr(resolver_match, 'route', None) if resolver_match and with_route else None, # app_name is the application namespace for the URL pattern that matches the URL. # See https://docs.djangoproject.com/en/stable/ref/urlresolvers/#django.urls.ResolverMatch.app_name app_name=(resolver_match.app_name or None) if resolver_match and with_app_name else None, # Framework centric information. framework=('django:%s' % django_version) if with_framework else None, # Information about the database and driver. db_driver=db_driver if with_db_driver else None, **get_opencensus_values() if with_opencensus else {}, **get_opentelemetry_values() if with_opentelemetry else {}) # TODO: MySQL truncates logs > 1024B so prepend comments # instead of statements, if the engine is MySQL. # See: # * https://github.com/basecamp/marginalia/issues/61 # * https://github.com/basecamp/marginalia/pull/80 sql += sql_comment # Add the query to the query log if debugging. if execute.__self__.__class__ is CursorDebugWrapper: execute.__self__.db.queries_log.append(sql) return execute(sql, params, many, context)
def test_opencensus_disabled(self): with mock_opencensus_tracer(): self.assertEqual(generate_sql_comment(with_opencensus=False), '')
def test_opencensus_not_available(self): self.assertEqual(generate_sql_comment(with_opencensus=True), '') self.assertEqual(generate_sql_comment(with_opencensus=False), '')
def test_url_quoting(self): self.assertIn("foo='bar%%2Cbaz'", generate_sql_comment(foo='bar,baz'))
def test_end_comment_escaping(self): self.assertIn(r'%%2A/abc', generate_sql_comment(a='*/abc'))
def test_no_args(self): # As per internal issue #28, we've commented out # the old defaults such as os.uname() information. self.assertEqual(generate_sql_comment(), '')
def test_bytes(self): self.assertIn("a='%%2A/abc'", generate_sql_comment(a=b'*/abc'))
def test_no_args(self): self.assertEqual(generate_sql_comment(), '')