New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace all uses of twisted.python.log with twisted.logger. #29
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,14 +5,20 @@ | |
|
||
from zope.interface import implementer | ||
|
||
from twisted import logger | ||
from twisted.internet import reactor, protocol, defer, error | ||
from twisted.python import log, reflect | ||
from twisted.python import reflect | ||
from twisted.protocols import amp | ||
from twisted.python import runtime | ||
from twisted.python.compat import set | ||
|
||
from ampoule import iampoule | ||
|
||
|
||
|
||
log = logger.Logger() | ||
|
||
|
||
gen = itertools.count() | ||
|
||
if runtime.platform.isWindows(): | ||
|
@@ -63,7 +69,7 @@ def signalProcess(self, signalID): | |
return self.transport.signalProcess(signalID) | ||
|
||
def connectionMade(self): | ||
log.msg("Subprocess %s started." % (self.name,)) | ||
log.info(u'Subprocess {n} started.', n=self.name) | ||
self.amp.makeConnection(self) | ||
|
||
# Transport | ||
|
@@ -94,10 +100,10 @@ def childDataReceived(self, childFD, data): | |
|
||
def errReceived(self, data): | ||
for line in data.strip().splitlines(): | ||
log.msg("FROM %s: %s" % (self.name, line)) | ||
log.error(u'FROM {n}: {l}', n=self.name, l=line) | ||
|
||
def processEnded(self, status): | ||
log.msg("Process: %s ended" % (self.name,)) | ||
log.info(u'Process: {n} ended', n=self.name) | ||
self.amp.connectionLost(status) | ||
if status.check(error.ProcessDone): | ||
self.finished.callback('') | ||
|
@@ -111,8 +117,15 @@ def main(reactor, ampChildPath): | |
from twisted.application import reactors | ||
reactors.installReactor(reactor) | ||
|
||
from twisted.python import log | ||
log.startLogging(sys.stderr) | ||
from twisted import logger | ||
observer = logger.textFileLogObserver(sys.stderr) | ||
logLevelPredicate = logger.LogLevelFilterPredicate( | ||
defaultLogLevel=logger.LogLevel.info | ||
) | ||
filteringObserver = logger.FilteringLogObserver( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for changing the bootstrapping as well! One interesting question might be how to configure the filters on this predicate without having to put in a whole custom There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good thought. I kind of hate that all this code lives in a big-old wad of text. I can vaguely imagine some hand-wavey thing where we take command-line arguments, or an ampoule.cfg file, and generate the bootstrap code from a template. Definitely bigger than the scope of this work, but it would have several benefits, including configuration of log levels and log formatters. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
same
or you know, parse a datastructure passed in |
||
observer, [logLevelPredicate] | ||
) | ||
logger.globalLogBeginner.beginLoggingTo([filteringObserver]) | ||
|
||
from twisted.internet import reactor, stdio | ||
from twisted.python import reflect, runtime | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,17 +9,23 @@ | |
count = functools.partial(next, itertools.count()) | ||
pop = heapq.heappop | ||
|
||
from twisted import logger | ||
from twisted.internet import defer, task, error | ||
from twisted.python import log | ||
|
||
from ampoule import commands, main | ||
|
||
|
||
|
||
log = logger.Logger() | ||
|
||
|
||
try: | ||
DIE = signal.SIGKILL | ||
except AttributeError: | ||
# Windows doesn't have SIGKILL, let's just use SIGTERM then | ||
DIE = signal.SIGTERM | ||
|
||
|
||
class ProcessPool(object): | ||
""" | ||
This class generalizes the functionality of a pool of | ||
|
@@ -135,17 +141,17 @@ def _addProcess(self, child, finished): | |
Adds the newly created child process to the pool. | ||
""" | ||
def fatal(reason, child): | ||
log.msg("FATAL: Process exited %s" % (reason,)) | ||
log.error(u'FATAL: Process exited.') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be a single log message as well. |
||
log.error(u'\t{r}', r=reason.getErrorMessage()) | ||
self._pruneProcess(child) | ||
|
||
def dieGently(data, child): | ||
log.msg("STOPPING: '%s'" % (data,)) | ||
log.info(u'STOPPING: {s}', s=data) | ||
self._pruneProcess(child) | ||
|
||
self.processes.add(child) | ||
self.ready.add(child) | ||
finished.addCallback(dieGently, child | ||
).addErrback(fatal, child) | ||
finished.addCallback(dieGently, child).addErrback(fatal, child) | ||
self._finishCallbacks[child] = finished | ||
self._lastUsage[child] = now() | ||
self._calls[child] = 0 | ||
|
@@ -377,15 +383,15 @@ def _cb(_): | |
return defer.DeferredList(l).addCallback(_cb) | ||
|
||
def dumpStats(self): | ||
log.msg("ProcessPool stats:") | ||
log.msg('\tworkers: %s' % len(self.processes)) | ||
log.msg('\ttimeout: %s' % (self.timeout)) | ||
log.msg('\tparent: %r' % (self.ampParent,)) | ||
log.msg('\tchild: %r' % (self.ampChild,)) | ||
log.msg('\tmax idle: %r' % (self.maxIdle,)) | ||
log.msg('\trecycle after: %r' % (self.recycleAfter,)) | ||
log.msg('\tProcessStarter:') | ||
log.msg('\t\t%r' % (self.starter,)) | ||
log.info(u'ProcessPool stats:') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you mind putting this together into one big log message, rather than spreading it out across a whole bunch of different ones? Conceptually, what's happening here is a dump of stats at a particular moment in time; they should all be associated with each other. The repeated logging calls were just there trying to get the message to format in a particular way, which shouldn't be the log emitter's concern. |
||
log.info(u'\tworkers: {w}', w=len(self.processes)) | ||
log.info(u'\ttimeout: {t}', t=self.timeout) | ||
log.info(u'\tparent: {p}', p=self.ampParent) | ||
log.info(u'\tchild: {c}', c=self.ampChild) | ||
log.info(u'\tmax idle: {i}', i=self.maxIdle) | ||
log.info(u'\trecycle after: {r}', r=self.recycleAfter) | ||
log.info(u'\tProcessStarter:') | ||
log.info(u'\t\t{s}', s=self.starter) | ||
|
||
pp = None | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you put the
log
object ontoAMPChild
, and then doself.log.info
, this type of namespacing comes along for free. Is this a feature you knew about but didn't want to use, or just something that wasn't clearly documented enough?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Iog_system itself is undocumented at https://twistedmatrix.com/documents/current/core/howto/logger.html; I found it while digging around in the code looking for the equivalent of twisted.python.log.msg's "system" kwarg. twisted.logger looks as though this kind of thing should be handled by the "namespace" kwarg passed to Logger(); in my experiments, setting the namespace explicitly, e.g., "log = logger.Logger(namespace=u'foo')" doesn't always work. I didn't get to the bottom of that, as I found the "log_system" kwarg and it did exactly what I wanted.
Ideally, I think a logger should be constructed with the namespace argument, as the preferred way to do instantiate it, as opposed to embedding it as a class member and relying on automagical namespace configuration, but that's likely to be out of scope for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update: I tried the "self.log" approach, and it emits a more correct, but also more verbose namespace for messages, which I found very noisy.
versus
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@glyph any thoughts on this? I personally like the log_system approach, because it offers more control over what shows up in the log message's namespace, and also because it allows me to have exactly one instance of Logger for the module, whereas the self.log approach creates a new Logger per instance of the containing class. Maybe that's not a big deal, but if you are making a lot of instances, that's a lot of identical loggers logging exactly the same information and seems wasteful to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I definitely don't like the
log_system
approach 😄. As you've observed, it's not documented! It's the way the log system implements the namespacing feature, not a parameter that's supposed to be passed in directly.TempDirChild
is not a qualified name, so there's no way for someone to look at that message and figure out what module or source file it's come from. The design of the log system is that you should rely on automagical namespace configuration, unless you have a really good reason not to. Why is it that you want to avoid it? I can see wanting to avoid too muchLogger
construction, since objects are not cheap as they should be.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. Module-global is definitely OK by me, and Python is definitely slower than I'd like about creating objects. And in practice (i.e.: module-level) this is how I've mostly used it; I was mainly suggesting instance-level just because you were deriving the namespace from the class rather than the module.
For an application, you might be right in some cases. (However, at work we just completed a migration from manual namespace specification to automagical ) But ampoule isn't an application, and the purpose of a namespace is not for our reading pleasure but for people operating applications to filter on. So having a hierarchical identifier that separates it from the application is key.
Surprise! You found one. I run Ampoule in production and consume structured logs via Cloudwatch in AWS :). (In a previous job, I consumed structured logs in ElasticSearch. And I'm aware of at least 2 other Twisted deployments doing this with an ELK stack.)
One good wall deserves another :). But I think for the purpose of this code review, just "let's use a module-level thing" is a fine start.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@glyph thanks for the thoughtful response, I appreciate it. So, module-level logger instance for this PR, what about the use of the log_system kwarg? I ask because a module-level logger will not include the classname in its namespace.
Also, I decided to figure out how I got ahold of the log_system kwarg in the first place and it was in the docstring for formatEventAsClassicLogText:
...which reads to me like it is something I could meaningfully expect to use.
Further, eventAsText documents its use as well, using a copy of the same docstring, but still it is an expected value.
If using log_system is acceptable, then a single, module-level Logger instance can still provide class-level logging, e.g.,
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we really need to include the class name in the namespace at all, frankly, especially for this first cut; I'd just skip passing it entirely. I definitely wouldn't want to custom-construct one like this which mashes the log level in there as a string like that. So just
log.info(u'Shutdown message received, goodbye.')
Except maybe structured-format like a PID in there or something so we can identify who received it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@glyph I've updated to PR to remove the log_system kwarg and go with plain module-level logging for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not find any clear path forward to adding a PID to that shutdown message, so I skipped that task.