Effortless Logging - Let the loggers work for you
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Number of Parts | 160 | |
Author | ||
License | CC Attribution - NonCommercial - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/33755 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
00:00
Core dumpHill differential equationSample (statistics)Personal digital assistantLoginConfiguration spaceGrand Unified TheoryDirection (geometry)Connectivity (graph theory)Human migrationSoftware developerOperator (mathematics)
00:42
Grand Unified TheoryPersonal digital assistantSample (statistics)LoginConfiguration spaceCASE <Informatik>CodeLoginSampling (statistics)Letterpress printingInformationBit rateSoftware developerPole (complex analysis)Bimodal distributionCartesian coordinate systemEndliche ModelltheorieBlock (periodic table)String (computer science)MultiplicationIntegrated development environmentJava appletElement (mathematics)Proper mapMultiplication signSlide ruleComputer animation
02:46
Row (database)InformationLoginLibrary (computing)Parameter (computer programming)Category of beingLine (geometry)Video game consoleString (computer science)Endliche ModelltheorieSystem callObject (grammar)Right angleNetwork socketEmailInstance (computer science)Physical lawLetterpress printingRow (database)Computer fileInformationComputer animationLecture/Conference
04:10
InformationRow (database)Digital filterDegree (graph theory)Structural loadInformationCodeSocial classTouchscreenRow (database)Endliche ModelltheorieEvent horizon2 (number)Category of beingObject (grammar)Filter <Stochastik>String (computer science)Computer fileFunctional (mathematics)Right angleWordCondition numberBlogComputer animationProgram flowchart
05:16
Digital filterHierarchySinc functionCondition numberString (computer science)Right angleFactory (trading post)Functional (mathematics)Default (computer science)Information1 (number)Insertion lossSigma-algebraCodeFilter <Stochastik>Row (database)File formatMereologyField (computer science)Inheritance (object-oriented programming)Meeting/InterviewProgram flowchart
06:33
Row (database)HierarchyInheritance (object-oriented programming)File formatDigital filterFilter <Stochastik>Sheaf (mathematics)Row (database)Physical lawMereologyAttribute grammarHierarchyDataflowInheritance (object-oriented programming)Computer animationProgram flowchartLecture/Conference
07:10
Digital filterRow (database)HierarchyInheritance (object-oriented programming)Abelian categoryLevel (video gaming)Inheritance (object-oriented programming)MereologyHierarchyCondition numberEndliche ModelltheorieComputer animationProgram flowchart
07:35
Inheritance (object-oriented programming)Category of beingCodeProgram flowchartComputer animation
07:59
InformationException handlingEndliche ModelltheorieProjective planeComputer filePhysical systemProduct (business)Computer animation
08:38
Exception handlingInformationParameter (computer programming)File formatKey (cryptography)Category of beingPhysical lawSoftware testingServer (computing)InformationError messageSoftware developerException handlingEndliche ModelltheorieGodDefault (computer science)Electronic mailing listBlogStack (abstract data type)File formatAttribute grammarProjective planeTemplate (C++)Computer fileHierarchyDifferent (Kate Ryan album)
09:59
File formatException handlingInformationError messageParameter (computer programming)Free variables and bound variablesString (computer science)Arithmetic meanType theoryComputer animation
10:24
Exception handlingInformationError messageException handlingMultiplication signString (computer science)Representation (politics)Error messageKey (cryptography)Computer animation
10:45
Exception handlingInformationError messageLoginConfiguration spaceAsynchronous Transfer ModeFile formatStreaming mediaTouchscreenBitSet (mathematics)4 (number)Limit (category theory)2 (number)LoginComputer fileDefault (computer science)Operator (mathematics)Point (geometry)Parameter (computer programming)Diffuser (automotive)Protein foldingComputer animation
11:24
Configuration spaceFile formatStreaming mediaConfiguration spaceLoginComputer fileStructural loadEndliche ModelltheorieObject (grammar)Linear regressionRevision controlNetwork topologyComputer configuration
12:13
Configuration spaceRevision controlVideo game consoleFile formatInformationLoginDemo (music)Type theoryLine (geometry)CodeConfiguration spaceBinary codeHoaxXMLComputer animation
13:05
Demo (music)Newton's law of universal gravitationInformationSoftware frameworkRevision controlComputer virusInheritance (object-oriented programming)IRIS-TRow (database)Demo (music)InformationString (computer science)Parameter (computer programming)Source codeLevel (video gaming)Right angleSoftware testingBlogLogarithmPolygonComputer animationLecture/Conference
14:12
InformationRevision controlDigital filterRow (database)Filter <Stochastik>Social classFunctional (mathematics)Multiplication signVideo gameDemo (music)
14:52
Demo (music)Inheritance (object-oriented programming)Sample (statistics)Personal digital assistantError messageSoftware bugCartesian coordinate systemInformationBlogMultiplication signWeb 2.0EmailComputer fileMultiplicationFile formatForm (programming)Right angleCASE <Informatik>
15:49
Personal digital assistantModule (mathematics)Process (computing)Thread (computing)Error messageDigital filterContext awarenessRow (database)Process (computing)CodeMultiplication signFactory (trading post)System callBlogDemo (music)Filter <Stochastik>Strategy gameContext awarenessConfiguration spaceComputer animation
16:10
Personal digital assistantDigital filterContext awarenessFilter <Stochastik>InformationField (computer science)FamilyGradient descentAreaRight angleContext awarenessBlog
16:49
Multiplication signPlastikkarteBuffer solutionCodeMereologyMathematicsError messageRight angleSystem callInheritance (object-oriented programming)Lecture/Conference
17:21
Newton's law of universal gravitationPersonal digital assistantBuffer solutionLoginInformationCASE <Informatik>BlogLetterpress printingFreewareError messageMultiplication signResultantOrder (biology)Operator (mathematics)Lecture/Conference
17:57
Personal digital assistantPerformance appraisalLoginHost Identity ProtocolObject (grammar)Functional (mathematics)String (computer science)LoginEndliche ModelltheorieMereologyPhysical systemGame theorySoftware bugContext awarenessBlogLibrary (computing)NumberWeightSound effectPattern language
19:23
IterationMiniDiscEndliche ModelltheorieBuffer solutionComputer fileBlogDivisorLoop (music)InformationEmailBasis <Mathematik>Lecture/Conference
20:40
BlogFunctional (mathematics)Product (business)CASE <Informatik>Data loggerInformationRight angleParameter (computer programming)
21:49
Computer fileVotingNormal (geometry)Process (computing)Lecture/Conference
22:16
BlogParameter (computer programming)View (database)Software developerComputer fileRight angleGodDifferent (Kate Ryan album)Variable (mathematics)Block (periodic table)Data structureShared memoryFluxScripting languageQuicksortGraph (mathematics)Process (computing)Bridging (networking)
23:15
Integrated development environmentMobile appReading (process)Query languageData loggerSpline (mathematics)Computer fileSign (mathematics)Lecture/Conference
24:05
Order (biology)Expected valueComputing platformLevel (video gaming)Multiplication signInheritance (object-oriented programming)File formatComputer fileSystem callSingle-precision floating-point formatMessage passingProcess (computing)Thread (computing)Object (grammar)Image resolution
Transcript: English(auto-generated)
00:04
Hello everyone. Well, first of all, thank you very much to your Python for hosting me. I think this is one of the hardest conferences, because you don't only have to compete with the rest of the speakers, but as well with the beach. I'm surprised to see I have that many people here.
00:20
So my name is Mario Corchero. I work at Bloomberg. I'm a Python developer, and I work in news automation. If you want to speak about my company or Spain, because I'm Spanish, or the Python Spain, or anything else, I'll hang around afterwards. So today we are going to speak about login. We'll have a brief introduction on why should you log, how login works, how can you use it, how do you configure it.
00:45
We'll try to do a little code demo, so I'm going to be jumping, going forth, and then some sample use cases that you can implement on top of login. And then we'll have some time, well, we'll try to have some time for Q&A. So first of all, well, we can do a full talk on why login matters, but in brief,
01:05
I see documentation as the information you give to the rest of the developers when they are coding. And I see login as the information that you give to the developers and the C-admin when your application is running. This is one of the things that we usually don't care that much about, right?
01:21
We put all kind of login trash, and then when we have an issue, we really wish that we had the proper logs. So, about why should you use login instead of, for example, just printing blah blah blah around all your code? First of all, well, some people actually say that if you have to use a debugger, that's a smell that you are not using login properly.
01:45
I don't know if you want to go with that stream, but using login instead of printing, it gives you a lot of benefits. It's much versatile and comfortable. It's really beautiful how it splits the how you log from the what you log.
02:02
And if you have a multi-threaded environment, it will work compared to print, which has some issues to it. I'm trying to come with all the notes I had in my slides, so bear with me. And there are more things, I promise, but you can check the slides afterwards. So, how will login work in Python?
02:22
Well, is there any Java developer in the room? You can put your hands. This is not the C++ conferences. We are not going to hit you or anything like that. We respect people. This is the Python environment. So, if you are familiar with Apache Log4j, the Python login model was built with that in mind, following that standard.
02:41
So, we are going to see all different elements on the login model. So, first of all, on the login library, we have loggers. Loggers are your main weapon to be able to log anything. They just allow you to pass a string and whatever arguments you have with a category. You know that this is going to call the Logger object, and some log line is going to go to a string, like to the console or a file, right?
03:09
I'm going really quickly because I don't know what's next. Okay, so, yeah, but it's not just like that. So, the login model is actually creating a login record, which is an object that is going to shuffle everything in it, and that object is actually what's going to be logged.
03:22
And you might be wondering, but okay, if that's an object, how is the object actually going to the console or to the file? So, that's where we use handlers, okay? So, we have seen LoggerSubject, like LoggerRecords, LoggerObjects, and now you are seeing HandlerHander, are the instances that are objects that allow you to print things to a file or to a console.
03:47
There are many loggers, there are many handlers already in the built-in login model. For example, you can log to a file, you can log to console, you can log via HTTP. You can send things to a socket, you can send them via email, and I know I'm missing one, it's in the notes, check the slides.
04:07
So, now we say, we are going to create the LoggerObject, we are going to call info on it, or whatever category you want to log on, we pass all the information, that's going to call the code that lives in the Logger class, which is going to create the LoggerRecord and it's going to pass it to the handler, which is going to do its magic,
04:23
it's going to emit it with its code, and it's going to transform it into, you know, it's going to put it into a file or into the screen. And you might wonder, but how does the handler know how to put all this beautiful LoggerRecord information into a file?
04:41
So, that's where we have formatters. Formatters are going to mix all the information in that we have in the LoggerRecord, and they are going to give you back a string. Okay, so we said, we have a LoggerObject, we call info or whatever category in it with all the information, that's going to go to the LoggerCode, the LoggerCode is going to create the LoggerRecord, the LoggerRecord is going to pass it to the handler, the handler will call its formatter, which will give you back a string,
05:04
and then you can pass it to a file or a function, whatever, right? So we know how Logger works. On top of all this, we have filters, I'm going to just spend 30 seconds of them, filters are a way to be able to, is a really flexible tool to be able to filter logs in some predefined conditions.
05:26
By default, I mean the default logger is not that much useful, but you can create your own ones, and it allows you to attach those ones to your loggers and to your handlers to filter out some loggers they do not want to log.
05:41
So, we said, we create the logger, we pass the information, it goes to the code of the logger, it checks the filter, if all the filters turn true, cool. We emit the log record that we just created, it goes to the handler, the handler also has some filters, if that passes, then we format the log record, we get back the string and we send it out to whatever we decide.
06:01
Great. We also have the log hierarchy. So what is the log hierarchy? We haven't seen so far how do we actually create the logger. All the loggers are defined with that factory function, which is called a getLogger, and it allows you to pass a string which is a dot-separated main convention. So for example, here we can see we're creating parent dot child,
06:23
and what that means is that here we are defining the child logger, which is a child of the parent logger, and so on, if you were to create the logger one. Okay, so now we have the final one, right? So we create the logger, we know how to, and now we log something, it goes to the logger code,
06:41
it goes through the filters, if it's all good, then we go to the handler section, which is we emit the log record, it goes to the handler, blah blah blah, but then also if the logger has an attribute called propagate, it will go to its parents and emit the log record, following again this same flow. Will it execute the filter code?
07:00
No. This is a great pitfall that I have fallen many times. So it will just execute this part of the parents. So what the hierarchy means is that once you have, when you log, when you call all your handlers, you're going to call the handlers of your parents. It's not that you're going to call your parent, it's just you're going to call the handlers of your parents.
07:21
Also another way that the hierarchy impacts is that if you don't set the level on the logger, it will use the parents one. Okay, so we know how logger works, right? More or less. There's just two more conditions. I promise this is the final one. So on top of that, loggers can be enabled or disabled, and you have the category, which is also on the handler,
07:41
but this is more or less the whole workflow. If you don't trust me, you can go to the documentation, which you have this other one, which is kind of the same. Okay, so we know how logger works, right? Yeah, but let's see how we use this huge implementation, this huge base code. So here we can see how can we log some stuff.
08:02
This is how you, as a developer, we just log things. This is the what you log, not the how you log it. I think this is... I love this way of separating the concern of how you log things from what you log things. You are usually fine separating them, but yeah. So we just import login, we get the logger, we use the name.
08:21
So you can use the name of the model, which it gets... So name is, it will give you the full path of the model that you're using. So for example, let's say you have project one, and then a folder called folder one, and then a file, file one. This will be project one, if there are Python models,
08:41
folder one, sorry, project one, folder one, file one. So it gives you, by default, a really nice and beautiful hierarchy that follows your file hierarchy. We log a debug, so we have different categories, debug info, error, and critical. We have a debug log that we just usually want to see
09:01
maybe in our tests, or maybe in our development server. We do actually some execution, we catch an exception, we do log exception, this is not a new category, this is just error, but passing X info, and X info is going to log all the information it has, like the traceback and all the information it has about the exception.
09:20
So we can do the same with other categories by just enforcing X info to be true. And if you're wondering, oh my god, I love this traceback thing, I want to see it as well in all the logs. From Python 3.2, I believe, don't quote me on that, you have this stack info attribute that you can pass when you log, which will also print all the stack.
09:41
Okay, cool. Now, some things I wished I knew when I started, things I should not do with login, is if you do this, even if it's debug, it's going to format and it's going to use, this has a computational cost of formatting the string, even if you don't use it, so just pass the template and then pass the argument that you want to put in the placeholders.
10:04
If you don't like this kind of string notation, the formatters have different types. I have ten minutes, or I have done ten minutes? Ten more? Wow. So really quickly, if you are doing this, this is, I don't know how many, sorry?
10:21
Okay, thank you, thank you. So if you're doing this, you probably will see errors like a terrible error has happened, data. I don't know how many times I've seen this in programs. So quite often, you are capturing an exception and logging that exception, but what is happening is logging the string representation of the exception,
10:41
which for key errors, is just the key that is missing, which is extremely frustrating. So what you probably want to do is pass X info, which we saw before, and you're going to see the full screen. I'm going to jump really quickly. We spoke a little bit about this. Now, we know how to use it, how do we configure it? Isn't it beautiful?
11:02
I'll leave you five seconds. This is how you can configure the whole login engine. Isn't it great? So this is the basic, this is one of the ways you can configure it. You have all those parameters. By default, it's going to pin to console, but if you pass a file name, it's going to point to a file.
11:20
You can go over all the parameters, but basically, this gives you the same defaults. If you don't like the same defaults, there are two other ways to configure the login model. Well, there are three. You can configure it via code, like just creating all the objects manually and wiring them. You can also configure it via a configuration file, which I don't like, so I won't show you.
11:40
And the third one is you can configure it using the dict config. So dict config allows you to pass a dictionary with all the options. It can get even higher than this. I don't know if you have seen the login configuration on Django. That's actually what you are doing behind the hoods. You are using the dict config. I usually use, I personally prefer dict config,
12:01
and I will put this config in a YAML file. I'll load the YAML file, and then I pass it to dict config. Cool. So here we can see, you can define under the config formatters, handlers, and loggers. I'm more into the documentation.
12:22
So, I want to show you that all I've said is true, and I have a demo. Yes, because there are not enough technical difficulties, right? So, and I have to type with one hand.
12:40
So, we are just going to see how a log line is going to go through all the code we explained already. So, we are just going to import logging. We do basic config. We get the logger, and we just print it. And this is my cheat sheet. So, we use Python 2.4.
13:00
I'm joking. Python 3, minus NPDB. Oh, you don't see that? You probably want to see it. There you are. So, uh, okay. Bum bum bum. Okay, so what's the first thing we see?
13:22
We are going to check if the logger is enabled for that level, right? It is, because we have it configured in info. And then we go to the inner function, underscore log, which has all the interesting stuff. It's getting the strike info, source file, bum bum bum, gets the caller info. And now we are creating this log record that we spoke about, right?
13:42
So, next, next. You can print with parentheses, because it's three, record. So, here you can see how this log record looks like. Okay, so it has all the information that can capture around it, and also the string and the parameters that we passed. We go next.
14:05
Have I gone inside? Sorry. Start again. Amazing. Don't do demo with a single hand. Bum bum bum bum bum bum. Next, next. No, no.
14:20
Don't do demo with a single hand. I told you, Mario. So, we go inside. Next, next, next, next, next. We create the logger, the log record here. Cool, and now we go into handle, which is not the handle class, but the handle method within the function. And what do we check if the logger is enabled or not? And we check all the filters,
14:41
because they return true, then we are going to call all the handlers. And I'm running out of time. This is really interesting. I recommend you do it afterwards. I'm going to put it in the slides, but there is still more content to go over, and we have five minutes. I promise you we'll go through all we spoke, but there is no time. And I forgot the clicker.
15:02
Okay. So, I see some use cases, because this might be more interesting. So, I'm going to jump over this one. Basically, you're going to find multiple handlers. Something I have on my web is I send to myself all the critical errors via email. I have a single file with all the error logs,
15:21
because I may want to see in three months what actually happened with the error logs, but for the info and the bug only available in dev, I will just rotate them every four days, for example. So, I keep only four day of logs. You can print JSON. Where would we do the JSON formatting? In the handler? No, in the formatter, right?
15:40
So, this is how you could configure your application to upload JSON, because, you know, you are on the hype, and you know that logs should not be human readable, and you prefer to send it to some kind of post-processing logs that will do a much better job if they take JSON. There is code. I'll try to upload it, and I don't have time to do the demo.
16:03
Using filters to add context. If you are over Python 3.2, there is now a factory method you can configure to capture all the calls whenever a log record is created, and you can add some extra contextual information. If you are before 3.3, some kind of convention is you can overuse the filters to add information.
16:21
So, what is this doing? This is using a nice and global scary stuff that is going to be passed to, like all the logs are going to pass through here, because we saw how the filters can be added to both loggers and handlers, and you can enrich it with something, and then put it in your formatter. Where would you put it? In the logger, or in the handler?
16:41
If you want to do it for all the logs? Not everyone answering at the same time, because I don't understand what you say. In the handler, we are right, because if you do it in the logger, it will only happen for the parent one, right? Because we saw that with the hierarchy, you're going to call all your handlers
17:00
and the handlers of your parents, but you are not going to execute the code of your parent, okay? More cool stuff, buffering. How many times do you have an error? You have logged an error, and you wish you had only changed that debug log to info, right? So what this smart buffer handler is going to do
17:22
is it's going to buffer the previous log, in this case one, and whenever it detects an error, it's going to not only log that error, but also the previous info. So you can do, for example, whenever there is an error, print the last 20 debug logs.
17:41
Do I have time for the demo? Okay, so then let's jump, in case you have questions. If you want to, let's say that you want to get only in-depth, sorry, you want to log the result of a function only in-depth, I don't know why you would do that, because you might have, for example,
18:02
what's called a hasten bug, which is a bug that only happens when you have your logs in debug. You can use this kind of pattern, which is that you create an object that will call your function only when you call the string. And that's it. Take away stop illegal logging in Amazonas. That was the talk about,
18:22
but not really. The logging model is, I think it's amazing. You can build on top of it as much as you want, and it's really, I love the way the what and the how is separated. It allows all your parts of the system to collaborate on logging. So for example,
18:41
you saw how we did before this thing of enriching the context of the logger. So something you can do, if you do that, it's not only that your logs are going to log that global scary stuff, it's that also the library that you call are going to log it. So you're really separating the what and the how.
19:01
And that's all. We have two minutes for questions. That's my uni pic. I have some questions. Really a huge applause for Mago Game because it was in the middle of everything.
19:21
So we have a question here. It seems that the logging model does a lot, as you said. What is the performance impact? So actually something really scary is that if for example, this is why I say that more or less you can split the what and the how. If you have sending an email,
19:41
it depends on the handler, but quite often everything happens synchronously. So it's really going to impact your performance. If you, for example, enable debug logs and you're debugging something in a for loop, it's really going to go to the buffer and then to the file for every single iteration.
20:02
So don't just ignore it. Be careful with logging. So it does have an impact, but haven't measured it. In normal performance, forget the debugging or something. Yeah, so it depends on what you log, right? So you can see, it depends on how much you log, but it does have an impact. How many milliseconds it's going to take,
20:21
it depends on, for example, if you're sending an email, it's going to take longer if you log to a file. How long it takes to log to a file, it depends on your disk. There is another question. What's your opinion about
20:40
to use decorators for logging? To use, like, this thing where you put decorators on a function and then it will log when it gets in and when it gets out with all the... Yeah, because I heard that it's a great use case for decorators, for logging, yeah. Yeah, I've seen that. It might be, I mean, depends on your use case.
21:04
I'm worried, you know, I personally prefer to write my own logs because the decorators, it feels more like I'm dumping this here, so then I'm going to get the information when I did it, whilst I really like to think about my logs. I really want to think on, you know,
21:20
I want the logs to tell me a story, right? I want to go to my log files and see, I am doing this. I want to see value, because when you do the decorator, it works great for today, but in six months, when you have a problem in production and you see this function name, which is do stuff, what was this thing doing? Right? You can still go to the code,
21:40
but if you can save the person, the sys admin, which might not have any idea what's going on, if you can save that step to him, that's really valuable. Thank you. Okay. Did you solve the problem with multiprocessing logging, because it's always an issue? Yeah, you cannot do it.
22:01
That's it. I mean, you would have to do like, you cannot use the normal, you cannot use something that is logging to a file. You cannot use a normal handler that will log to a file from two different processes, because they will share the file descriptor, right? Yes, but for stdout, for example.
22:24
The problem is they will, hmm, that might actually, meh, meh. I mean, I have had that issue before, and what I've done is, I would log via TCP to another process, which is actually going to collect all the login. Sorry?
22:43
Excuse me. Yeah, so a lot of developers put just everything into log info, right? So just dump different variables and parameters. So one of the approaches to solve this problem is to use struct logs. I don't know if you know the struct log.
23:00
Can you say that again? Struct log, so structured logging. So how is your view on solving this problem so that developer just put everything into it? Do you use structured logging, or... Yeah, so this fragment where I'm logging JSON, it actually comes from there. I was shuffling it,
23:20
and then you can push it afterwards and choose what you want to see, how you want to see it. The thing is, I usually, I'm still maybe old school using log files. Not that I like it, but my environment is what we have at home. At work, for example, we use things like Splunk and more convenient things.
23:41
So if you have the infrastructure to do, like, structured logging where you can log JSON and then parse it and accumulate it and whatever you want, go for it. If you are gonna do an app at home that you're gonna log files, or even your company doesn't have that, and you're still scraping through log files,
24:01
you really don't want to read JSON, right? But I think it's cool. I think, you know, what we do today as well is we log into a file, and then we parse it. If you can save that, it makes cool sense, yeah.
24:20
To your comment on, to your comment on, don't have a call that just says do stuff. So one best practice I heard was always have at least enough hierarchical naming that you can see what object. Sorry, I don't. To your comment that don't just have a logging message that says do stuff, a best practice I heard is preserve
24:41
at least enough levels of hierarchical naming on the object that you can unambiguously see what called and when. And I had a question for you. What is the guarantee, you can format, you can format millisecond resolution on timing, but what is your actual guarantee about the order and the accuracy, platform level accuracy of the timing and logging messages,
25:01
especially if it has to ripple up through multiple parents. So did you believe the millisecond timing? Did you ask if it guarantees that things will go to the file in the same order that you log? Was that the question? Supposedly it's guaranteed that in a single threaded process stuff is in order, but the millisecond timings may still be inaccurate.
25:21
So what's your level of expectation about the actual accuracy of the millisecond timings? Should you, what can you trust? Is it good to the nearest 10 millisecond or 100? Sorry, I'm not sure I understand the question. Did you ask, maybe the mic. I'll catch you off. Yeah, if you don't mind, you can talk with him later.
25:41
Okay, and thank you so much for everyone and another applause for Mario.