Logging and Exception Handling for Django by Ryan Sullivan

This video features Ryan Sullivan at DjangoCon US 2019 in San Diego, California, USA.

Logging and Exception Handling for Django by Ryan Sullivan
0:44:06
Published October 25, 2019
10,278 views
203 likes

DjangoCon 2019 - Logging and Exception Handling for Django by Ryan Sullivan

Logging is better than print(), but often the effort to set up and use Python logging is perceived to be impractical. In this session we'll review Python's logging API, explore handling exceptions using logging, and discuss various configurations available in Django.

This talk was presented at: https://2019.djangocon.us/talks/logging-and-exception-handling-for/

LINKS:
Follow Ryan Sullivan 👇
On Twitter: https://twitter.com/rgs258
Official homepage: https://www.linkedin.com/in/rgs258

Follow DjangCon US 👇
https://twitter.com/djangocon

Follow DEFNA 👇
https://twitter.com/defnado
https://www.defna.org/

Intro music: "This Is How We Quirk It" by Avocado Junkie.
Video production by Confreaks TV.
Captions by White Coat Captioning.

Summary

Logging is worth using instead of print because it separates messages from code, supports severity levels, preserves useful runtime context, and lets developers troubleshoot production systems without editing the application. Ryan Sullivan explains Django and Python’s logging configuration, including loggers, hierarchical names, handlers, formatters, filters, propagation, rotating file handlers, and custom formatters that add data such as the hostname. He also covers raising, catching, logging, re-raising, and chaining exceptions, advises allowing Django to handle exceptions when appropriate, and shows how custom exception hierarchies and a global exception hook can capture failures outside view processing, such as crashed management commands.

Key takeaways

  • Use logging levels such as debug, info, warning, error, and critical so messages can be enabled or suppressed without changing code.
  • Create module-specific loggers with logging.getLogger(__name__) to mirror the Python package hierarchy and configure them through Django’s LOGGING dictionary.
  • Handlers route records to destinations such as the console, rotating files, email, or aggregation services; formatters control their output and filters provide finer-grained selection.
  • logger.exception() records exception details and traceback information, while re-raising with context preserves the original failure and adds useful guidance.
  • Do not catch every exception indiscriminately: handle cases you know how to resolve and let Django produce suitable responses such as 404 or 500 pages for the rest.
  • Install a custom sys.excepthook when failures can occur outside view handling, so uncaught exceptions from management commands can be logged and reported.

Summarised automatically from the transcript.

Chapters

  1. 0:00 Introduction and Logging Goals Ryan Sullivan introduces the talk and outlines logging, exception handling, and Django configuration.
  2. 5:01 Benefits of Logging The talk explains why logging is preferable to print statements and how it helps with debugging, documentation, and production troubleshooting.
  3. 7:23 Django Logging Configuration Django’s default loggers, Python’s root logger, and the dictionary-based configuration used in settings are introduced.
  4. 10:30 Persistent Log Files The speaker shows how to configure rotating file handlers and replace print statements with controllable log messages.
  5. 12:50 Python Logging Framework Log records, logging levels, and the flow of messages through Python’s logging system are explained.
  6. 16:00 Loggers and Hierarchies The talk covers named loggers, module-based logger names, hierarchical namespaces, and propagation.
  7. 18:21 Handlers and Filters Handlers, handler levels, and filters are used to control where log records go and which messages are emitted.
  8. 21:28 Custom Formatters The speaker demonstrates custom formatters, including adding a hostname to log records and output.
  9. 22:59 Raising and Handling Exceptions Django’s exception behavior is illustrated through raising, catching, logging, re-raising, and allowing exceptions to reach Django’s handler.
  10. 28:24 Exception Design and Chaining The talk discusses avoiding invisible exception handling, defining exception hierarchies, and adding context when re-raising errors.
  11. 31:29 Third-Party Logging and Exception Hooks Examples cover logging Django ORM SQL, aggregating logs with external services, and capturing uncaught exceptions outside request handling.
  12. 36:03 Recommendations and Resources Ryan summarizes practical next steps and points to Python, Django, and related logging resources.
  13. 38:06 Questions The speaker answers questions about debug logs, testing, runtime configuration, and placement of exception hooks.

Transcript

7,145 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:15

Speaker 1: Uh thank you. Thank you, Russ. Welcome everybody to logging and exception handling for Django. I'm Ryan. I'm super excited to be here at DjangoCon US 2019. First, I just want to say thank you to the organizers for the opportunity to speak here today. I also wouldn't be doing my job if I didn't thank my employer, the Wharton School of Business at the University of Pennsylvania, specifically my department, Wharton Research Data Services, who sent me here to San Diego. If you'd like to know more about words, specifically our API that exposes 60,000 endpoints and one and a half petabytes of data, my colleague Tim is giving a talk on just that subject right next door. It's being recorded, I hear, and you can watch it after the conference.

1:03

Speaker 1: It's a great presentation. I've heard it before, and I recommend everybody check it out. Thank you for attending. Logging has been around for more than a million years. And like writing tests, we all know that we should be doing it. Many of us still don't. It is true it's easier to write print than it is to write logging. It's actually six more keystrokes to write logger. info than it is to write print. I think that it's worth the effort and I'm here to try to convince you that it is as well. I'll talk for about 35 minutes. I'll take five minutes of questions and uh hopefully release you five minutes early. We'll see if I can stick to that So I'm here to talk to you about what I've learned about Django. Uh sorry, about logging.

1:50

Speaker 1: Um we'll talk about configuring logging. How we import the logging module and get a logger, gain access to the logger, placing messages into the logger, for example using logger. info, logger. warning, logger. exception. We then will try some code, uh raise some exceptions, and accept them and see how we can handle those. And we'll talk a little bit about formatting and how handlers can receive messages from loggers and direct them. If you're wondering if you're in the right place, hopefully this session is for everybody. I'll discuss logging for Django and Python at a More entry level at first, and then we'll go into a dive into the logging framework for Python, uh

2:40

Speaker 1: some exception handling, and then we'll tie it together with some next steps and recommendations So in a former life I worked for Urban Outfitters and I sort of did their feeds. I was responsible for um generating feeds and then receiving feeds from our from our partners and we had a bunch of uh Java classes that just processed feeds and we would cron them. and I would use Bash uh to try and rotate logs and I never knew what was happening in my feeds and my God classes the as they were. And uh so one day I said I need something better. So I said, you know, I'm just gonna learn log per j And I uh so I printed the documentation

3:26

Speaker 1: because back then I printed things and I said I'm gonna read it while I'm traveling, while I'm sitting still and have nothing else to do. And so I started reading this documentation and I got so engrossed in it that I actually accidentally left my suitcase on the platform of San Francisco's regional rail system. I was really happy that it was still there when I got back, but The point is that it's the the documentation is for log for J was actually great and Python and Django's logging for the documentation is also really engaging. You can actually really get in there if if you once you start. So I highly recommend it. I've been doing software for 16 years. I started with Java. I then did Cold Fusion, that's Tim's fault. You can blame him for it.

4:12

Speaker 1: I'm now doing Django on Python. That's Tim's fault. We can all thank him for that. And now I'm a web developer at Words, team lead, VP of technology at a startup, which means I am a web developer team lead again. So some success criteria. By the end of this talk, I hope you'll all be familiar with the terms and mechanics of logging for Python, configuring logging in Python, and particularly with regard to Django, and placing messages into the l into the logger and outputting those messages. messages. Why don't we just log? Well, at first it's faster to just write print. The logging API is intimidating. We're busy people and nobody's paying us to log. If you work at Century, that's not true for you. Everybody else, nobody's paying us to log.

5:01

Speaker 1: So Logging can provide us with a tool to debug in development and can also give us an opportunity to enhance our documentation. It's not a replacement for documentation. I'm not giving you permission to do that But you can if you if we're writing our log messages into our code and then we're writing them at appropriate levels, we can turn off those messages Later on, and we can still have access to the ideas that we were thinking about as we were writing those those log statements so that hopefully we can know what we were thinking later on when we're looking at our code. It can also give us runtime information uh what's happening in production and uh it can give us flexibility into seeing what's happening in other people's packages. So if everybody plays nicely with the logging framework

5:49

Speaker 1: We can all see each other's messages later on without having to go too deeply into the code and instrument it too much. And then one of the things that I really like about logging is that it's one of my first troubleshooting steps. I think everybody here would agree that it's one of their first troubleshooting shooting steps. When we're in production, when something goes wrong, the first thing I do is just crank up logging as high as it'll go and look for what went wrong before I go into any of the code. And so if we're writing good log statements at the debug and info level, we will be able to see those in our own code. Let's just quickly cover some terms to level set. A package is a collection of modules, commonly a directory. A module is a Python file

6:36

Speaker 1: or something we can import. double underscore name double underscore is a variable that is a dotted namespace name of the module within which it is set and it is set by the importer. That's a lot to say. That's actually paraphrasing a lot as well. But the point here is that uh Python has modules and when we're interacting with a sort of a script, a program, a file, we're working with a module, and then all those modules are arranged into packages, which are basically the directories and the hierarchy of the of the application. And then a logger is an instance of a class and represents a logging channel. And a logging channel is something that we can write log messages to.

7:23

Speaker 1: And finally, an exception is an instance of the exception class or a subclass of it, and is the actual actualization of an exceptional event So first let's explore logging through a couple of examples. The first example is Django 's logging out of the box. So Django's logging out of the box gives us not a whole lot, but it gives us something, and it's it's we'll and we'll get into this. It does give us a Django logger and a Django. server logger at the info level, and this gives us a lot of information. about the request-response cycle during the running of the application, particularly run server when run server is running. That can be helpful for debugging.

8:08

Speaker 1: Python gives us a root logger by default, and that's set to warning. Django will also give us email admins at the error level And that'll be fired whenever debug is false. So when we're in production. But there's no file, there's n uh we can only see things at the console level. So here's a wall of text. This is my basic login configuration, what I like to do when I first start a project. And this adds my preferred format, which is wildly verbose because I have a wide screen. And then it also adds a root logger at the info level. So again, Python gives you a root logger out of the box, but I've chosen to specific explicitly configure a root logger here.

8:56

Speaker 1: That's there. Um, because I want my root logger at the info level. And then of course you can see that I'm redefining the Django logger and I'm redefining the Django server logger. I do this because I want to be able to configure the root logger and the Django logger at two different levels. I would like to sometimes put different handlers on each. logger and then I configure Django server because I don't really like what Django server Django does with Django server out of the box. I want all of those messages to just be propagated straight up to the Django logger and my configuration for it So why did this configuration get loaded and why is Python respecting it?

9:43

Speaker 1: The reason for that is that Django looks to settings. py or wherever you happen to configure Django for a variable named logging. And the variable names logging is a Python dictionary. And that Python dictionary is then, uh Python does some magic to it, but effectively then takes it and passes it to Python's dict config function on the logging module. That ships with Python. And that puts your configuration into effect. So you can configure Python logging in a couple of different ways. You can do it programmatically through the API, you can do it through a file, you can actually do it through a a port listener, a listener on a port, but Django does it this way and we'll do it this way.

10:30

Speaker 1: Next, um so what if we wanted to see the logging messages that we generate after the application receives starts. As Django is configured out of the box, we will lose all of those messages. They will just uh they'll they'll go to the console and then we'll restart the application and well, nice knowing you logs. So we can configure a file handler, and this is how we might configure a basic file handler. We are configuring a rotating file handler And we are giving the file handler handler a location where we want to configure, where we want those files to go. We'll give it a format, we'll give it our mode, we'll give it an encoding, a format, there it is.

11:15

Speaker 1: And then how many files we want to keep, how big we want those files to get. And this uh configuration here will rotate some log files for us and save them between restarts. And then all we really need to do is place that handler into our loggers in our different config and now we will get all of our log messages saved to a file that we can look at later on. And uh now we get to keep our fancy log messages. So we have to get messages into the logger. And we'll do that by uh well not this. Here we're just printing some stuff to the console. Um and this is what a lot of us might do. This is what I did for many years. Um actually I started out doing system.

12:01

Speaker 1: out. println and so this is a lot cleaner than that. But you can see here that we're formatting a date time into the print string and uh then we are and and This is what we've got. The only way to get rid of these print messages, these messages from the console later on, is to comment or delete them, which means editing the code. The formatting is in the code. The only formatting we'll ever receive is the timestamp where the messages were generated. And nobody else can turn them off. You're the only one that can turn them off because it's your code, unless somebody edits your code. So if we replace these with log messages, we have basically the same thing, except now we have two different levels. We have info and debug. And so if somebody doesn't want to see the debug messages during the running of the application, they can just raise the level of the logger to say that

12:50

Speaker 1: they don't want to see debug messages, in which case they can turn off those messages themselves. As well, I've taken the s the date time string out of the message and I'm leaving that to the formatter that we had configured earlier. And uh then I'm finding and replacing all my print statements with logger. info. Because why not? And now a short introduction to Python's logging framework. This is the dive part. So uh Here are some of the concepts behind the past couple of slides. So first is the log record. And the log record is an instance of something that's being logged. So the log record is created when you log a message or an exception.

13:39

Speaker 1: That's like calling logger. info, etc. And it's passed around within the logging framework until it's either handled by a handler or it is discarded. The log record has a number of attributes that are set when it's created and you can actually set more attributes on the log record. You can modify the log record as you see fit until it's ultimately used. And those attributes are some of them are described here. This is an abbreviated list. More are available. And you can use them when you're filtering, which is something we'll discuss in a moment, and then when you're formatting messages. So all of these attributes are available to you while you're formatting strings that are ultimately going to be output by your handlers.

14:25

Speaker 1: Your formatters will be set on your handlers and they will output messages in the formats that you describe, which will use these attributes. Let's talk about logging levels. This is one of the ones that is really fun but kind of terrifying. They're set um to allow you to control the granularity of log messages that are being output And you can set them on loggers, handlers, and filters. And they do a different thing when they're set on each of those. You also get to define the log level on the messages that you generate. So first let's talk about generating log messages When you call logger. info, you are generating a log message at the level info. And the the level info is a constant on the logging module that is 20.

15:11

Speaker 1: So if your logger is set to log level warning, then your log level, the logging level of your logger is at a higher level, 30, than the message info that you generated by calling logger. info. And that's why when we say that if your logger is set to warning, only messages of warning or higher will be output by that logger and your info messages will be discarded. If however your logger is set to level info and you log a message at level info by calling logger. info, you'll see that message in your logger, assuming it hasn't been filtered by your handler or your filters. There are four basic classes in the Python logging framework, and they are loggers, which are your interface to Python logging.

16:00

Speaker 1: And uh they allow you to call logger. info, logger. debug, etc. and place messages into the logger. Uh they 're named hierarchically and you get them by going to a logging convention, finding the most capable logger, inviting them to come work for you, and no, that's actually So, okay, now you get them by uh calling logging. git logger and passing them a string that represents the name of the logger that you'd like to receive. And so here we're using double underscore name, dunder name, which is the name of the module in which you are asking for the logger. And in this way, your logger is named the same as your module's name within the package, which gives you one logger per module

16:49

Speaker 1: and gives you a really nice hierarchy that matches your Python package hierarchy. um inside of your logging setup. So the logging hierarchy Each logger represents a logging channel and the hierarchy is represented by uh dot-separated namespace. I think I just muted that by accident. And again, so the hierarchy is represented by the dot -separated namespace. It's the same as Python packages. So this slide sort of uh exemplifies the logging hierarchy and shows that root is the low the highest level logger. It has no name. It just is the highest level logger and will always be

17:36

Speaker 1: Django is the second highest logger that we have in this configuration. And Django. server is within Django because there's one dot. Django. debub. db. backends is not within Django. server, but is within Django. And dot Backends is within DB is within Django. Another interesting thing to see here is the propagate flag. When propagate is set to true, which it is by default. any message that goes to this logger will be propagated to the logger above it. If I hadn't set propagate to false on Django, the Messages sent to the Django logger would also end up in the root logger.

18:21

Speaker 1: But because I like to configure the root logger and the Django logger separately so that I can have specific control over how Django's messages are output separate from how the root logger's messages are output, I've set the propagates a false. Handlers send log records, which are created by loggers, to their appropriate destination. And we've seen the console handler, the file handler, and the mail admins handler. Handlers can have levels and handlers by default have a level that is not set, which I did not show you on my levels slide, but Handlers with the level not set are at the level zero, which means that they will accept and handle every single message.

19:07

Speaker 1: If you set a level on a handler, and that level is higher than zero, only messages at or above that level will be handled by your handler. Generally you don't need to do that. Filters are tests to be performed on each log record and they allow you to have detailed control over what gets logged. This is a um, I forget what it's called, but it's filtering wood chips. It's the most wood logging appropriate slide I could find for, I don't know. I don't know. It's like 20 minutes of my life. I'll never get back. Filters filter out messages. So an example is that you could have a filter that filters out messages that have a particular word

19:54

Speaker 1: in the message, and you don't want mess those words to um uh mess logs log records that have those messages to be output. So this is an example of that. I've defined the something filter The something filter looks for the word something and the record's message, which is an attribute on the record, and the record represents the log record. And whenever it sees something in the message, it returns false. When filter returns false, then the filter will stop the message from processing and this something filter being set up as something filter will then cause the console handler to not write messages that have the word something in them. Another great example comes straight from Django, and this is Django's Require Debug False Filter.

20:42

Speaker 1: It is the It confuses me every time I look at it because what it says is that when debug is set to true, then it will return the opposite, which is false, which will be The filter returning false whenever debug is set to true, which says that mail admins requiring debug false will filter any message sent to this handler mail admins whenever debug is set to true. But this is an example of a filter that ships with Django. It's in all of your Django applications unless you've ripped it out. Formatters convert log messages to strings that use the attributes on the log records

21:28

Speaker 1: in order to make those strings for you. And so here's a slide that I added 20 minutes ago. This is how to make your own formatter. I love doing this because there's one attribute that I want in every log record that I don't get out of the box by Python because Python isn't a web framework. Django is. And I like to see the host name in my logs. So the first thing that I do is I define my own formatter Uh you can name it whatever you want. And then I just say whenever we call format, go ahead and try and find the hostname using get hostname. And if we can get the hostname, go ahead and put it on my record. There you go. Go ahead and put it on the log record with the name host

22:14

Speaker 1: word hostname. Otherwise, still give me the word host name and just say I couldn't find the host name. In this way, whenever I use this formatter, I will have the word hostname available to me to format into my messages so that I can see the hostname of the server that was running the code that generated this message. If you're running your Django application on more than one server, you might want to do this. So here's an example of a formatter, and you can see here that I'm using that host name. And if you don't have this slide in your code then or something like it, then this format string is going to throw an error every time code executes and uh tries to call to generate a log message because you don't won't have the host name attribute takes the format, applies as

22:59

Speaker 1: the format of a formatter using the host name adding formatter class, and then I've taken my formatter and I've put it into the console as the class, sorry, as the formatter for the console handler. We'll talk a little bit about exceptions. Raising an exception is to declare an exceptional event, often a failure. Raising an exception creates an instance of an exception or a subclass of exception and describes the type and value of the event. Exceptions are handled when an accept clause declares that it receives exceptions of the same type

23:45

Speaker 1: And exceptions pop propagate up the call stack until they either reach an accept clause that is capable of handling them or else they go unhandled. So here's an example of raising exceptions, and this comes from the Django tutorial. It comes from the polls app that we created when we learned to Django for the first time, possibly. And uh here, vote is looking for a question, and in order to get that question, it's calling the shortcut GitObject or 404. Get object or 404 is going to go and try and find a question and what doesn't find a question it's going to raise a HTTP 404 And an HTTP 404 is just a subclass of exception, and in this way we are raising an exception.

24:31

Speaker 1: What happens when we raise an exception? Oh, sorry, that's two slides from now. So handling an exception is to do something about it. The accept clause can stop the ex the exception from propagating. In this first example When the user can't be found, we create a new user and we log the event to debug. So this is to say that we can just see that something happened that we were expecting and choose to do something else about it. In the next example, the event is simply reported as an error and the user remains unset. That's going to become somebody else's problem in the next line of code. In this third example, We are calling logger.

25:17

Speaker 1: exception, which is almost the exact same as logger. error, except logger. exception adds the exception information to the message That is being created by the logger, and that allows the stack trace to be available on the log record, which would allow formatters that are capable of processing it to show you a stack trace. Console is a great example of that, that being the stream handler. If you call logger. exception, not only will you get this string, but you'll also get the stack trace that was generated when user. objects. get uh failed to find your user. And then finally we can choose to just re-raise the exception. Here we are raising another does not exist. exception and we're raising it from the exception so we don't lose any of the information that was generated when

26:04

Speaker 1: uh the first exception was generated. You could also just choose to not wrap this in a try-catch and then your exception, the exception generated by user objects git would bubble straight up to somebody else become somebody else's problem And that's frequently a good thing, particularly in the case of the 404. Unhandled exceptions are handled by Django 's exception handler And so when we choose not to try catch or we choose to raise an exception, Django's exception handler will pick those up for us so long as those exceptions are being thrown in the context of processing of view. There are probably other contexts in which this is true. It has a lot to do with middleware.

26:51

Speaker 1: That's some pretty technical stuff. But for all intents and for these intents, the intents and purposes of processing a view, if you choose not to handle an exception Django's exception handler will come up with a useful message and status code for your users and give it to them. So if you're in dev, that useful message might be a helpful error page with some yellow on top and a whole bunch of gray on the bottom that you can scroll through for days. If you're in prod it's a gray page saying something went wrong and a 500 message uh status code. And in the case of a HTTP 404 That would be a 404 page, whichever you've configured in your application, would be shown to your user, and the status code 404 would be given to their client. Because Django

27:37

Speaker 1: will help you in this way, you don't have to catch every single exception. Don't feel as though you need to You can accept you should catch exceptions that you want to and know how to handle and many of them such as when the question couldn't be found you can just let Django handle for you and it'll show your users a 404. This is a really fun slide and if there's time I want to get back to it. But this is why Django's exception handler actually works in the middleware. I hope we have time for that. Invisible exception handlers, just to talk about this anti-pattern for a minute. This deals this is the anti -pattern of dealing with exceptional code

28:24

Speaker 1: like it's business code. So business code is the you code you write in order to accomplish uh your goals. And if you choose to conflate the exceptional edge cases with your business code and just when you see something wrong, go ahead and write something to deal with it rather than throwing an exception or using uh the exception framework that's built into Python. It makes it very difficult for anybody or impossible for anybody to predict how your application might fail in production. It does nothing to document your code, so uh you know you won't really know why you're dealing with a situation or even that one might arise in the future when you're reading your code again. And it's not very dry.

29:09

Speaker 1: It uh doesn't follow the principles of do not repeat yourself. Instead, um you'll find yourself handling the same exceptions over and over again Here's a slide on making your own exceptions. I thought about taking this out, but I think it's a cool slide because it's uh taken directly from Python's LDAP3 library. Uh anybody here use LDAP? Yeah, that's fun, right? Uh when you use LDAP, the LDAP3 library, you'll end up with um LDAP exceptions. LDAP exception errors and LDAP configuration errors amongst a whole bunch of different exceptions. But one of the nice things about the LDAP3 library is that because LDAP is this wildly obtuse protocol

29:55

Speaker 1: and it can fail in so many amazing different ways. The LDAP3 library has defined a great exception hierarchy so that you can specifically handle the exceptions that you're interested in. While some frameworks might only throw messages at the exception level that are at their outer exception level. Hopefully every ex every pipe every library you use has at least one root of root exception that extends exception. So you don't just have to catch exception. You don't want to have to just accept exception. But you you really might want to be able to handle an exception at a lower level. So specifically you might want to handle a configuration error rather than having to just handle the exception. And uh Python's LDAP3

30:40

Speaker 1: library allows you to do that. So kudos to them for uh for setting up such a nice hierarchy. And I recommend you doing this as well. I strive to do this in all of my projects. Stripe to re raising and chaining exceptions. So when we're handling exceptions, sometimes it's just best to add some additional context and then re-raise the exception. And uh this is a great example taken again directly from Django. Uh this is the manage. py file that uh most of us have seen before. And it does exactly that. It tries to import Django and when Django isn't installed or we are working on the wrong virtual uh uh uh virtual environment, then it gives us the error message and rather than just throwing the error message out at us, it actually raises,

31:29

Speaker 1: re-raises the import error with some very nice information for us so that we know exactly what to do about this. It says go ahead and activate your virtual environment. That reminder has been still helpful to me on a number of occasions. And knowing exactly why I see that that helpful message is great. So let's go through three more quick examples. First is third-party packages. This is something that you might find yourself wanting to do now that you are using logging. You might want to see the log messages generated by third-party packages. One of those packages is uh, well, Django, it's third party to you because you didn't write it. And Django has an ORM, which is great. Somebody in this room who's done a lot of work on that.

32:14

Speaker 1: Thank you to that guy. And seriously, thank you. I couldn't thank you enough. And you have access to all of the SQL that's generated by Django's ORM because the developers, the core developers, were thoughtful enough and kind enough to write into their documentation that if you turn up the log level of Django DB backends to debug, then all of the SQL that's generated by Django's ORM will be Dumped to whatever handlers you place on this logger. And here I've placed the console and file loggers. And so in this way, if you want to see the SQL that's being used to select or or

33:00

Speaker 1: update the database, et cetera, you can see all of that just by adding this to your locking configuration. Another great thing that you can do here is set up logging aggregation with something like rollbar, accessory, airbreak, elastic, whatever you should like. And this is nice because it gives you a web interface to all of your log files. And so you might do that by signing up for a rollbar account. Pip installing rollbar, adding a little bit of configuration. This would be some of that configuration. You would just configure rollbar, add rollbar's middleware. This is a fancy thing I like to do in um If you have multiple configuration files, uh you don't have to add rollbar to dev or to local.

33:46

Speaker 1: You might just want to add it to your production uh configuration and you can do that by just placing these two lines into however you configure production so that uh rollbar is activated and production around your middleware. And then you'll just add rollbar as a handler to your loggers. So here I'm adding it to the root logger and the Django logger. Django has a great exception handler. Uh Django's exception handler will not handle your exceptions that are processed outside of the context of uh generating a view, handling a view that is um coming up with a git response. And so if for example you write a manage. py command and something breaks in there, and all that's going to happen is your app is gonna

34:31

Speaker 1: your app is gonna crash. And the standard exception hook is going to take the the error message that was generated by the exception that's crashing your application and dump it to standard error. And you'll have access to that exception on your console or wherever you receive standard error, and that's it. I hope you're recording standard error, otherwise you might lose access to that forever. So one thing you can do is to set up an exception hook, and the exception hook is just any function that takes three arguments, and those arguments are the type, value, and traceback. And then you can do whatever you want with those. And what I like to do there is I like to call git logger, git a logger specifically named accept hook.

35:17

Speaker 1: This is just a name for a logger that I've defined. And then I log critical, I say uncaught exception, I say that I here's the exception information to the logger. And uh then I replace the sys. accept hook with this function. And then that way when the application crashes, if you have a root logger that is logging to mail admins, to rollbar, and to the file, then your logger will take the exception that caused your application to crash. And before it dies, hopefully, hopefully, um, but most of the time it will, put that error message onto the console as it usually would, also into the files so that you don't lose it forever, and also send you an email message And if rollbar is on there, you might also see that exception in rollbar.

36:03

Speaker 1: This is a great way to know why your application crashed. If you're if you have uh code and manage, you know, manage the pie commands that you're crawning, this is great. So some next steps. I hope that I, you know, this has been helpful. Immediately after this, you could go and configure a root logger. You could add a format that works for you. You could create a file handler if you want a file handler. You can add an accept hook at the bottom of your settings. py, that's where I like to add it. You can assign the variable logger by calling logging. getlogger dunder name at the top of all of your modules

36:50

Speaker 1: You could find and replace all of your print statements with logging. info and logging. debug, whichever you think is appropriate. And if you wanted to, you could create a root exception for your application. So these are some resources that I reviewed that I've uh used over the past three years. Python's logging documentation is fantastic. Django's logging documentation is also truly excellent. This is Django's base login configuration, and it's worth a read if you haven't read it before. And then finally, uh Peter. . . I actually wrote a blog post called Django Logging the Right Way a couple of years ago and it was the inspiration for me to learn a lot more about logging and uh for me to give this talk. So he knows a lot about logging and if you see him around the conference, um

37:37

Speaker 1: definitely recommend having it having a chat with him. He knows a great deal. Thank you for your time. You can get the examples from this talk at the URL. I also have the slides posted in the repo. Um and I'm happy to take any questions here. And if uh we ran out of time and you have more questions, feel free to find me in the hallway or send me an email. Thank you.

38:06

Speaker 2: Hi. When you're adding debug logs for uh fixing something where one might otherwise use print statements and Once you've completed that, how often do you remove those debug bug logs versus treat the new logs like code that should have been uh present before and uh leave them that they might be useful?

38:28

Speaker 1: Yeah, absolutely. So um sort of the way that I do that is, you know, as you're developing code, you might write you might write the code once and then you might rewrite it, you might rewrite it, and you'll find that some of your log messages just don't make sense anymore and you'll be removing them or updating over the course of writing your code. Once your code is good enough to ship, whatever log messages you have in there, whatever log statements you have are probably valuable later on as a reminder to you and useful to others. And nobody's going to run your code and you're not going to run your code in production with your logger set to the level debug. unless you really want to see those messages, in which case you'll be thankful that they're there. So the answer that I would have for you is leave them. Don't remove them. That's the reason that we have levels to call on the logger.

39:14

Speaker 1: Just log them at level debug and then turn off debug and production. Thank you.

39:21

Speaker 3: Hi. This is kind of a I guess a general question, but one issue I've had with logging is When I then have unit tests that are trying to check the logs, I end up in a bit of a vortex , partly because the way tests can be run sometimes messes with the logging structure. Do you have any suggestions on that if that sounds familiar to you?

39:45

Speaker 1: I really wish I did have an answer and a suggestion for that. But um I'm going to admit to you that I've done not as much testing as I would have liked in my life. And I actually haven't run into any issues where the logger has gotten in the way of my test So I don't have an answer for you there. I would hope that you could change the level of your loggers uh in production. So, you know, if you were to do something like Um I'm just gonna scroll here So if you were to do something like this, then where you're saying that your handler is sorry, your level for your root logger and for your Django logger are going to be getting this uh environment variable.

40:34

Speaker 1: the root log level and the Django log level, hopefully you could just set a environment variable and set that all the way up to critical or something and that way you would just wouldn't see those log messages. But I don't know how that's going to actually interact between tests and logging so I don't have an answer to your question.

40:51

Speaker 4: I think I heard you say earlier that there was a way of uh setting up a configure for listening on a port. Does that mean you could actually change your logging level while the application is running?

41:03

Speaker 1: Yes, and I don't know how Yeah, so Jenk uh sorry the Python uh locking documentation makes very clear that you can you can set up listing on a port for for log configuration and you can do exactly that. And um I think it's really cool, but again I've never done it. So thank you for asking though. It's really useful to look into that.

41:25

Speaker 5: You showed that example with the accept hook and that little block of code that defined one. Where would you put that code? Um is it in settings. py or where do you

41:35

Speaker 1: guys want to try a live demo?

41:38

Speaker 5: Why yes I do.

41:41

Speaker 1: I have hopefully a great example of that for us right here. Let's see what happens. So I'm gonna deactivate. And then I'm gonna call manage. py And I'm going to get this message that says critical accept hook. uncaught exception and so this is uh sort of I've put my own accept hook in here and the reason that that works is that In this code that's hard to see.

42:27

Speaker 1: In this code that's hard to see Inside of manage. py. There we go. I created a I changed the format. I just change the default formatter to be a new formatter. And then I'm using that formatter uh well I'm I'm just replacing the accept hook with a lambda here saying that um I want and I to you know say uncaught exception So that's one place you could put it, and then the place that I

43:12

Speaker 1: and you wouldn't actually want to put it there, but I just think that that's a an interesting example. The place that I always put my accepts ha accept hooks. is right at the bottom of settings. py. I define the exception method, and then I just set the accept hook. And the reason I do it in settings. py is that that's always going to get loaded when your application runs. And so by placing it there, you know that your accept hook is going to be assigned as soon as Django fires up. So I think that answers the question. Great. Okay, we'll need to leave it there so we can get our next speaker set up. Could everyone thank Ryan again for his presentation. Thank you.

Questions this talk answers

How do I configure logging in Django?

Put a `LOGGING` dictionary in `settings.py` (or another Django configuration location). Django passes that dictionary to Python’s `logging.config.dictConfig`, which applies the logger, handler, formatter, and filter settings.

Discussed at 9:43

How can I save Django logs to a file between application restarts?

Configure a rotating file handler with a filename, format, encoding, maximum size, and backup count, then attach that handler to the relevant loggers. This preserves log messages in files and rotates them as they grow.

Discussed at 10:30

What is the difference between logging at debug, info, warning, and error levels?

A logger configured at a given level emits messages at that level or higher; for example, a logger set to `WARNING` discards `INFO` and `DEBUG` messages. This lets you leave useful debug statements in the code while turning them off in production.

Discussed at 15:11

How do I create a logger for a Python or Django module?

Call `logging.getLogger(__name__)` at the top of the module. Using the module’s dotted name gives each module a logger that mirrors the Python package hierarchy.

Discussed at 16:00

What does logger.exception do in Python?

`logger.exception` works much like `logger.error`, but also attaches the current exception information and traceback to the log record. This allows handlers such as the console handler to display the stack trace.

Discussed at 25:17

How does Django handle unhandled exceptions in a view?

Unhandled exceptions propagate to Django’s exception handler, which returns a detailed debugging page in development or a 500 response in production. An unhandled `Http404` produces the configured 404 page and a 404 status code, so views do not need to catch every exception.

Discussed at 26:04

How can I see the SQL generated by Django’s ORM?

Set the `django.db.backends` logger to `DEBUG` and attach the handlers where you want the output, such as the console or a file. Django will then send the ORM’s generated SQL to those handlers.

Discussed at 32:14

How can I log exceptions from Django code that runs outside a view, such as a management command?

Install a custom `sys.excepthook` that receives the exception type, value, and traceback, then logs the uncaught exception—often at `CRITICAL` level—through a configured logger. This can preserve the crash in files or send it to email and an error-monitoring service instead of leaving it only on standard error.

Discussed at 34:31

Should I remove debug log statements after fixing a problem?

Usually no. Once the code ships, useful log statements can serve as documentation and help with future troubleshooting; leave them at `DEBUG` level and disable that level in production unless needed.

Discussed at 38:28

Can Python logging configuration be changed while the application is running?

Yes. Python’s logging framework supports listening on a port for logging configuration, which can be used to change settings such as log levels at runtime, although the speaker had not personally used it.

Discussed at 41:03

Where should I put a custom exception hook in a Django project?

The speaker usually defines the exception-hook function and assigns `sys.excepthook` at the bottom of `settings.py`, because Django loads that file when the application starts. That ensures the hook is installed as Django starts up.

Discussed at 43:12

Presenters

Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.

More videos by Ryan Sullivan

More videos from DjangoCon US