On 10/12/16 10:44 AM, Mike Bayer wrote:
On 10/12/2016 02:18 AM, Mike Bayer wrote:
In this case I'd in the simple case just add "threadid" to the log
format
(https://docs.python.org/2/library/logging.html#logrecord-attributes)
and use grep against the stream. More ambitiously I'd write a simple
logging.Filter that only allows messages from a certain thread (some
example filters such as
https://docs.python.org/2/howto/logging-cookbook.html#using-filters-to-impart-contextual-information).
here is the filter:
import logging
import threading
class MyFilter(logging.Filter):
def __init__(self):
self.flags = threading.local()
def log_sql(self):
self.flags.log = True
def unlog_sql(self):
del self.flags.log
def filter(self, record):
return getattr(self.flags, 'log', False)
here's how to stick it on an Engine, note using engine.logger instead
of hardcoding "sqlalchemy.engine.base.Engine", using the filter on
loggers instead of handlers means it must be applied directly:
my_filter = MyFilter()
engine.logger.setLevel(logging.INFO)
engine.logger.addFilter(my_filter)
"engine.logger" is not documented but is not an underscored attribute
and I will gladly document this as a public attribute. The filter can
also be put on the handler in use.
Setting logger.setLevel() back to logging.WARN will cause all
subsequent connections to revert to sending no messages to the logger
and the filter will remain un-invoked at that point. The filter
itself can also be removed using engine.logger.removeFilter(my_filter).
Using normal Python logging patterns is preferable to adding new flags
and behaviors to SQLAlchemy's "echo" feature. Adding per-connection,
detectable-during-change "echo" flags would add complexity and testing
burden to the SQLAlchemy project, as this would mean special code and
conditionals would need to be added to Connection's logging logic that
must be guaranteed to behave in a very specific way to suit this
specific use case.
here is a demo. hope this helps.
import logging
import threading
class MyFilter(logging.Filter):
def __init__(self):
self.flags = threading.local()
def log_sql(self):
self.flags.log = True
def unlog_sql(self):
del self.flags.log
def filter(self, record):
return getattr(self.flags, 'log', False)
if __name__ == '__main__':
from sqlalchemy import Integer, Column, create_engine, ForeignKey
from sqlalchemy.orm import relationship, Session
from sqlalchemy.ext.declarative import declarative_base
import time
Base = declarative_base()
class A(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
bs = relationship("B")
class B(Base):
__tablename__ = 'b'
id = Column(Integer, primary_key=True)
a_id = Column(ForeignKey('a.id'))
e = create_engine("postgresql://scott:tiger@localhost/test")
Base.metadata.drop_all(e)
Base.metadata.create_all(e)
def worker(log=False):
for j in range(5):
s = Session(e)
a1 = A(bs=[B(), B(), B()])
s.add(a1)
s.flush()
# note we are mid-transaction.
if log:
my_filter.log_sql()
for a in s.query(A).filter_by(id=a1.id):
a.bs
if log:
my_filter.unlog_sql()
s.commit()
time.sleep(.5)
my_filter = MyFilter()
logging.basicConfig(
format='%(thread)d %(message)s'
)
e.logger.setLevel(logging.INFO)
e.logger.addFilter(my_filter)
threads = [threading.Thread(
target=worker, args=(i == 6, )) for i in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
Mike,
Thank you, in fact thank you very, very much. Writing out the code was
really above and beyond my expectations. I will use this snippet in our
app.
The following problems remain for us (and also likely other users of
sqla) although they are not immediate issues:
1) I need to make sure that I can use "theading.local", since we are
based on flask we actually use werkzeug's threads, which I now have to
investigate if they are compatible with this snippet. Likewise anyone
using greenlets may have to modify the snippet to fit with greenlets.
That said, I do believe that porting this snippet to werkzeug's thread
primatives should insulate us from greenlet/native-threads problems, but
then those folks not using werkzeug don't have that option. This is why
overall it's a appears problematic to me.... however I've not confirmed
this yet, it's possible that "threading.local" works for most cases
(greenlets and werkzeug) but... I have to test.
2) It looks like to me that by using the engine's logger we would be
turning on logging across all threads. What happens when we have 1000
threads? What will happen to us performance wise?
So as a question, if I ever get to it, would you entertain patches to do
this without turning on global logging and/or being tied to the
threading implementation?
btw, if you're wondering where I'm coming from with these insane scaling
questions.... I used to be CTO of OKCupid and scaled them, now at a new
place, so these things matter to me and my team.
thanks again Mike and apologies for the tone of my original email!
-Alfred
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.