Skip to content

Commit

Permalink
L-2183 Ensure flushed logs in long running scripts (#22)
Browse files Browse the repository at this point in the history
- add options flushIntervalMs (default 5s) and throwExceptions (default false) to LogtailHandler
- change default of bufferLimit from 0 to 1000 and flushOnOverflow from false to true
- add LogtailHandlerBuilder
  • Loading branch information
PetrHeinz authored Jun 27, 2024
1 parent 91e4a5c commit e6be89f
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 17 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
push:
branches:
- master
- 2.x

jobs:
build:
Expand Down
6 changes: 3 additions & 3 deletions example-project/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Now it’s time to create a `Logger` instance and push a `LogtailHandler` handle

```php
$logger = new Logger("logtail-source");
$logger->pushHandler(new LogtailHandler("<source-token>"));
$logger->pushHandler(LogtailHandlerBuilder::withSourceToken("<source-token>")->build());
```

Don’t forget to change `<source-token>` to your actual token which you can find in the *Basic information* section when clicking on *Edit* on your select source.
Expand All @@ -69,11 +69,11 @@ Creating multiple loggers for different channels is fairly easy:
```php
# Logger for shopping cart component
$cart_logger = new Logger("shoping-cart");
$cart_logger->pushHandler(new LogtailHandler("<source-token>"));
$cart_logger->pushHandler(LogtailHandlerBuilder::withSourceToken("<source-token>")->build());

# Logger for payment component
$payment_logger = new Logger("payment");
$payment_logger->pushHandler(new LogtailHandler("<source-token>"));
$payment_logger->pushHandler(LogtailHandlerBuilder::withSourceToken("<source-token>")->build());
```

Then you can filter your logs using the following search formula:
Expand Down
9 changes: 7 additions & 2 deletions example-project/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# Setting logger
use Monolog\Logger;
use Logtail\Monolog\LogtailHandler;
use Logtail\Monolog\LogtailHandlerBuilder;

# Check for arguments
if($argc != 2){
Expand All @@ -17,7 +17,12 @@
}

$logger = new Logger("logtail-source");
$logger->pushHandler(new LogtailHandler($argv[1]));
$handler = LogtailHandlerBuilder::withSourceToken($argv[1])
->withBufferLimit(100)
->withFlushIntervalMilliseconds(500)
->withExceptionThrowing(true)
->build();
$logger->pushHandler($handler);

# Below you can see available methods that can be used to send logs to logtail.
# Each method corresponds to Monologs log level.
Expand Down
71 changes: 64 additions & 7 deletions src/Monolog/LogtailHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,84 @@
*/
class LogtailHandler extends BufferHandler
{
const DEFAULT_BUBBLE = true;
const DEFAULT_BUFFER_LIMIT = 1000;
const DEFAULT_FLUSH_ON_OVERFLOW = true;
const DEFAULT_FLUSH_INTERVAL_MILLISECONDS = 5000;

/**
* @var int|null $flushIntervalMs
*/
private $flushIntervalMs;

/**
* @var int|float|null highResolutionTimeOfNextFlush
*/
private $highResolutionTimeOfNextFlush;

/**
* @param string $sourceToken Logtail source token
* @param int|string $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param $endpoint Logtail ingesting endpoint
* @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param string $endpoint Logtail ingesting endpoint
* @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer
* @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
* @param int $connectionTimeoutMs The maximum time in milliseconds that you allow the connection phase to the server to take
* @param int $timeoutMs The maximum time in milliseconds that you allow a transfer operation to take
* @param int|null $flushIntervalMs The time in milliseconds after which next log record will trigger flushing all logs. Null to disable
* @param bool $throwExceptions Whether to throw exceptions when sending logs fails
*/
public function __construct(
$sourceToken,
$level = Logger::DEBUG,
$bubble = true,
$bubble = self::DEFAULT_BUBBLE,
$endpoint = LogtailClient::URL,
$bufferLimit = 0,
$flushOnOverflow = false,
$bufferLimit = self::DEFAULT_BUFFER_LIMIT,
$flushOnOverflow = self::DEFAULT_FLUSH_ON_OVERFLOW,
$connectionTimeoutMs = LogtailClient::DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS,
$timeoutMs = LogtailClient::DEFAULT_TIMEOUT_MILLISECONDS
$timeoutMs = LogtailClient::DEFAULT_TIMEOUT_MILLISECONDS,
$flushIntervalMs = self::DEFAULT_FLUSH_INTERVAL_MILLISECONDS,
$throwExceptions = SynchronousLogtailHandler::DEFAULT_THROW_EXCEPTION
) {
parent::__construct(new SynchronousLogtailHandler($sourceToken, $level, $bubble, $endpoint, $connectionTimeoutMs, $timeoutMs), $bufferLimit, $level, $bubble, $flushOnOverflow);
parent::__construct(new SynchronousLogtailHandler($sourceToken, $level, $bubble, $endpoint, $connectionTimeoutMs, $timeoutMs, $throwExceptions), $bufferLimit, $level, $bubble, $flushOnOverflow);
$this->flushIntervalMs = $flushIntervalMs;
$this->setHighResolutionTimeOfLastFlush();
}

/**
* @inheritDoc
*/
public function handle(array $record): bool
{
$return = parent::handle($record);

if ($this->highResolutionTimeOfNextFlush !== null && $this->highResolutionTimeOfNextFlush <= hrtime(true)) {
$this->flush();
$this->setHighResolutionTimeOfLastFlush();
}

return $return;
}

/**
* @inheritDoc
*/
public function flush(): void
{
parent::flush();
$this->setHighResolutionTimeOfLastFlush();
}

private function setHighResolutionTimeOfLastFlush(): void
{
$currentHighResolutionTime = hrtime(true);
if ($this->flushIntervalMs === null || $currentHighResolutionTime === false) {
$this->highResolutionTimeOfNextFlush = null;

return;
}

// hrtime(true) returns nanoseconds, converting flushIntervalMs from milliseconds to nanoseconds
$this->highResolutionTimeOfNextFlush = $currentHighResolutionTime + $this->flushIntervalMs * 1e+6;
}
}
180 changes: 180 additions & 0 deletions src/Monolog/LogtailHandlerBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?php declare(strict_types=1);

/*
* This file is part of the logtail/monolog-logtail package.
*
* (c) Better Stack
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Logtail\Monolog;

use Monolog\Logger;

final class LogtailHandlerBuilder
{
private $sourceToken;
private $level = Logger::DEBUG;
private $bubble = LogtailHandler::DEFAULT_BUBBLE;
private $endpoint = LogtailClient::URL;
private $bufferLimit = LogtailHandler::DEFAULT_BUFFER_LIMIT;
private $flushOnOverflow = LogtailHandler::DEFAULT_FLUSH_ON_OVERFLOW;
private $connectionTimeoutMs = LogtailClient::DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS;
private $timeoutMs = LogtailClient::DEFAULT_TIMEOUT_MILLISECONDS;
private $flushIntervalMs = LogtailHandler::DEFAULT_FLUSH_INTERVAL_MILLISECONDS;
private $throwExceptions = SynchronousLogtailHandler::DEFAULT_THROW_EXCEPTION;

/**
* @internal use {@see self::withSourceToken()} instead
*/
private function __construct($sourceToken)
{
$this->sourceToken = $sourceToken;
}

/**
* Builder for comfortable creation of {@see LogtailHandler}.
*
* @var string $sourceToken Your Better Stack source token.
* @see https://logs.betterstack.com/team/0/sources
* @return self Always returns new immutable instance
*/
public static function withSourceToken($sourceToken): self
{
return new self($sourceToken);
}

/**
* Sets the minimum logging level at which this handler will be triggered.
*
* @param int|string $level
* @return self Always returns new immutable instance
*/
public function withLevel($level): self
{
$clone = clone $this;
$clone->level = $level;

return $clone;
}

/**
* Sets whether the messages that are handled can bubble up the stack or not.
*
* @param bool $bubble
* @return self Always returns new immutable instance
*/
public function withLogBubbling($bubble): self
{
$clone = clone $this;
$clone->bubble = $bubble;

return $clone;
}

/**
* Sets how many entries should be buffered at most, beyond that the oldest items are flushed or removed from the buffer.
*
* @param int $bufferLimit
* @return self Always returns new immutable instance
*/
public function withBufferLimit($bufferLimit): self
{
$clone = clone $this;
$clone->bufferLimit = $bufferLimit;

return $clone;
}

/**
* Sets whether the buffer is flushed (true) or discarded (false) when the max size has been reached.
*
* @param bool $flushOnOverflow
* @return self Always returns new immutable instance
*/
public function withFlushOnOverflow($flushOnOverflow): self
{
$clone = clone $this;
$clone->flushOnOverflow = $flushOnOverflow;

return $clone;
}

/**
* Sets the maximum time in milliseconds that you allow the connection phase to the server to take.
*
* @param int $connectionTimeoutMs
* @return self Always returns new immutable instance
*/
public function withConnectionTimeoutMilliseconds($connectionTimeoutMs): self
{
$clone = clone $this;
$clone->connectionTimeoutMs = $connectionTimeoutMs;

return $clone;
}

/**
* Sets the maximum time in milliseconds that you allow a transfer operation to take.
*
* @param int $timeoutMs
* @return self Always returns new immutable instance
*/
public function withTimeoutMilliseconds($timeoutMs): self
{
$clone = clone $this;
$clone->timeoutMs = $timeoutMs;

return $clone;
}

/**
* Set the time in milliseconds after which next log record will trigger flushing all logs. Null to disable.
*
* @param int|null $flushIntervalMs
* @return self Always returns new immutable instance
*/
public function withAlwaysFlushingEveryMilliseconds($flushIntervalMs): self
{
$clone = clone $this;
$clone->flushIntervalMs = $flushIntervalMs;

return $clone;
}

/**
* Sets whether to throw exceptions when sending logs fails.
*
* @param bool $throwExceptions
* @return self Always returns new immutable instance
*/
public function withExceptionThrowing($throwExceptions): self
{
$clone = clone $this;
$clone->throwExceptions = $throwExceptions;

return $clone;
}

/**
* Builds the {@see LogtailHandler} instance based on the setting.
*
* @return LogtailHandler
*/
public function build(): LogtailHandler
{
return new LogtailHandler(
$this->sourceToken,
$this->level,
$this->bubble,
$this->endpoint,
$this->bufferLimit,
$this->flushOnOverflow,
$this->connectionTimeoutMs,
$this->timeoutMs,
$this->flushIntervalMs
);
}
}
Loading

0 comments on commit e6be89f

Please sign in to comment.