From 01eb0fc4b2eaceeaf410357d84d83c2c287e9847 Mon Sep 17 00:00:00 2001 From: Joseph Szobody Date: Sun, 6 Aug 2023 16:21:25 -0400 Subject: [PATCH] v2 (#18) * cleanup, php8 types, upgraded deps, first stab at posthog driver * driver created callback * no default value, some backends do better without it * posthog test * list PH up front * ci * ci * . --- .github/workflows/run-tests.yml | 28 ++++ .gitignore | 4 +- .travis.yml | 19 --- README.md | 74 ++++++---- composer.json | 28 +++- config/metrics.php | 5 + phpunit.xml | 44 +++--- src/Drivers/AbstractDriver.php | 63 ++------- src/Drivers/CloudWatch.php | 69 ++-------- src/Drivers/InfluxDB.php | 129 ++++++------------ src/Drivers/LogDriver.php | 17 +-- src/Drivers/NullDriver.php | 16 +-- src/Drivers/PostHog.php | 65 +++++++++ .../Metrics.php} | 9 +- src/Metric.php | 107 +++------------ src/MetricsManager.php | 55 +++++--- src/MetricsServiceProvider.php | 21 +++ src/Traits/ProvidesMetric.php | 39 +----- tests/CloudWatchDriverTest.php | 2 +- tests/CloudWatchEventListeningTest.php | 1 + tests/InfluxDBDriverTest.php | 5 +- tests/InfluxDBEventListeningTest.php | 1 + tests/LogDriverTest.php | 2 +- tests/MetricTest.php | 9 +- tests/NullDriverTest.php | 8 +- tests/PostHogDriverTest.php | 32 +++++ tests/TestCase.php | 13 +- 27 files changed, 406 insertions(+), 459 deletions(-) create mode 100644 .github/workflows/run-tests.yml delete mode 100644 .travis.yml create mode 100644 src/Drivers/PostHog.php rename src/{MetricsFacade.php => Facades/Metrics.php} (62%) create mode 100644 tests/PostHogDriverTest.php diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..b0d31f8 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,28 @@ +name: CI PHP 8.2 + +on: [push] + +jobs: + build-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: php-actions/composer@v6 + with: + php_version: "8.2" + + - name: PHPUnit Tests + uses: php-actions/phpunit@v3 + env: + APP_ENV: testing + APP_KEY: 7yPSFp$SCRcRh8zkq?!B$!5tPEF$cxq3 + XDEBUG_MODE: coverage + with: + bootstrap: vendor/autoload.php + configuration: phpunit.xml + php_extensions: xdebug + args: --coverage-text + php_version: "8.2" + version: "10" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 416e12a..005b664 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ vendor .idea composer.lock -build \ No newline at end of file +build +.phpunit +.phpunit.cache \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5b077e1..0000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: php - -php: - - 7.3 - - 7.4 - -env: - matrix: - - COMPOSER_FLAGS="" - -before_script: - - travis_retry composer self-update - - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source - -script: - - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover - -after_script: - - php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover diff --git a/README.md b/README.md index 69ee174..d71d150 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ [![Latest Version on Packagist](https://img.shields.io/packagist/v/stechstudio/laravel-metrics.svg?style=flat-square)](https://packagist.org/packages/stechstudio/laravel-metrics) [![Total Downloads](https://img.shields.io/packagist/dt/stechstudio/laravel-metrics.svg?style=flat-square)](https://packagist.org/packages/stechstudio/laravel-metrics) -[![Build Status](https://img.shields.io/travis/stechstudio/laravel-metrics/master.svg?style=flat-square)](https://travis-ci.org/stechstudio/laravel-metrics) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) -This package makes it incredibly easy to ship app metrics to backends such as InfluxDB or CloudWatch. +This package makes it incredibly easy to ship app metrics to backends such as PostHog, InfluxDB or CloudWatch. + +There are two major components: a facade that lets you create metrics on your own, and an event listener to +automatically send metrics for Laravel events. -There are two major components: a facade that lets you create metrics on your own, and an event listener to automatically send metrics for Laravel events. - ## Installation You know the drill... @@ -19,9 +19,22 @@ composer require stechstudio/laravel-metrics ## Backend configuration +### PostHog + +1. Install the PostHog PHP client: `composer require posthog/posthog-php` + +2. Add the following to your `.env` file: + +``` +METRICS_BACKEND=posthog +POSTHOG_API_KEY=... +``` + ### InfluxDB -Add the following to your `.env` file: +1. Install the InfluxDB PHP client: `composer require influxdata/influxdb-client-php` + +2. Add the following to your `.env` file: ``` METRICS_BACKEND=influxdb @@ -39,9 +52,9 @@ IDB_UDP_PORT=... ### CloudWatch -First make sure you have AWS itself properly setup. That means `composer install aws/aws-sdk-php` and making sure you have your AWS credentials configured. - -From there, you simply need to add: +1. Install the AWS PHP SDK: `composer require aws/aws-sdk-php`. + +2. Add the following to your `.env` file: ``` METRICS_BACKEND=cloudwatch @@ -60,7 +73,7 @@ If you need to disable metrics just set the backend to null: METRICS_BACKEND=null ``` -This `null` driver will simply discard any metrics. +This `null` driver will simply discard any metrics. ## Sending an individual metric @@ -75,27 +88,31 @@ Metrics::create('order_placed') ]); ``` -The only required attribute is the `name`, everything else is optional. +The only required attribute is the `name`, everything else is optional. ## Driver mapping -This is how we are mapping metric attributes in our backends. +This is how we are mapping metric attributes in our backends. -| Metric attribute | InfluxDB | CloudWatch | -| ---------------- | ------------- | ----------------- | -| name | measurement | MetricName | -| value | fields[value] | Value | -| unit | _ignored_ | Unit | -| resolution | _ignored_ | StorageResolution | -| tags | tags | Dimensions | -| extra | fields | _ignored_ | -| timestamp | timestamp | Timestamp | +| Metric attribute | PostHog | InfluxDB | CloudWatch | +|------------------|-------------------|---------------|-------------------| +| name | event | measurement | MetricName | +| value | properties[value] | fields[value] | Value | +| unit | _ignored_ | _ignored_ | Unit | +| resolution | _ignored_ | _ignored_ | StorageResolution | +| tags | _ignored_ | tags | Dimensions | +| extra | properties | fields | _ignored_ | +| timestamp | _ignored_ | timestamp | Timestamp | -See the [CloudWatch docs](http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) and [InfluxDB docs](https://docs.influxdata.com/influxdb/latest/concepts/key_concepts/) for more information on their respective data formats. Note we only do minimal validation, you are expected to know what data types and formats your backend supports for a given metric attribute. +See the [CloudWatch docs](http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) +and [InfluxDB docs](https://docs.influxdata.com/influxdb/latest/concepts/key_concepts/) for more information on their +respective data formats. Note we only do minimal validation, you are expected to know what data types and formats your +backend supports for a given metric attribute. ## Sending metrics from Laravel events -The main motivation for this library was to send metrics automatically when certain events occur in a Laravel application. So this is where things really get fun! +The main motivation for this library was to send metrics automatically when certain events occur in a Laravel +application. So this is where things really get fun! Let's say you have a simple Laravel event called OrderReceived: @@ -111,21 +128,21 @@ class OrderReceived { ``` The first step is to implement an interface: - + ```php use STS\Metrics\Contracts\ShouldReportMetric; class OrderReceived implements ShouldReportMetric { ``` -This will tell the global event listener to send a metric for this event. +This will tell the global event listener to send a metric for this event. There are two different ways you can then provide the metric details. ### 1. Use the `ProvidesMetric` trait You can also include a trait that helps with building this metric: - + ```php use STS\Metrics\Contracts\ShouldReportMetric; use STS\Metrics\Traits\ProvidesMetric; @@ -138,7 +155,7 @@ In this case, the trait will build a metric called `order_received` (taken from #### Customizing event metric data -If you decide to use the trait, you likely will want to customize the event metric data. +If you decide to use the trait, you likely will want to customize the event metric data. You can provide metric data with class attributes: @@ -160,11 +177,12 @@ public function getMetricValue() } ``` -You can provide any of our metric attributes using these class attributes or getter methods. +You can provide any of our metric attributes using these class attributes or getter methods. ### 2. Create the metric yourself -Depending on how much detail you need to provide for your metric, it may be simpler to just build it yourself. In this case you can ditch the trait and simply provide a public `createMetric` function that returns a new `Metric` instance: +Depending on how much detail you need to provide for your metric, it may be simpler to just build it yourself. In this +case you can ditch the trait and simply provide a public `createMetric` function that returns a new `Metric` instance: ```php use STS\Metrics\Contracts\ShouldReportMetric; diff --git a/composer.json b/composer.json index f325747..6d7442b 100644 --- a/composer.json +++ b/composer.json @@ -14,14 +14,23 @@ } ], "require": { - "illuminate/support": "^5.6|^6.0|^7.0|^8.0|^9.0|^10.0", - "influxdb/influxdb-php": "^1.14" + "php": "^8.0", + "illuminate/support": "^5.6|^6.0|^7.0|^8.0|^9.0|^10.0" }, "require-dev": { - "phpunit/phpunit": "^9.0", - "orchestra/testbench": "^7.0", - "mockery/mockery": "^1.0", - "aws/aws-sdk-php": "^3.133" + "phpunit/phpunit": "^10.0", + "orchestra/testbench": "^8.0", + "mockery/mockery": "^1.6", + "aws/aws-sdk-php": "^3.2", + "influxdb/influxdb-php": "^1.15", + "posthog/posthog-php": "^3.0", + "influxdata/influxdb-client-php": "^3.4" + }, + "suggest": { + "posthog/posthog-php": "PostHog integration", + "aws/aws-sdk-php": "AWS CloudWatch integration", + "influxdb/influxdb-php": "For integration with InfluxDB 1.7 or earlier", + "influxdata/influxdb-client-php": "For integration with InfluxDB 1.8 or later" }, "autoload": { "psr-4": { @@ -40,8 +49,13 @@ "STS\\Metrics\\MetricsServiceProvider" ], "aliases": { - "Metrics": "STS\\Metrics\\MetricsFacade" + "Metrics": "Metrics" } } + }, + "config": { + "allow-plugins": { + "php-http/discovery": true + } } } diff --git a/config/metrics.php b/config/metrics.php index 91b9d3a..07c6d73 100644 --- a/config/metrics.php +++ b/config/metrics.php @@ -16,6 +16,11 @@ 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'namespace' => env('CLOUDWATCH_NAMESPACE') + ], + "posthog" => [ + 'key' => env('POSTHOG_API_KEY'), + 'host' => env('POSTHOG_HOST', 'https://app.posthog.com'), + 'distinct_prefix' => env('POSTHOG_DISTINCT_PREFIX', 'user:') ] ], ]; diff --git a/phpunit.xml b/phpunit.xml index 2098973..6b9f1c0 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,27 +1,21 @@ - - - - tests - - - - - src/ - - - - - - - + + + + + + + + + + + tests + + + + + + src/ + + diff --git a/src/Drivers/AbstractDriver.php b/src/Drivers/AbstractDriver.php index 7cb9bb1..f6b75c2 100644 --- a/src/Drivers/AbstractDriver.php +++ b/src/Drivers/AbstractDriver.php @@ -4,34 +4,15 @@ use STS\Metrics\Metric; -/** - * Class AbstractDriver - * @package STS\Metrics\Drivers - */ abstract class AbstractDriver { - /** - * @var array - */ - protected $metrics = []; + protected array $metrics = []; - /** - * @var array - */ - protected $tags = []; + protected array $tags = []; - /** - * @var array - */ - protected $extra = []; + protected array $extra = []; - /** - * @param $name - * @param $value - * - * @return Metric - */ - public function create($name, $value = null) + public function create(string $name, $value = null): Metric { $metric = new Metric($name, $value, $this); $this->add($metric); @@ -39,16 +20,11 @@ public function create($name, $value = null) return $metric; } - /** - * @param Metric $metric - * - * @return $this - */ - public function add(Metric $metric) + public function add(Metric $metric): static { $metric->setDriver($this); - if($metric->getTimestamp() == null) { + if($metric->getTimestamp() === null) { $metric->setTimestamp(new \DateTime); } @@ -57,22 +33,15 @@ public function add(Metric $metric) return $this; } - /** - * @return array - */ - public function getMetrics() + public function getMetrics(): array { return $this->metrics; } /** * Set default tags to be merged in on all metrics - * - * @param array $tags - * - * @return $this */ - public function setTags(array $tags) + public function setTags(array $tags): static { $this->tags = $tags; @@ -81,27 +50,15 @@ public function setTags(array $tags) /** * Set default extra to be merged in on all metrics - * - * @param array $extra - * - * @return $this */ - public function setExtra(array $extra) + public function setExtra(array $extra): static { $this->extra = $extra; return $this; } - /** - * @param Metric $metric - * - * @return mixed - */ abstract public function format(Metric $metric); - /** - * @return $this - */ - abstract public function flush(); + abstract public function flush(): static; } diff --git a/src/Drivers/CloudWatch.php b/src/Drivers/CloudWatch.php index 7d99dda..72c1d39 100644 --- a/src/Drivers/CloudWatch.php +++ b/src/Drivers/CloudWatch.php @@ -5,56 +5,29 @@ use Aws\CloudWatch\CloudWatchClient; use STS\Metrics\Metric; -/** - * Class CloudWatch - * @package STS\Metrics\Drivers - */ class CloudWatch extends AbstractDriver { - /** - * @var CloudWatchClient - */ - protected $client; + protected CloudWatchClient $client; - /** - * @var string - */ - protected $namespace; + protected string $namespace; - /** - * CloudWatch constructor. - * - * @param CloudWatchClient $client - * @param $namespace - */ public function __construct(CloudWatchClient $client, $namespace) { $this->setClient($client); $this->namespace = $namespace; } - /** - * @return CloudWatchClient - */ - public function getClient() + public function getClient(): CloudWatchClient { return $this->client; } - /** - * @param CloudWatchClient $client - */ - public function setClient(CloudWatchClient $client) + public function setClient(CloudWatchClient $client): void { $this->client = $client; } - /** - * Flush all queued metrics to CloudWatch - * - * @return $this - */ - public function flush() + public function flush(): static { if (!count($this->getMetrics())) { return $this; @@ -67,12 +40,7 @@ public function flush() return $this; } - /** - * Send one or more metrics to CloudWatch now - * - * @param $metrics - */ - public function send($metrics) + public function send($metrics): void { $this->getClient()->putMetricData([ 'MetricData' => array_map(function ($metric) { @@ -82,12 +50,7 @@ public function send($metrics) ]); } - /** - * @param Metric $metric - * - * @return array - */ - public function format(Metric $metric) + public function format(Metric $metric): array { return array_merge( array_filter([ @@ -97,19 +60,15 @@ public function format(Metric $metric) 'Timestamp' => $this->formatTimestamp($metric->getTimestamp()), 'Unit' => $metric->getUnit() ]), - [ - 'Value' => $metric->getValue() - ]); + $metric->getValue() === null + ? [] + : ['Value' => $metric->getValue()] + ); } - /** - * @param $timestamp - * - * @return int - */ - protected function formatTimestamp($timestamp) + protected function formatTimestamp($timestamp): int { - if (is_numeric($timestamp) && strlen($timestamp) == 10) { + if (is_numeric($timestamp) && strlen($timestamp) === 10) { // This appears to be in seconds already return $timestamp; } @@ -127,7 +86,7 @@ protected function formatTimestamp($timestamp) return time(); } - protected function formatDimensions(array $dimensions) + protected function formatDimensions(array $dimensions): array { return array_map(function ($key, $value) { return [ diff --git a/src/Drivers/InfluxDB.php b/src/Drivers/InfluxDB.php index 478ad25..7b427d6 100644 --- a/src/Drivers/InfluxDB.php +++ b/src/Drivers/InfluxDB.php @@ -3,42 +3,22 @@ namespace STS\Metrics\Drivers; use InfluxDB\Database; +use InfluxDB\Exception; use InfluxDB\Point; use STS\Metrics\Metric; -/** - * Class InfluxDB - * @package STS\Metrics\Drivers - */ class InfluxDB extends AbstractDriver { - /** - * @var Database - */ - protected $readConnection; - /** - * @var Database - */ - protected $writeConnection; - /** - * @var array - */ - protected $points = []; - /** - * @var Database - */ - protected $tcpConnection; - /** - * @var Database - */ - protected $udpConnection; + protected Database $readConnection; + + protected Database $writeConnection; + + protected array $points = []; + + protected Database $tcpConnection; + + protected Database $udpConnection; - /** - * InfluxDB constructor. - * - * @param $tcpConnection - * @param $udpConnection - */ public function __construct($tcpConnection, $udpConnection = null) { $this->readConnection = $tcpConnection; @@ -52,14 +32,20 @@ public function __construct($tcpConnection, $udpConnection = null) * Queue up a new measurement * * @param string $measurement the name of the measurement ... 'this-data' - * @param mixed $value measurement value ... 15 - * @param array $tags measurement tags ... ['host' => 'server01', 'region' => 'us-west'] - * @param array $fields measurement fields ... ['cpucount' => 10, 'free' => 2] - * @param mixed $timestamp timestamp in nanoseconds on Linux ONLY + * @param mixed|null $value measurement value ... 15 + * @param array $tags measurement tags ... ['host' => 'server01', 'region' => 'us-west'] + * @param array $fields measurement fields ... ['cpucount' => 10, 'free' => 2] + * @param mixed|null $timestamp timestamp in nanoseconds on Linux ONLY * * @return $this */ - public function measurement($measurement, $value = null, array $tags = [], array $fields = [], $timestamp = null) + public function measurement( + string $measurement, + mixed $value = null, + array $tags = [], + array $fields = [], + mixed $timestamp = null + ): static { return $this->point(new Point( $measurement, @@ -70,14 +56,7 @@ public function measurement($measurement, $value = null, array $tags = [], array )); } - /** - * Queue up a new point - * - * @param Point $point - * - * @return $this - */ - public function point(Point $point) + public function point(Point $point): static { $this->points[] = $point; @@ -85,24 +64,20 @@ public function point(Point $point) } /** - * A public way tog et the nanosecond precision we desire. - * - * @param mixed $timestamp - * - * @return int|null + * A public way to get the nanosecond precision we desire. */ - public function getNanoSecondTimestamp($timestamp = null) + public function getNanoSecondTimestamp(mixed $timestamp = null): int { if ($timestamp instanceof \DateTime) { return $timestamp->getTimestamp() * 1000000000; } - if (strlen($timestamp) == 19) { + if (strlen($timestamp) === 19) { // Looks like it is already nanosecond precise! return $timestamp; } - if (strlen($timestamp) == 10) { + if (strlen($timestamp) === 10) { // This appears to be in seconds return $timestamp * 1000000000; } @@ -117,10 +92,9 @@ public function getNanoSecondTimestamp($timestamp = null) } /** - * @return $this - * @throws \InfluxDB\Exception + * @throws Exception */ - public function flush() + public function flush(): static { if (empty($this->getMetrics())) { return $this; @@ -138,16 +112,13 @@ public function flush() } /** - * @param Metric $metric - * - * @return Point - * @throws \InfluxDB\Database\Exception + * @throws Database\Exception */ - public function format(Metric $metric) + public function format(Metric $metric): Point { return new Point( $metric->getName(), - $metric->getValue(), + $metric->getValue() ?? 1, array_merge($this->tags, $metric->getTags()), array_merge($this->extra, $metric->getExtra()), $this->getNanoSecondTimestamp($metric->getTimestamp()) @@ -155,13 +126,10 @@ public function format(Metric $metric) } /** - * Send one or more metrics to InfluxDB now - * - * @param $metrics - * - * @throws \InfluxDB\Exception + * @throws Exception + * @throws Database\Exception */ - public function send($metrics) + public function send($metrics): void { $this->getWriteConnection()->writePoints( array_map(function ($metric) { @@ -170,42 +138,27 @@ public function send($metrics) ); } - /** - * @return array - */ - public function getPoints() + public function getPoints(): array { return $this->points; } - /** - * @return Database - */ - public function getWriteConnection() + public function getWriteConnection(): Database { return $this->writeConnection; } - /** - * @param Database $connection - */ - public function setWriteConnection(Database $connection) + public function setWriteConnection(Database $connection): void { $this->writeConnection = $connection; } - /** - * @return Database - */ - public function getReadConnection() + public function getReadConnection(): Database { return $this->readConnection; } - /** - * @param Database $connection - */ - public function setReadConnection(Database $connection) + public function setReadConnection(Database $connection): void { $this->readConnection = $connection; } @@ -218,9 +171,9 @@ public function setReadConnection(Database $connection) * * @return mixed */ - public function __call($method, $parameters) + public function __call($method, $parameters): mixed { - if (strpos($method, 'write') === 0) { + if (str_starts_with($method, 'write')) { return $this->getWriteConnection()->$method(...$parameters); } diff --git a/src/Drivers/LogDriver.php b/src/Drivers/LogDriver.php index f563511..b35c000 100644 --- a/src/Drivers/LogDriver.php +++ b/src/Drivers/LogDriver.php @@ -5,24 +5,13 @@ use STS\Metrics\Metric; use Psr\Log\LoggerInterface; -/** - * Class LogDriver - * @package STS\Metrics\Drivers - */ class LogDriver extends AbstractDriver { - - /** - * @var LoggerInterface - */ - protected $logger; - - public function __construct(LoggerInterface $logger) + public function __construct(protected LoggerInterface $logger) { - $this->logger = $logger; } - public function format(Metric $metric) + public function format(Metric $metric): array { return array_filter([ 'name' => $metric->getName(), @@ -35,7 +24,7 @@ public function format(Metric $metric) ]); } - public function flush() + public function flush(): static { if (!count($this->getMetrics())) { return $this; diff --git a/src/Drivers/NullDriver.php b/src/Drivers/NullDriver.php index bc4ab21..d467c46 100644 --- a/src/Drivers/NullDriver.php +++ b/src/Drivers/NullDriver.php @@ -4,26 +4,14 @@ use STS\Metrics\Metric; -/** - * Class Null - * @package STS\Metrics\Drivers - */ class NullDriver extends AbstractDriver { - /** - * @return $this - */ - public function flush() + public function flush(): static { return $this; } - /** - * @param Metric $metric - * - * @return array - */ - public function format(Metric $metric) + public function format(Metric $metric): array { return []; } diff --git a/src/Drivers/PostHog.php b/src/Drivers/PostHog.php new file mode 100644 index 0000000..3eef4d3 --- /dev/null +++ b/src/Drivers/PostHog.php @@ -0,0 +1,65 @@ +format($metric)); + + return $this; + } + + /** + * 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; + } + + public function format(Metric $metric): array + { + return [ + 'distinctId' => $this->distinctId, + 'event' => $metric->getName(), + 'properties' => array_merge( + $metric->getValue() + ? ['value' => $metric->getValue()] + : [], + $this->extra, + $metric->getExtra() + ), + ]; + } + + /** + * Pass through PostHog anything we don't handle. + * + * @param $method + * @param $parameters + * + * @return mixed + */ + public function __call($method, $parameters) + { + return PostHogClient::$method(...$parameters); + } +} diff --git a/src/MetricsFacade.php b/src/Facades/Metrics.php similarity index 62% rename from src/MetricsFacade.php rename to src/Facades/Metrics.php index df28c72..3a30b61 100644 --- a/src/MetricsFacade.php +++ b/src/Facades/Metrics.php @@ -1,15 +1,16 @@ value; } - /** - * @param mixed $value - * - * @return $this - */ - public function setValue($value) + public function setValue(mixed $value): static { $this->value = $value; return $this; } - /** - * @return string - */ - public function getUnit() + public function getUnit(): string|null { return $this->unit; } - /** - * @param string $unit - * - * @return $this - */ - public function setUnit($unit) + public function setUnit(mixed $unit): static { $this->unit = $unit; return $this; } - /** - * @return array - */ - public function getTags() + public function getTags(): array { return $this->tags; } - /** - * @param array $tags - * - * @return $this - */ - public function setTags(array $tags) + public function setTags(array $tags): static { $this->tags = $tags; return $this; } - /** - * @param $key - * @param $value - */ - public function addTag($key, $value) + public function addTag($key, $value): static { $this->tags[$key] = $value; + + return $this; } - /** - * @return array - */ - public function getExtra() + public function getExtra(): array { return $this->extra; } - /** - * @param array $extra - * - * @return $this - */ - public function setExtra(array $extra) + public function setExtra(array $extra): static { $this->extra = $extra; return $this; } - /** - * @param $key - * @param $value - */ - public function addExtra($key, $value) + public function addExtra($key, $value): static { $this->extra[$key] = $value; + + return $this; } - /** - * @return mixed - */ - public function getTimestamp() + public function getTimestamp(): mixed { return $this->timestamp; } - /** - * @param mixed $timestamp - * - * @return $this - */ - public function setTimestamp($timestamp) + public function setTimestamp($timestamp): static { $this->timestamp = $timestamp; return $this; } - /** - * @return mixed - */ - public function add() + public function add(): AbstractDriver { - $this->getDriver()->add($this); + return $this->getDriver()->add($this); } - - /** - * Return our own driver if we have one, otherwise our Metrics default driver - * - * @return AbstractDriver - */ - public function getDriver() + public function getDriver(): AbstractDriver { - return $this->driver - ? $this->driver - : app('metrics')->driver(); + return $this->driver ?? app('metrics')->driver(); } - /** - * @param AbstractDriver $driver - * - * @return $this - */ - public function setDriver(AbstractDriver $driver) + public function setDriver(AbstractDriver $driver): static { $this->driver = $driver; return $this; } - /** - * @return mixed - */ - public function format() + public function format(): mixed { return $this->getDriver()->format($this); } diff --git a/src/MetricsManager.php b/src/MetricsManager.php index 2cd40e0..41de2f7 100644 --- a/src/MetricsManager.php +++ b/src/MetricsManager.php @@ -3,55 +3,66 @@ namespace STS\Metrics; use Illuminate\Support\Manager; +use STS\Metrics\Drivers\AbstractDriver; use STS\Metrics\Drivers\CloudWatch; use STS\Metrics\Drivers\InfluxDB; use STS\Metrics\Drivers\LogDriver; use STS\Metrics\Drivers\NullDriver; +use STS\Metrics\Drivers\PostHog; /** - * Class MetricsManager - * @package STS\Metrics + * @mixin AbstractDriver */ class MetricsManager extends Manager { - /** - * @return string - */ - public function getDefaultDriver() + protected $driverCreatedCallback = null; + + public function whenDriverCreated(callable $callback) + { + $this->driverCreatedCallback = $callback; + + return $this; + } + + public function getDefaultDriver(): string { return $this->container['config']['metrics.default'] == null ? 'null' : $this->container['config']['metrics.default']; } - /** - * @return InfluxDB - */ - public function createInfluxdbDriver() + protected function createDriver($driver) + { + $driver = parent::createDriver($driver); + + if($this->driverCreatedCallback) { + call_user_func($this->driverCreatedCallback, $driver); + } + + return $driver; + } + + public function createInfluxdbDriver(): InfluxDB { return $this->container->make(InfluxDB::class); } - /** - * @return mixed - */ - public function createCloudwatchDriver() + public function createCloudwatchDriver(): CloudWatch { return $this->container->make(CloudWatch::class); } - /** - * @return LogDriver - */ - public function createLogDriver() + public function createPostHogDriver(): PostHog + { + return $this->container->make(PostHog::class); + } + + public function createLogDriver(): LogDriver { return $this->container->make(LogDriver::class); } - /** - * @return mixed - */ - public function createNullDriver() + public function createNullDriver(): NullDriver { return new NullDriver(); } diff --git a/src/MetricsServiceProvider.php b/src/MetricsServiceProvider.php index b25b972..549c209 100644 --- a/src/MetricsServiceProvider.php +++ b/src/MetricsServiceProvider.php @@ -5,12 +5,14 @@ use Aws\CloudWatch\CloudWatchClient; use Illuminate\Support\Arr; use Illuminate\Support\ServiceProvider; +use Illuminate\Support\Str; use InfluxDB\Client; use STS\Metrics\Contracts\ShouldReportMetric; use STS\Metrics\Drivers\CloudWatch; use STS\Metrics\Drivers\InfluxDB; use Illuminate\Foundation\Application as LaravelApplication; use Laravel\Lumen\Application as LumenApplication; +use STS\Metrics\Drivers\PostHog; use STS\Metrics\Octane\Listeners\FlushMetrics; /** @@ -37,6 +39,10 @@ public function register() $this->app->singleton(CloudWatch::class, function () { return $this->createCloudWatchDriver($this->app['config']['metrics.backends.cloudwatch']); }); + + $this->app->singleton(PostHog::class, function () { + return $this->createPostHogDriver($this->app['config']['metrics.backends.posthog']); + }); } /** @@ -164,4 +170,19 @@ protected function createCloudWatchDriver(array $config) return new CloudWatch(new CloudWatchClient($opts), $config['namespace']); } + + protected function createPostHogDriver(array $config) + { + \PostHog\PostHog::init($config['key'], [ + 'host' => $config['host'], + ]); + + return new PostHog( + match(true) { + auth()->check() => $config['distinct_prefix'] . auth()->id(), + session()->isStarted() => sha1(session()->getId()), + default => Str::random() + } + ); + } } diff --git a/src/Traits/ProvidesMetric.php b/src/Traits/ProvidesMetric.php index 57bc168..ad8c747 100644 --- a/src/Traits/ProvidesMetric.php +++ b/src/Traits/ProvidesMetric.php @@ -5,16 +5,12 @@ use Illuminate\Support\Str; use STS\Metrics\Metric; -/** - * Class ProvidesMetric - * @package STS\Metrics\Traits - */ trait ProvidesMetric { /** * @return Metric */ - public function createMetric() + public function createMetric(): Metric { return (new Metric($this->getMetricName())) ->setValue($this->getMetricValue()) @@ -25,59 +21,41 @@ public function createMetric() ->setResolution($this->getMetricResolution()); } - /** - * @return string - */ - public function getMetricName() + public function getMetricName(): string { return property_exists($this, 'metricName') ? $this->metricName : Str::snake((new \ReflectionClass($this))->getShortName()); } - /** - * @return mixed - */ - public function getMetricValue() + public function getMetricValue(): mixed { return property_exists($this, 'metricValue') ? $this->metricValue : 1; } - /** - * @return string - */ - public function getMetricUnit() + public function getMetricUnit(): string|null { return property_exists($this, 'metricUnit') ? $this->metricUnit : null; } - /** - * @return array - */ - public function getMetricTags() + public function getMetricTags(): array { return property_exists($this, 'metricTags') ? $this->metricTags : []; } - /** - * @return array - */ - public function getMetricExtra() + public function getMetricExtra(): array { return property_exists($this, 'metricExtra') ? $this->metricExtra : []; } - /** - * @return mixed - */ public function getMetricTimestamp() { return property_exists($this, 'metricTimestamp') @@ -85,10 +63,7 @@ public function getMetricTimestamp() : new \DateTime; } - /** - * @return int - */ - public function getMetricResolution() + public function getMetricResolution(): int|null { return property_exists($this, 'metricResolution') ? $this->metricResolution diff --git a/tests/CloudWatchDriverTest.php b/tests/CloudWatchDriverTest.php index 93771ae..d036627 100644 --- a/tests/CloudWatchDriverTest.php +++ b/tests/CloudWatchDriverTest.php @@ -1,6 +1,6 @@ setTags(['tag1' => 'tag_value'])->setExtra(['extra1' => 'extra_value']); $metric = (new \STS\Metrics\Metric("my_metric")) + //->setValue(1) ->setTags(['foo' => 'bar']); $point = $driver->format($metric); $this->assertCount(2, $point->getTags()); $this->assertEquals("tag_value", $point->getTags()['tag1']); - $this->assertCount(1, $point->getFields()); + + $this->assertCount(2, $point->getFields()); $this->assertEquals('"extra_value"', $point->getFields()['extra1']); } diff --git a/tests/InfluxDBEventListeningTest.php b/tests/InfluxDBEventListeningTest.php index 3bec2ea..1eb5df3 100644 --- a/tests/InfluxDBEventListeningTest.php +++ b/tests/InfluxDBEventListeningTest.php @@ -1,4 +1,5 @@ setupInfluxDB(); - (new \STS\Metrics\Metric("my_metric", 5)) + (new Metric("my_metric", 5)) ->setTags(['foo' => 'bar']) ->add(); @@ -32,7 +35,7 @@ public function testCreatedFromDriver() public function testDefaultTimestampWhenAdding() { - $metric = new \STS\Metrics\Metric('my_metric', 1); + $metric = new Metric('my_metric', 1); $this->assertNull($metric->getTimestamp()); @@ -52,7 +55,7 @@ public function testDefaultTimestampWhenCreatingFromDriver() public function testGivenTimestampIsntChanged() { - $metric = new \STS\Metrics\Metric('my_metric', 1); + $metric = new Metric('my_metric', 1); $time = time(); $metric->setTimestamp($time); diff --git a/tests/NullDriverTest.php b/tests/NullDriverTest.php index 3b5f5cd..b697605 100644 --- a/tests/NullDriverTest.php +++ b/tests/NullDriverTest.php @@ -1,5 +1,7 @@ set('metrics.default', null); - $manager = app(\STS\Metrics\MetricsManager::class); + $manager = app(MetricsManager::class); $this->assertInstanceOf(NullDriver::class, $manager->driver()); } @@ -16,7 +18,7 @@ public function testEmptyFormat() { $driver = app(NullDriver::class); - $metric = (new \STS\Metrics\Metric("my_metric")); + $metric = (new Metric("my_metric")); $this->assertEquals([], $driver->format($metric)); } @@ -25,7 +27,7 @@ public function testDoesntFlush() { $driver = app(NullDriver::class); - $metric = (new \STS\Metrics\Metric("my_metric")); + $metric = (new Metric("my_metric")); $driver->add($metric); // Make sure we DO keep track of metrics diff --git a/tests/PostHogDriverTest.php b/tests/PostHogDriverTest.php new file mode 100644 index 0000000..246cafe --- /dev/null +++ b/tests/PostHogDriverTest.php @@ -0,0 +1,32 @@ +setupPostHog(); + + $metric = (new \STS\Metrics\Metric("file_uploaded")) + ->setExtra(["foo" => "bar"]) + ->setValue(5); + + $formatted = app(PostHog::class)->format($metric); + + $this->assertEquals("file_uploaded", $formatted['event']); + $this->assertEquals('bar', $formatted['properties']['foo']); + $this->assertEquals(5, $formatted['properties']['value']); + + } + + public function testNoDefaultValue() + { + $this->setupPostHog(); + + $metric = (new \STS\Metrics\Metric("file_uploaded")); + + $formatted = app(PostHog::class)->format($metric); + + $this->assertFalse(isset($formatted['properties']['value'])); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index ec46a29..0e50b59 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,15 +1,18 @@ 'STS\Metrics\MetricsFacade' + 'Metrics' => Metrics::class ]; } @@ -52,6 +55,12 @@ protected function setupCloudWatch($config = [], $mock = true) } } + protected function setupPostHog($config = [], $mock = true) + { + app('config')->set('metrics.default', 'posthog'); + app('config')->set('metrics.backends.posthog.key', 'Testing'); + } + protected function setupLogDriver($config = [], $mock = true) { app('config')->set('metrics.default', 'log');