Seldaek / monolog

Sends your logs to files, sockets, inboxes, databases and various web services

Home Page:https://seldaek.github.io/monolog/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

DOCS: either example is wrong or explanation of handler stack is unclear

php4fan opened this issue · comments

This is an issue in the docs.

From here: https://github.com/Seldaek/monolog/blob/main/doc/01-usage.md#core-concepts

Every Logger instance has a channel (name) and a stack of handlers. Whenever you add a record to the logger, it traverses the handler stack. Each handler decides whether it fully handled the record, and if so, the propagation of the record ends there.

Question: traverses the stack in what direction? Bottom to top or top to bottom? Note that the concepts of "bottom" and "top" or "up" and "down" in a stack are well-defined: what you add first is at the bottom, what you add last is at the top, that is by definition. In other words, "pushing" to the stack means adding at the top, "popping" means taking from the top.
Let's read on and see if the question is answered.

This allows for flexible logging setups, for example having a StreamHandler at the bottom of the stack that will log anything to disk, and on top of that add a MailHandler that will send emails only when an error message is logged.

Ok so, in the example, the StreamHandler is explicitly said to be at the bottom of the stack, and the MailHandler is said to be on top of it. My intuition would tend to suggest that the StreamHandler is called first, given that in this example it logs everything, and then it passes the message on to the "next" handler which is the MailHandler, which only handles a subset of the messages. So, this would mean that the stack is traversed from bottom to top.

However, then it continues:

Handlers also have a $bubble property which defines whether they block the record or not if they handled it.

<ASIDE>
Now first of all (and this is besides the point of this issue), when you say that there is a boolean property X that determines whether or not something, you should express it in such a way that when X is true, something is true. In this case, it is reversed. Obviously, $bubble=true means it bubbles, that is, it does not block the record. But I only know that because I am already familiar with the concept of bubbling (in other contexts) and I know that bubbling is the opposite of blocking (when you block something, you stop it from bubbling). If I only read $bubble as a variable name with no intrinsic meaning and were to deduce the behavior from that sentence, I would either get it wrong (i.e. $bubble=true means to block the message), or I would not know (as in "$block defines whether or not to block: which one is true and which one is false?").
</ASIDE>

Ok let's ignore that and get back to the issue.

I was saying, it continues:

Handlers also have a $bubble property which defines whether they block the record or not if they handled it.
In this example, setting the MailHandler's $bubble argument to false means that records handled by the MailHandler will not propagate to the StreamHandler anymore.

Now I am confused, because I was assuming that the stack was being traversed bottom-up, so, changing $bubble to false for the MailHandler wouldn't make a difference in this example (not as far as the two handlers mentioned are concerned).

So, assuming it's not an error in the docs, it must mean my previous assumptions were wrong.

Then the stack is traversed from the top down. Then it makes sense: the MailHandler is met first, and if its $bubble property is false, the message does not propagate to the StreamHandler below it.

But then it would mean that bubbles travel from the top down, which is counterintuitive as hell. In all the contexts where I've ever seen the concept of bubbling (e.g. event frameworks), bubbling is always conventionally pictured as going up, not down. That's why it's called bubbling, because bubbles in real life usually go upwards (at least air bubbles in a liquid).

So now I'm led to seriously question if you actually made a mistake somewhere and switched "top" with "bottom" or "StreamHandler" with "MailHandler" somewhere.
If not, then it means that the stack is traversed from the top down (which is not clearly stated and one has to deduce through an awful lot of reasoning), and that that the verb "to bubble" is used, counterintuitively, to depict the passing of message in the downwards direction.

Or it is an upside-down stack, one that grows downwards.

Note that I'm not being pedantic. I am genuinely not sure, at this stage, in which order I need to push the handlers into the stack to get the desired result.

The stack is indeed correctly built, and as per your expectations, which you can see here:

public function pushHandler(HandlerInterface $handler): self
{
array_unshift($this->handlers, $handler);
return $this;
}
/**
* Pops a handler from the stack
*
* @throws \LogicException If empty handler stack
*/
public function popHandler(): HandlerInterface
{
if (!$this->handlers) {
throw new \LogicException('You tried to pop from an empty handler stack.');
}
return array_shift($this->handlers);
}

The stack is then traversed top to bottom, as per this code:

public function addRecord(int $level, string $message, array $context = []): bool
{
$offset = 0;
$record = null;
foreach ($this->handlers as $handler) {
if (null === $record) {
// skip creating the record as long as no handler is going to handle it
if (!$handler->isHandling(['level' => $level])) {
continue;
}
$levelName = static::getLevelName($level);
$record = [
'message' => $message,
'context' => $context,
'level' => $level,
'level_name' => $levelName,
'channel' => $this->name,
'datetime' => new DateTimeImmutable($this->microsecondTimestamps, $this->timezone),
'extra' => [],
];
try {
foreach ($this->processors as $processor) {
$record = $processor($record);
}
} catch (Throwable $e) {
$this->handleException($e, $record);
return true;
}
}
// once the record exists, send it to all handlers as long as the bubbling chain is not interrupted
try {
if (true === $handler->handle($record)) {
break;
}

I am not entirely sure what lead you to believe the opposite:

Ok so, in the example, the StreamHandler is explicitly said to be at the bottom of the stack, and the MailHandler is said to be on top of it. My intuition would tend to suggest that the StreamHandler is called first, given that in this example it logs everything, and then it passes the message on to the "next" handler which is the MailHandler, which only handles a subset of the messages. So, this would mean that the stack is traversed from bottom to top.

You make a big assumption there with the fact that StreamHandler passes records to the next handler. The top level handlers all receive the records in order (top to bottom of stack) until bubbling is interrupted.

--

As for the bubbling down, instead of up.. well there I gotta say you got me, I really did not consider this nomenclature fuckup back then (I guess my mental representation is more one of bubbling to the next, rather than up per se) and I don't really see what can be done to fix it except documenting it better. I see your point about Handlers also have a $bubble property which defines whether they block the record being unclear as hell, but the reason I phrased it like that was that I am not sure how to otherwise explain bubbling to someone not familiar with the concept.

If you feel like sending a PR with doc improvements now that you hopefully grasp things better, that would be great.