- logging scale-back; the echo_uow flag on Session is deprecated, and unit of work logging is now

class level like all the other logging.
- trimmed back the logging API, centralized class_logger() as the single point of configuration for
logging, removed per-instance logging checks from ORM.
- Engine and Pool logging remain at the instance level.  The modulus of "instance ids" has been upped
to 65535.  I'd like to remove the modulus altogether but I do see a couple of users each month
calling create_engine() on a per-request basis, an incorrect practice but I'd rather their applications
don't just run out of memory.
This commit is contained in:
Mike Bayer
2008-08-24 21:10:36 +00:00
parent 6f60e76883
commit 4c29ed71d0
13 changed files with 74 additions and 83 deletions
+3
View File
@@ -69,6 +69,9 @@ CHANGES
may now be a mix of lists and tuples. (Previously members
were always lists.)
- The `echo_uow` flag on `Session` is deprecated, and unit-of-work
logging is now application-level only, not per-session level.
- schema
- Added "sorted_tables" accessor to MetaData, which returns
Table objects sorted in order of dependency as a list.
+1 -1
View File
@@ -2664,7 +2664,7 @@ class MySQLSchemaReflector(object):
return self._re_keyexprs.findall(identifiers)
MySQLSchemaReflector.logger = log.class_logger(MySQLSchemaReflector)
log.class_logger(MySQLSchemaReflector)
class _MySQLIdentifierPreparer(compiler.IdentifierPreparer):
-2
View File
@@ -566,8 +566,6 @@ class OracleDialect(default.DefaultDialect):
table.append_constraint(schema.ForeignKeyConstraint(value[0], value[1], name=name))
OracleDialect.logger = log.class_logger(OracleDialect)
class _OuterJoinColumn(sql.ClauseElement):
__visit_name__ = 'outer_join_column'
def __init__(self, column):
+32 -35
View File
@@ -6,16 +6,13 @@
"""Logging control and utilities.
Provides a few functions used by instances to turn on/off their logging,
including support for the usual "echo" parameter.
Control of logging for SA can be performed from the regular python logging
module. The regular dotted module namespace is used, starting at
'sqlalchemy'. For class-level logging, the class name is appended, and for
instance-level logging, the hex id of the instance is appended.
'sqlalchemy'. For class-level logging, the class name is appended.
The "echo" keyword parameter which is available on some SA objects corresponds
to an instance-level logger for that instance.
The "echo" keyword parameter which is available on SQLA ``Engine``
and ``Pool`` objects corresponds to a logger specific to that
instance only.
E.g.::
@@ -26,11 +23,12 @@ is equivalent to::
import logging
logger = logging.getLogger('sqlalchemy.engine.Engine.%s' % hex(id(engine)))
logger.setLevel(logging.DEBUG)
"""
import logging
import sys
import weakref
rootlogger = logging.getLogger('sqlalchemy')
if rootlogger.level == logging.NOTSET:
@@ -48,27 +46,34 @@ def default_logging(name):
'%(asctime)s %(levelname)s %(name)s %(message)s'))
rootlogger.addHandler(handler)
def _get_instance_name(instance):
# since getLogger() does not have any way of removing logger objects from
# memory, instance logging displays the instance id as a modulus of 16 to
# prevent endless memory growth also speeds performance as logger
# initialization is apparently slow
return "%s.%s.0x..%s" % (instance.__class__.__module__,
instance.__class__.__name__,
hex(id(instance))[-2:])
def class_logger(cls):
return logging.getLogger(cls.__module__ + "." + cls.__name__)
def is_debug_enabled(logger):
return logger.isEnabledFor(logging.DEBUG)
def is_info_enabled(logger):
return logger.isEnabledFor(logging.INFO)
def class_logger(cls, enable=False):
logger = logging.getLogger(cls.__module__ + "." + cls.__name__)
if enable == 'debug':
logger.setLevel(logging.DEBUG)
elif enable == 'info':
logger.setLevel(logging.INFO)
cls._should_log_debug = logger.isEnabledFor(logging.DEBUG)
cls._should_log_info = logger.isEnabledFor(logging.INFO)
cls.logger = logger
def instance_logger(instance, echoflag=None):
"""create a logger for an instance.
Warning: this is an expensive call which also results in a permanent
increase in memory overhead for each call. Use only for
low-volume, long-time-spanning objects.
"""
# limit the number of loggers by chopping off the hex(id).
# many novice users unfortunately create an unlimited number
# of Engines in their applications which would otherwise
# cause the app to run out of memory.
name = "%s.%s.0x...%s" % (instance.__class__.__module__,
instance.__class__.__name__,
hex(id(instance))[-4:])
if echoflag is not None:
name = _get_instance_name(instance)
l = logging.getLogger(name)
if echoflag == 'debug':
default_logging(name)
@@ -78,15 +83,8 @@ def instance_logger(instance, echoflag=None):
l.setLevel(logging.INFO)
elif echoflag is False:
l.setLevel(logging.NOTSET)
def cleanup(ref):
# the id() of an instance may be reused again after the
# previous instance has been gc'ed. set a cleanup handler
# to remove logging config
logging.getLogger(name).setLevel(logging.NOTSET)
instance._logging_cleanup = weakref.ref(instance, cleanup)
else:
l = logging.getLogger(_get_instance_name(instance))
l = logging.getLogger(name)
instance._should_log_debug = l.isEnabledFor(logging.DEBUG)
instance._should_log_info = l.isEnabledFor(logging.INFO)
return l
@@ -95,7 +93,6 @@ class echo_property(object):
__doc__ = """\
When ``True``, enable log output for this element.
This has the effect of setting the Python logging level for the namespace
of this element's class and object reference. A value of boolean ``True``
indicates that the loglevel ``logging.INFO`` will be set for the logger,
+1 -1
View File
@@ -29,7 +29,7 @@ class DynaLoader(strategies.AbstractRelationLoader):
def create_row_processor(self, selectcontext, path, mapper, row, adapter):
return (None, None)
DynaLoader.logger = log.class_logger(DynaLoader)
log.class_logger(DynaLoader)
class DynamicAttributeImpl(attributes.AttributeImpl):
uses_objects = True
-5
View File
@@ -623,8 +623,6 @@ class PropertyOption(MapperOption):
self._process(query, False)
def _process(self, query, raiseerr):
if self._should_log_debug:
self.logger.debug("applying option to Query, property key '%s'" % self.key)
paths = self.__get_paths(query, raiseerr)
if paths:
self.process_query_property(query, paths)
@@ -703,9 +701,6 @@ class PropertyOption(MapperOption):
return l
PropertyOption.logger = log.class_logger(PropertyOption)
PropertyOption._should_log_debug = log.is_debug_enabled(PropertyOption.logger)
class AttributeExtension(object):
"""An abstract class which specifies `append`, `delete`, and `set`
event handlers to be attached to an object property.
+11 -14
View File
@@ -189,9 +189,6 @@ class Mapper(object):
self.compiled = False
self.__should_log_info = log.is_info_enabled(self.logger)
self.__should_log_debug = log.is_debug_enabled(self.logger)
self.__compile_inheritance()
self.__compile_extensions()
self.__compile_class()
@@ -202,11 +199,11 @@ class Mapper(object):
self.__log("constructed")
def __log(self, msg):
if self.__should_log_info:
if self._should_log_info:
self.logger.info("(" + self.class_.__name__ + "|" + (self.local_table and self.local_table.description or str(self.local_table)) + (self.non_primary and "|non-primary" or "") + ") " + msg)
def __log_debug(self, msg):
if self.__should_log_debug:
if self._should_log_debug:
self.logger.debug("(" + self.class_.__name__ + "|" + (self.local_table and self.local_table.description or str(self.local_table)) + (self.non_primary and "|non-primary" or "") + ") " + msg)
def _is_orphan(self, state):
@@ -1042,7 +1039,7 @@ class Mapper(object):
mapper which does not inherit from any other mapper.
"""
if self.__should_log_debug:
if self._should_log_debug:
self.__log_debug("_save_obj() start, " + (single and "non-batched" or "batched"))
# if batch=false, call _save_obj separately for each object
@@ -1080,7 +1077,7 @@ class Mapper(object):
existing = attributes.instance_state(instance)
if not uowtransaction.is_deleted(existing):
raise exc.FlushError("New instance %s with identity key %s conflicts with persistent instance %s" % (state_str(state), str(instance_key), state_str(existing)))
if self.__should_log_debug:
if self._should_log_debug:
self.__log_debug("detected row switch for identity %s. will update %s, remove %s from transaction" % (instance_key, state_str(state), state_str(existing)))
uowtransaction.set_row_switch(existing)
@@ -1102,7 +1099,7 @@ class Mapper(object):
pks = mapper._pks_by_table[table]
instance_key = mapper._identity_key_from_state(state)
if self.__should_log_debug:
if self._should_log_debug:
self.__log_debug("_save_obj() table '%s' instance %s identity %s" % (table.name, state_str(state), str(instance_key)))
isinsert = not instance_key in uowtransaction.session.identity_map and not postupdate and not has_identity
@@ -1119,7 +1116,7 @@ class Mapper(object):
if value is not None:
params[col.key] = value
elif mapper.polymorphic_on and mapper.polymorphic_on.shares_lineage(col):
if self.__should_log_debug:
if self._should_log_debug:
self.__log_debug("Using polymorphic identity '%s' for insert column '%s'" % (mapper.polymorphic_identity, col.key))
value = mapper.polymorphic_identity
if ((col.default is None and
@@ -1297,7 +1294,7 @@ class Mapper(object):
flush operation.
"""
if self.__should_log_debug:
if self._should_log_debug:
self.__log_debug("_delete_obj() start")
if 'connection_callable' in uowtransaction.mapper_flush_opts:
@@ -1479,7 +1476,7 @@ class Mapper(object):
instance = session_identity_map[identitykey]
state = attributes.instance_state(instance)
if self.__should_log_debug:
if self._should_log_debug:
self.__log_debug("_instance(): using existing instance %s identity %s" % (instance_str(instance), str(identitykey)))
isnew = state.runid != context.runid
@@ -1498,7 +1495,7 @@ class Mapper(object):
currentload = True
loaded_instance = False
else:
if self.__should_log_debug:
if self._should_log_debug:
self.__log_debug("_instance(): identity key %s not in session" % str(identitykey))
if self.allow_null_pks:
@@ -1527,7 +1524,7 @@ class Mapper(object):
else:
instance = self.class_manager.new_instance()
if self.__should_log_debug:
if self._should_log_debug:
self.__log_debug("_instance(): created new instance %s identity %s" % (instance_str(instance), str(identitykey)))
state = attributes.instance_state(instance)
@@ -1633,7 +1630,7 @@ class Mapper(object):
cond = sql.and_(*allconds)
return sql.select(tables, cond, use_labels=True)
Mapper.logger = log.class_logger(Mapper)
log.class_logger(Mapper)
def reconstructor(fn):
+5 -4
View File
@@ -96,7 +96,7 @@ class ColumnProperty(StrategizedProperty):
def __str__(self):
return str(self.parent.class_.__name__) + "." + self.key
ColumnProperty.logger = log.class_logger(ColumnProperty)
log.class_logger(ColumnProperty)
class CompositeProperty(ColumnProperty):
"""subclasses ColumnProperty to provide composite type support."""
@@ -204,7 +204,8 @@ class SynonymProperty(MapperProperty):
def merge(self, session, source, dest, dont_load, _recursive):
pass
SynonymProperty.logger = log.class_logger(SynonymProperty)
log.class_logger(SynonymProperty)
class ComparableProperty(MapperProperty):
"""Instruments a Python property for use in query expressions."""
@@ -735,7 +736,7 @@ class PropertyLoader(StrategizedProperty):
def _post_init(self):
if log.is_info_enabled(self.logger):
if self._should_log_info:
self.logger.info(str(self) + " setup primary join %s" % self.primaryjoin)
self.logger.info(str(self) + " setup secondary join %s" % self.secondaryjoin)
self.logger.info(str(self) + " synchronize pairs [%s]" % ",".join("(%s => %s)" % (l, r) for l, r in self.synchronize_pairs))
@@ -861,7 +862,7 @@ class PropertyLoader(StrategizedProperty):
if not self.viewonly:
self._dependency_processor.register_dependencies(uowcommit)
PropertyLoader.logger = log.class_logger(PropertyLoader)
log.class_logger(PropertyLoader)
class BackRef(object):
"""Attached to a PropertyLoader to indicate a complementary reverse relationship.
+1 -1
View File
@@ -1693,7 +1693,7 @@ class _ColumnEntity(_QueryEntity):
def __str__(self):
return str(self.column)
Query.logger = log.class_logger(Query)
log.class_logger(Query)
class QueryContext(object):
def __init__(self, query):
+9 -4
View File
@@ -135,8 +135,7 @@ def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False,
directly to the constructor for ``Session``.
echo_uow
When ``True``, configure Python logging to dump all unit-of-work
transactions. This is the equivalent of
Deprecated. Use
``logging.getLogger('sqlalchemy.orm.unitofwork').setLevel(logging.DEBUG)``.
extension
@@ -526,7 +525,7 @@ class Session(object):
def __init__(self, bind=None, autoflush=True, expire_on_commit=True,
_enable_transaction_accounting=True,
autocommit=False, twophase=False, echo_uow=False,
autocommit=False, twophase=False, echo_uow=None,
weak_identity_map=True, binds=None, extension=None, query_cls=query.Query):
"""Construct a new Session.
@@ -534,7 +533,13 @@ class Session(object):
[sqlalchemy.orm#sessionmaker()] function.
"""
self.echo_uow = echo_uow
if echo_uow is not None:
util.warn_deprecated(
"echo_uow is deprecated. "
"Use logging.getLogger('sqlalchemy.orm.unitofwork').setLevel(logging.DEBUG).")
log.class_logger(UOWTransaction, echo_uow)
if weak_identity_map:
self._identity_cls = identity.WeakInstanceDict
else:
+7 -10
View File
@@ -40,13 +40,12 @@ class DefaultColumnLoader(LoaderStrategy):
active_history=active_history
)
DefaultColumnLoader.logger = log.class_logger(DefaultColumnLoader)
log.class_logger(DefaultColumnLoader)
class ColumnLoader(DefaultColumnLoader):
def init(self):
self.columns = self.parent_property.columns
self._should_log_debug = log.is_debug_enabled(self.logger)
self.is_composite = hasattr(self.parent_property, 'composite_class')
def setup_query(self, context, entity, path, adapter, column_collection=None, **kwargs):
@@ -91,7 +90,7 @@ class ColumnLoader(DefaultColumnLoader):
self.logger.debug("%s deferring load" % self)
return (new_execute, None)
ColumnLoader.logger = log.class_logger(ColumnLoader)
log.class_logger(ColumnLoader)
class CompositeColumnLoader(ColumnLoader):
def init_class_attribute(self):
@@ -146,7 +145,7 @@ class CompositeColumnLoader(ColumnLoader):
return (new_execute, None)
CompositeColumnLoader.logger = log.class_logger(CompositeColumnLoader)
log.class_logger(CompositeColumnLoader)
class DeferredColumnLoader(DefaultColumnLoader):
"""Deferred column loader, a per-column or per-column-group lazy loader."""
@@ -176,7 +175,6 @@ class DeferredColumnLoader(DefaultColumnLoader):
raise NotImplementedError("Deferred loading for composite types not implemented yet")
self.columns = self.parent_property.columns
self.group = self.parent_property.group
self._should_log_debug = log.is_debug_enabled(self.logger)
def init_class_attribute(self):
self.is_class_level = True
@@ -213,7 +211,7 @@ class DeferredColumnLoader(DefaultColumnLoader):
def setup_loader(self, state, props=None, create_statement=None):
return LoadDeferredColumns(state, self.key, props)
DeferredColumnLoader.logger = log.class_logger(DeferredColumnLoader)
log.class_logger(DeferredColumnLoader)
class LoadDeferredColumns(object):
"""serializable loader object used by DeferredColumnLoader"""
@@ -289,7 +287,6 @@ class AbstractRelationLoader(LoaderStrategy):
def init(self):
for attr in ['mapper', 'target', 'table', 'uselist']:
setattr(self, attr, getattr(self.parent_property, attr))
self._should_log_debug = log.is_debug_enabled(self.logger)
def _init_instance_attribute(self, state, callable_=None):
if callable_:
@@ -335,7 +332,7 @@ class NoLoader(AbstractRelationLoader):
)
return (new_execute, None)
NoLoader.logger = log.class_logger(NoLoader)
log.class_logger(NoLoader)
class LazyLoader(AbstractRelationLoader):
def init(self):
@@ -485,7 +482,7 @@ class LazyLoader(AbstractRelationLoader):
return (lazywhere, bind_to_col, equated_columns)
_create_lazy_clause = classmethod(_create_lazy_clause)
LazyLoader.logger = log.class_logger(LazyLoader)
log.class_logger(LazyLoader)
class LoadLazyAttribute(object):
"""serializable loader object used by LazyLoader"""
@@ -743,7 +740,7 @@ class EagerLoader(AbstractRelationLoader):
self.logger.debug("%s degrading to lazy loader" % self)
return self.parent_property._get_strategy(LazyLoader).create_row_processor(context, path, mapper, row, adapter)
EagerLoader.logger = log.class_logger(EagerLoader)
log.class_logger(EagerLoader)
class EagerLazyOption(StrategizedOption):
def __init__(self, key, lazy=True, chained=False, mapper=None):
+3 -5
View File
@@ -85,8 +85,6 @@ def register_attribute(class_, key, *args, **kwargs):
return attributes.register_attribute(class_, key, *args, **kwargs)
class UOWTransaction(object):
"""Handles the details of organizing and executing transaction
tasks during a UnitOfWork object's flush() operation.
@@ -111,9 +109,7 @@ class UOWTransaction(object):
# dictionary used by external actors to store arbitrary state
# information.
self.attributes = {}
self.logger = log.instance_logger(self, echoflag=session.echo_uow)
def get_attribute_history(self, state, key, passive=True):
hashkey = ("history", state, key)
@@ -313,6 +309,8 @@ class UOWTransaction(object):
self.logger.debug("Dependency sort:\n"+ str(ret))
return ret
log.class_logger(UOWTransaction)
class UOWTask(object):
"""Represents all of the objects in the UOWTransaction which correspond to
a particular mapper.
+1 -1
View File
@@ -692,7 +692,7 @@ class GenerativeTest(TestBase, AssertsExecutionResults):
manager_mapper = mapper(Manager, managers, inherits=person_mapper, polymorphic_identity='manager')
car_mapper = mapper(Car, cars, properties= {'employee':relation(person_mapper), 'status':relation(status_mapper)})
session = create_session(echo_uow=False)
session = create_session()
active = Status(name="active")
dead = Status(name="dead")