From 7fd93a79e28d466702038ba39dad7339461bb7d6 Mon Sep 17 00:00:00 2001 From: Joseph Szobody Date: Fri, 20 Feb 2026 17:18:59 -0500 Subject: [PATCH 1/2] Add withLog() for logging alongside metrics Adds Metric::withLog() to log a message when a metric is flushed. The log message is included in the metric's extra as 'message', and the log context includes the metric's extra plus an 'event' key. Also fixes PostHog to batch metrics like other drivers instead of sending immediately in add(), which broke fluent chaining like Metrics::create('event')->setExtra([...]). --- src/Drivers/AbstractDriver.php | 13 ++++++++ src/Drivers/CloudWatch.php | 2 ++ src/Drivers/InfluxDB.php | 2 ++ src/Drivers/LogDriver.php | 2 ++ src/Drivers/PostHog.php | 22 +++++--------- src/Drivers/PrometheusDriver.php | 2 ++ src/Metric.php | 30 ++++++++++++++++++- tests/MetricTest.php | 51 ++++++++++++++++++++++++++++++++ 8 files changed, 108 insertions(+), 16 deletions(-) diff --git a/src/Drivers/AbstractDriver.php b/src/Drivers/AbstractDriver.php index 8522c42..ec2218e 100644 --- a/src/Drivers/AbstractDriver.php +++ b/src/Drivers/AbstractDriver.php @@ -103,6 +103,19 @@ public function formatted(): mixed return null; } + protected function flushMetricLogs(): void + { + foreach ($this->metrics as $metric) { + if ($metric->getLogMessage()) { + app('log')->log( + $metric->getLogLevel(), + $metric->getLogMessage(), + array_merge(['event' => $metric->getName()], $metric->getExtra()) + ); + } + } + } + abstract public function format(Metric $metric); abstract public function flush(): static; diff --git a/src/Drivers/CloudWatch.php b/src/Drivers/CloudWatch.php index 72c1d39..ad7cec8 100644 --- a/src/Drivers/CloudWatch.php +++ b/src/Drivers/CloudWatch.php @@ -29,6 +29,8 @@ public function setClient(CloudWatchClient $client): void public function flush(): static { + $this->flushMetricLogs(); + if (!count($this->getMetrics())) { return $this; } diff --git a/src/Drivers/InfluxDB.php b/src/Drivers/InfluxDB.php index 56fee95..d3c24dc 100644 --- a/src/Drivers/InfluxDB.php +++ b/src/Drivers/InfluxDB.php @@ -53,6 +53,8 @@ public function point(IDBPoint|IDB2Point $point): static */ public function flush(): static { + $this->flushMetricLogs(); + if (empty($this->getMetrics())) { return $this; } diff --git a/src/Drivers/LogDriver.php b/src/Drivers/LogDriver.php index b35c000..634e8dd 100644 --- a/src/Drivers/LogDriver.php +++ b/src/Drivers/LogDriver.php @@ -26,6 +26,8 @@ public function format(Metric $metric): array public function flush(): static { + $this->flushMetricLogs(); + if (!count($this->getMetrics())) { return $this; } diff --git a/src/Drivers/PostHog.php b/src/Drivers/PostHog.php index 88c4a11..c24a2e1 100644 --- a/src/Drivers/PostHog.php +++ b/src/Drivers/PostHog.php @@ -12,24 +12,16 @@ public function __construct(protected string $distinctPrefix = '') { } - /** - * Note we are NOT enqueueing metrics on our own with PostHog. It queues internally - * and batches sends as it sees fit. Our best bet is to let PostHog do its thing. - * @throws Exception - */ - public function add(Metric $metric): static + public function flush(): static { - PostHogClient::capture($this->format($metric)); + $this->flushMetricLogs(); - return $this; - } + foreach ($this->getMetrics() as $metric) { + PostHogClient::capture($this->format($metric)); + } + + $this->metrics = []; - /** - * PostHog sends batches automatically on __destruct, this really isn't necessary. - * But we're including it in case you ever want to force send earlier on. - */ - public function flush(): static - { PostHogClient::flush(); return $this; diff --git a/src/Drivers/PrometheusDriver.php b/src/Drivers/PrometheusDriver.php index 3b57524..ac191bd 100644 --- a/src/Drivers/PrometheusDriver.php +++ b/src/Drivers/PrometheusDriver.php @@ -48,6 +48,8 @@ public function format(Metric $metric): Collector public function flush(): static { + $this->flushMetricLogs(); + $this->metrics = []; $this->registry->wipeStorage(); return $this; diff --git a/src/Metric.php b/src/Metric.php index 48c56e0..6f4ef4c 100644 --- a/src/Metric.php +++ b/src/Metric.php @@ -49,6 +49,10 @@ class Metric protected ?string $description = null; + protected ?string $logMessage = null; + + protected string $logLevel = 'info'; + /** * Metric constructor. * @@ -149,7 +153,13 @@ public function addTag($key, $value): static public function getExtra(): array { - return value($this->extra); + $extra = value($this->extra); + + if ($this->logMessage) { + $extra['message'] = $this->logMessage; + } + + return $extra; } public function setExtra(array|\Closure $extra): static @@ -167,6 +177,24 @@ public function addExtra($key, $value): static return $this; } + public function withLog(string $message, string $level = 'info'): static + { + $this->logMessage = $message; + $this->logLevel = $level; + + return $this; + } + + public function getLogMessage(): ?string + { + return $this->logMessage; + } + + public function getLogLevel(): string + { + return $this->logLevel; + } + public function getTimestamp(): mixed { return $this->timestamp; diff --git a/tests/MetricTest.php b/tests/MetricTest.php index 82124d4..8aa849f 100644 --- a/tests/MetricTest.php +++ b/tests/MetricTest.php @@ -84,6 +84,57 @@ public function testAddExtraAfterClosureSet() $this->assertEquals(['foo' => 'bar', 'baz' => 'qux'], $metric->getExtra()); } + public function testWithLogStoresMessageAndLevel() + { + $metric = new Metric("my_metric", 1); + $metric->withLog("Something happened", "warning"); + + $this->assertEquals("Something happened", $metric->getLogMessage()); + $this->assertEquals("warning", $metric->getLogLevel()); + } + + public function testWithLogDefaultsToInfo() + { + $metric = new Metric("my_metric", 1); + $metric->withLog("Something happened"); + + $this->assertEquals("info", $metric->getLogLevel()); + } + + public function testWithLogAddsMessageToExtra() + { + $metric = new Metric("my_metric", 1); + $metric->setExtra(['key' => 'value']); + $metric->withLog("Something happened"); + + $extra = $metric->getExtra(); + + $this->assertEquals('value', $extra['key']); + $this->assertEquals('Something happened', $extra['message']); + } + + public function testWithLogFlushesLog() + { + $this->setupInfluxDB(); + + $log = Mockery::mock(\Psr\Log\LoggerInterface::class); + $log->shouldReceive('log') + ->once() + ->with('info', 'Something happened', Mockery::on(function ($context) { + return $context['event'] === 'my_metric' + && $context['key'] === 'value' + && $context['message'] === 'Something happened'; + })); + + app()->instance('log', $log); + + Metrics::create("my_metric") + ->withLog("Something happened") + ->setExtra(['key' => 'value']); + + Metrics::flush(); + } + public function testGivenTimestampIsntChanged() { $metric = new Metric('my_metric', 1); From 905f4573bd8332310bfb4b9eff826e0d47604019 Mon Sep 17 00:00:00 2001 From: Joseph Szobody Date: Fri, 20 Feb 2026 17:22:13 -0500 Subject: [PATCH 2/2] Move flushMetricLogs() after empty metrics early return --- src/Drivers/CloudWatch.php | 4 ++-- src/Drivers/InfluxDB.php | 4 ++-- src/Drivers/LogDriver.php | 4 ++-- src/Drivers/PostHog.php | 4 ++++ src/Drivers/PrometheusDriver.php | 4 ++++ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Drivers/CloudWatch.php b/src/Drivers/CloudWatch.php index ad7cec8..3d74e85 100644 --- a/src/Drivers/CloudWatch.php +++ b/src/Drivers/CloudWatch.php @@ -29,12 +29,12 @@ public function setClient(CloudWatchClient $client): void public function flush(): static { - $this->flushMetricLogs(); - if (!count($this->getMetrics())) { return $this; } + $this->flushMetricLogs(); + $this->send($this->getMetrics()); $this->metrics = []; diff --git a/src/Drivers/InfluxDB.php b/src/Drivers/InfluxDB.php index d3c24dc..0f1b16d 100644 --- a/src/Drivers/InfluxDB.php +++ b/src/Drivers/InfluxDB.php @@ -53,12 +53,12 @@ public function point(IDBPoint|IDB2Point $point): static */ public function flush(): static { - $this->flushMetricLogs(); - if (empty($this->getMetrics())) { return $this; } + $this->flushMetricLogs(); + $this->send($this->getMetrics()); $this->metrics = []; diff --git a/src/Drivers/LogDriver.php b/src/Drivers/LogDriver.php index 634e8dd..1f94fef 100644 --- a/src/Drivers/LogDriver.php +++ b/src/Drivers/LogDriver.php @@ -26,12 +26,12 @@ public function format(Metric $metric): array public function flush(): static { - $this->flushMetricLogs(); - if (!count($this->getMetrics())) { return $this; } + $this->flushMetricLogs(); + $formatted = array_map([$this, 'format'], $this->getMetrics()); $this->logger->info("Metrics", $formatted); diff --git a/src/Drivers/PostHog.php b/src/Drivers/PostHog.php index c24a2e1..a21ecb3 100644 --- a/src/Drivers/PostHog.php +++ b/src/Drivers/PostHog.php @@ -14,6 +14,10 @@ public function __construct(protected string $distinctPrefix = '') public function flush(): static { + if (empty($this->getMetrics())) { + return $this; + } + $this->flushMetricLogs(); foreach ($this->getMetrics() as $metric) { diff --git a/src/Drivers/PrometheusDriver.php b/src/Drivers/PrometheusDriver.php index ac191bd..384e51d 100644 --- a/src/Drivers/PrometheusDriver.php +++ b/src/Drivers/PrometheusDriver.php @@ -48,6 +48,10 @@ public function format(Metric $metric): Collector public function flush(): static { + if (empty($this->metrics)) { + return $this; + } + $this->flushMetricLogs(); $this->metrics = [];