├── .github └── workflows │ └── run-tests.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── config └── metrics.php ├── phpunit.xml ├── src ├── Adapters │ ├── AbstractInfluxDBAdapter.php │ ├── InfluxDB1Adapter.php │ └── InfluxDB2Adapter.php ├── Contracts │ └── ShouldReportMetric.php ├── Drivers │ ├── AbstractDriver.php │ ├── CloudWatch.php │ ├── InfluxDB.php │ ├── LogDriver.php │ ├── NullDriver.php │ ├── PostHog.php │ └── PrometheusDriver.php ├── Facades │ └── Metrics.php ├── Metric.php ├── MetricType.php ├── MetricsManager.php ├── MetricsServiceProvider.php ├── Octane │ └── Listeners │ │ └── FlushMetrics.php └── Traits │ ├── ComputesNanosecondTimestamps.php │ └── ProvidesMetric.php └── tests ├── CloudWatchDriverTest.php ├── CloudWatchEventListeningTest.php ├── InfluxDBAdapterTest.php ├── InfluxDBDriverTest.php ├── InfluxDBEventListeningTest.php ├── InfluxDBV2DriverTest.php ├── LogDriverTest.php ├── MetricTest.php ├── NullDriverTest.php ├── PostHogDriverTest.php ├── PrometheusDriverTest.php ├── PrometheusEventListeningTest.php └── TestCase.php /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: CI PHP 8.2 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build-test: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - uses: php-actions/composer@v6 13 | with: 14 | php_version: "8.2" 15 | 16 | - name: PHPUnit Tests 17 | uses: php-actions/phpunit@v3 18 | env: 19 | APP_ENV: testing 20 | APP_KEY: 7yPSFp$SCRcRh8zkq?!B$!5tPEF$cxq3 21 | XDEBUG_MODE: coverage 22 | with: 23 | bootstrap: vendor/autoload.php 24 | configuration: phpunit.xml 25 | php_extensions: xdebug 26 | args: --coverage-text 27 | php_version: "8.2" 28 | version: "10" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .idea 3 | composer.lock 4 | build 5 | .phpunit 6 | .phpunit.cache -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Signature Tech Studio, Inc 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Metrics 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/stechstudio/laravel-metrics.svg?style=flat-square)](https://packagist.org/packages/stechstudio/laravel-metrics) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/stechstudio/laravel-metrics.svg?style=flat-square)](https://packagist.org/packages/stechstudio/laravel-metrics) 5 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 6 | 7 | This package makes it incredibly easy to ship app metrics to backends such as PostHog, InfluxDB or CloudWatch. 8 | 9 | There are two major components: a facade that lets you create metrics on your own, and an event listener to 10 | automatically send metrics for Laravel events. 11 | 12 | ## Installation 13 | 14 | You know the drill... 15 | 16 | ``` 17 | composer require stechstudio/laravel-metrics 18 | ``` 19 | 20 | ## Backend configuration 21 | 22 | ### PostHog 23 | 24 | 1. Install the PostHog PHP client: `composer require posthog/posthog-php` 25 | 26 | 2. Add the following to your `.env` file: 27 | 28 | ``` 29 | METRICS_BACKEND=posthog 30 | POSTHOG_API_KEY=... 31 | ``` 32 | 33 | ### InfluxDB v1.7 and under 34 | 35 | 1. Install the InfluxDB PHP client: `composer require influxdb/influxdb-php` 36 | 37 | 2. Add the following to your `.env` file: 38 | 39 | ``` 40 | METRICS_BACKEND=influxdb 41 | IDB_USERNAME=... 42 | IDB_PASSWORD=... 43 | IDB_HOST=... 44 | IDB_DATABASE=... 45 | IDB_VERSION=1 # Default 46 | 47 | # Only if you are not using the default 8086 48 | IDB_TCP_PORT=... 49 | 50 | # If you want to send metrics over UDP instead of TCP 51 | IDB_UDP_PORT=... 52 | ``` 53 | 54 | ### InfluxDB V1.8 and above 55 | 56 | 1. Install the InfluxDB PHP client: `composer require influxdata/influxdb-client-php` 57 | 58 | 2. Add the following to your `.env` file: 59 | 60 | 3. In order to use UDP with InfluxDB V1.8+ you must follow 61 | extra [setup steps](https://github.com/influxdata/influxdb-client-php#writing-via-udp) 62 | 63 | Add the following to your `.env` file: 64 | 65 | ``` 66 | METRICS_BACKEND=influxdb 67 | IDB_TOKEN=... 68 | IDB_DATABASE=... # Use the name of your desired bucket for this value 69 | IDB_HOST=... 70 | IDB_ORG=... 71 | IDB_VERSION=2 72 | 73 | # Only if you are not using the default 8086 74 | IDB_TCP_PORT=... 75 | 76 | # If you want to send metrics over UDP instead of TCP 77 | IDB_UDP_PORT=... 78 | ``` 79 | 80 | ### CloudWatch 81 | 82 | 1. Install the AWS PHP SDK: `composer require aws/aws-sdk-php`. 83 | 84 | 2. Add the following to your `.env` file: 85 | 86 | ``` 87 | METRICS_BACKEND=cloudwatch 88 | CLOUDWATCH_NAMESPACE=... 89 | 90 | AWS_DEFAULT_REGION=... 91 | AWS_ACCESS_KEY_ID=... 92 | AWS_SECRET_ACCESS_KEY=... 93 | ``` 94 | 95 | ### Prometheus 96 | 1. Install the Prometheus PHP client: `composer require promphp/prometheus_client_php` 97 | 2. Configuring the backend to use Prometheus, makes sense only if you have an endpoint to expose them. 98 | Its purpose is only to format the registered metrics in a way that Prometheus can scrape them. 99 | 100 | ``` 101 | METRICS_BACKEND=prometheus 102 | ``` 103 | 104 | ### NullDriver (for development) 105 | 106 | If you need to disable metrics just set the backend to null: 107 | 108 | ``` 109 | METRICS_BACKEND=null 110 | ``` 111 | 112 | This `null` driver will simply discard any metrics. 113 | 114 | ## Sending an individual metric 115 | 116 | You can create metric by using the facade like this: 117 | 118 | ```php 119 | Metrics::create('order_placed') 120 | ->setValue(1) 121 | ->setTags([ 122 | 'source' => 'email-campaign', 123 | 'user' => 54 124 | ]); 125 | ``` 126 | 127 | The only required attribute is the `name`, everything else is optional. 128 | 129 | ## Driver mapping 130 | 131 | This is how we are mapping metric attributes in our backends. 132 | 133 | | Metric attribute | PostHog | InfluxDB | CloudWatch | Prometheus | 134 | |------------------|-------------------|---------------|-------------------|-----------------------------------------------| 135 | | name | event | measurement | MetricName | name | 136 | | value | properties[value] | fields[value] | Value | value | 137 | | unit | _ignored_ | _ignored_ | Unit | _ignored_ | 138 | | resolution | _ignored_ | _ignored_ | StorageResolution | _ignored_ | 139 | | tags | _ignored_ | tags | Dimensions | keys -> labelNames
values -> labelValues | 140 | | extra | properties | fields | _ignored_ | _ignored_ | 141 | | timestamp | _ignored_ | timestamp | Timestamp | _ignored_ | 142 | | description | _ignored_ | _ignored_ | _ignored_ | help | 143 | | namespace | _ignored_ | _ignored_ | _ignored_ | namespace | 144 | | type | _ignored_ | _ignored_ | _ignored_ | used to register counter or gauge metric | 145 | 146 | See the [CloudWatch docs](http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) 147 | and [InfluxDB docs](https://docs.influxdata.com/influxdb/latest/concepts/key_concepts/) for more information on their 148 | respective data formats. Note we only do minimal validation, you are expected to know what data types and formats your 149 | backend supports for a given metric attribute. 150 | 151 | ## Sending metrics from Laravel events 152 | 153 | The main motivation for this library was to send metrics automatically when certain events occur in a Laravel 154 | application. So this is where things really get fun! 155 | 156 | Let's say you have a simple Laravel event called OrderReceived: 157 | 158 | ```php 159 | class OrderReceived { 160 | protected $order; 161 | 162 | public function __construct($order) 163 | { 164 | $this->order = $order; 165 | } 166 | } 167 | ``` 168 | 169 | The first step is to implement an interface: 170 | 171 | ```php 172 | use STS\Metrics\Contracts\ShouldReportMetric; 173 | 174 | class OrderReceived implements ShouldReportMetric { 175 | ``` 176 | 177 | This will tell the global event listener to send a metric for this event. 178 | 179 | There are two different ways you can then provide the metric details. 180 | 181 | ### 1. Use the `ProvidesMetric` trait 182 | 183 | You can also include a trait that helps with building this metric: 184 | 185 | ```php 186 | use STS\Metrics\Contracts\ShouldReportMetric; 187 | use STS\Metrics\Traits\ProvidesMetric; 188 | 189 | class OrderReceived implements ShouldReportMetric { 190 | use ProvidesMetric; 191 | ``` 192 | 193 | In this case, the trait will build a metric called `order_received` (taken from the class name) with a value of `1`. 194 | 195 | #### Customizing event metric data 196 | 197 | If you decide to use the trait, you likely will want to customize the event metric data. 198 | 199 | You can provide metric data with class attributes: 200 | 201 | ```php 202 | class OrderReceived implements ShouldReportMetric { 203 | use ProvidesMetric; 204 | 205 | protected $metricName = "new_order"; 206 | protected $metricTags = ["category" => "revenue"]; 207 | ... 208 | ``` 209 | 210 | Or if some of your metric data is dynamic you can use getter methods: 211 | 212 | ```php 213 | public function getMetricValue() 214 | { 215 | return $this->order->total; 216 | } 217 | ``` 218 | 219 | You can provide any of our metric attributes using these class attributes or getter methods. 220 | 221 | ### 2. Create the metric yourself 222 | 223 | Depending on how much detail you need to provide for your metric, it may be simpler to just build it yourself. In this 224 | case you can ditch the trait and simply provide a public `createMetric` function that returns a new `Metric` instance: 225 | 226 | ```php 227 | use STS\Metrics\Contracts\ShouldReportMetric; 228 | use STS\Metrics\Metric; 229 | 230 | class OrderReceived implements ShouldReportMetric { 231 | protected $order; 232 | 233 | public function __construct($order) 234 | { 235 | $this->order = $order; 236 | } 237 | 238 | public function createMetric() 239 | { 240 | return (new Metric('order_received')) 241 | ->setValue(...) 242 | ->setTags([...]) 243 | ->setTimestamp(...) 244 | ->setResolutions(...); 245 | } 246 | } 247 | ``` 248 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stechstudio/laravel-metrics", 3 | "description": "Easily track metrics from Laravel events, or on your own", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Joseph Szobody", 9 | "email": "joseph@stechstudio.com" 10 | }, 11 | { 12 | "name": "Rob 'Bubba' Hines", 13 | "email": "bubba@stechstudio.com" 14 | } 15 | ], 16 | "require": { 17 | "php": "^8.1", 18 | "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "^10.0|^11.5.3", 22 | "orchestra/testbench": "^8.0|^9.0|^10.0", 23 | "mockery/mockery": "^1.6", 24 | "aws/aws-sdk-php": "^3.2", 25 | "guzzlehttp/guzzle": "^7.5", 26 | "influxdb/influxdb-php": "^1.15", 27 | "posthog/posthog-php": "^3.0", 28 | "influxdata/influxdb-client-php": "^3.4", 29 | "promphp/prometheus_client_php": "^2.10" 30 | }, 31 | "suggest": { 32 | "posthog/posthog-php": "PostHog integration", 33 | "aws/aws-sdk-php": "AWS CloudWatch integration", 34 | "influxdb/influxdb-php": "For integration with InfluxDB 1.7 or earlier", 35 | "influxdata/influxdb-client-php": "For integration with InfluxDB 1.8 or later", 36 | "promphp/prometheus_client_php": "For integration with Prometheus" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "STS\\Metrics\\": "src" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "files": [ 45 | "tests/TestCase.php" 46 | ] 47 | }, 48 | "minimum-stability": "stable", 49 | "extra": { 50 | "laravel": { 51 | "providers": [ 52 | "STS\\Metrics\\MetricsServiceProvider" 53 | ], 54 | "aliases": { 55 | "Metrics": "Metrics" 56 | } 57 | } 58 | }, 59 | "config": { 60 | "allow-plugins": { 61 | "php-http/discovery": true 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /config/metrics.php: -------------------------------------------------------------------------------- 1 | env('METRICS_BACKEND'), 5 | 'backends' => [ 6 | 'influxdb' => [ 7 | 'username' => env('IDB_USERNAME'), 8 | 'password' => env('IDB_PASSWORD'), 9 | 'host' => env('IDB_HOST'), 10 | 'database' => env('IDB_DATABASE'), 11 | 'tcp_port' => env('IDB_TCP_PORT', 8086), 12 | 'udp_port' => env('IDB_UDP_PORT'), 13 | 'version' => env('IDB_VERSION', 1), 14 | 'token' => env('IDB_TOKEN'), 15 | 'org' => env('IDB_ORG') 16 | ], 17 | 'cloudwatch' => [ 18 | 'region' => env('CLOUDWATCH_REGION', env('AWS_DEFAULT_REGION', 'us-east-1')), 19 | 'key' => env('AWS_ACCESS_KEY_ID'), 20 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 21 | 'namespace' => env('CLOUDWATCH_NAMESPACE') 22 | ], 23 | "posthog" => [ 24 | 'key' => env('POSTHOG_API_KEY'), 25 | 'host' => env('POSTHOG_HOST', 'https://app.posthog.com'), 26 | 'distinct_prefix' => env('POSTHOG_DISTINCT_PREFIX', 'user:') 27 | ] 28 | ], 29 | ]; 30 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | tests 13 | 14 | 15 | 16 | 17 | 18 | src/ 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Adapters/AbstractInfluxDBAdapter.php: -------------------------------------------------------------------------------- 1 | readConnection; 22 | } 23 | 24 | public function setReadConnection(Database|QueryApi $connection): static 25 | { 26 | $this->readConnection = $connection; 27 | 28 | return $this; 29 | } 30 | 31 | public function getWriteConnection(): Database|WriteApi|UdpWriter 32 | { 33 | return $this->writeConnection; 34 | } 35 | 36 | public function setWriteConnection(Database|WriteApi|UdpWriter $connection): static 37 | { 38 | $this->writeConnection = $connection; 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * Pass through to the Influx client anything we don't handle. 45 | */ 46 | public function __call($method, $parameters): mixed 47 | { 48 | if (strpos($method, 'write') === 0) { 49 | return $this->getWriteConnection()->$method(...$parameters); 50 | } 51 | 52 | return $this->getReadConnection()->$method(...$parameters); 53 | } 54 | 55 | abstract public function point( 56 | string $measurement, 57 | mixed $value = null, 58 | array $tags = [], 59 | array $fields = [], 60 | mixed $timestamp = null 61 | ); 62 | 63 | abstract public function writePoints(array $points, $precision = null); 64 | } -------------------------------------------------------------------------------- /src/Adapters/InfluxDB1Adapter.php: -------------------------------------------------------------------------------- 1 | readConnection = $tcpConnection; 14 | 15 | $this->writeConnection = is_null($udpConnection) 16 | ? $tcpConnection 17 | : $udpConnection; 18 | } 19 | 20 | /** 21 | * @throws Exception 22 | */ 23 | public function point( 24 | string $measurement, 25 | mixed $value = null, 26 | array $tags = [], 27 | array $fields = [], 28 | mixed $timestamp = null 29 | ): Point 30 | { 31 | return new Point( 32 | $measurement, 33 | $value, 34 | $tags, 35 | $fields, 36 | $this->getNanoSecondTimestamp($timestamp) 37 | ); 38 | } 39 | 40 | /** 41 | * @throws \InfluxDB\Exception 42 | */ 43 | public function writePoints(array $points, $precision = Database::PRECISION_NANOSECONDS) 44 | { 45 | return $this->getWriteConnection()->writePoints($points, $precision); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/Adapters/InfluxDB2Adapter.php: -------------------------------------------------------------------------------- 1 | readConnection = $client->createQueryApi(); 17 | $this->writeConnection = $useUdp 18 | ? $client->createUdpWriter() 19 | : $client->createWriteApi(); 20 | } 21 | 22 | public function point( 23 | string $measurement, 24 | mixed $value = null, 25 | array $tags = [], 26 | array $fields = [], 27 | mixed $timestamp = null 28 | ): Point 29 | { 30 | return new Point( 31 | $measurement, 32 | $tags, 33 | array_merge(compact('value'), $fields), 34 | $this->getNanoSecondTimestamp($timestamp) 35 | ); 36 | } 37 | 38 | /** 39 | * @throws Throwable 40 | */ 41 | public function writePoints(array $points, $precision = Point::DEFAULT_WRITE_PRECISION) 42 | { 43 | $this->getWriteConnection()->write($points, $precision); 44 | return true; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/Contracts/ShouldReportMetric.php: -------------------------------------------------------------------------------- 1 | add($metric); 19 | 20 | return $metric; 21 | } 22 | 23 | public function add(Metric $metric): static 24 | { 25 | $metric->setDriver($this); 26 | 27 | if($metric->getTimestamp() === null) { 28 | $metric->setTimestamp(new \DateTime); 29 | } 30 | 31 | $this->metrics[] = $metric; 32 | 33 | return $this; 34 | } 35 | 36 | public function getMetrics(): array 37 | { 38 | return $this->metrics; 39 | } 40 | 41 | /** 42 | * Set default tags to be merged in on all metrics 43 | */ 44 | public function setTags(array $tags): static 45 | { 46 | $this->tags = $tags; 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * Set default extra to be merged in on all metrics 53 | */ 54 | public function setExtra(array $extra): static 55 | { 56 | $this->extra = $extra; 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * Implement this, when the driver needs to expose metrics to be polled by a third party service such as prometheus 63 | */ 64 | public function formatted(): mixed 65 | { 66 | return null; 67 | } 68 | 69 | abstract public function format(Metric $metric); 70 | 71 | abstract public function flush(): static; 72 | } 73 | -------------------------------------------------------------------------------- /src/Drivers/CloudWatch.php: -------------------------------------------------------------------------------- 1 | setClient($client); 17 | $this->namespace = $namespace; 18 | } 19 | 20 | public function getClient(): CloudWatchClient 21 | { 22 | return $this->client; 23 | } 24 | 25 | public function setClient(CloudWatchClient $client): void 26 | { 27 | $this->client = $client; 28 | } 29 | 30 | public function flush(): static 31 | { 32 | if (!count($this->getMetrics())) { 33 | return $this; 34 | } 35 | 36 | $this->send($this->getMetrics()); 37 | 38 | $this->metrics = []; 39 | 40 | return $this; 41 | } 42 | 43 | public function send($metrics): void 44 | { 45 | $this->getClient()->putMetricData([ 46 | 'MetricData' => array_map(function ($metric) { 47 | return $this->format($metric); 48 | }, (array)$metrics), 49 | 'Namespace' => $this->namespace 50 | ]); 51 | } 52 | 53 | public function format(Metric $metric): array 54 | { 55 | return array_merge( 56 | array_filter([ 57 | 'MetricName' => $metric->getName(), 58 | 'Dimensions' => $this->formatDimensions(array_merge($this->tags, $metric->getTags())), 59 | 'StorageResolution' => in_array($metric->getResolution(), [1, 60]) ? $metric->getResolution() : null, 60 | 'Timestamp' => $this->formatTimestamp($metric->getTimestamp()), 61 | 'Unit' => $metric->getUnit() 62 | ]), 63 | $metric->getValue() === null 64 | ? [] 65 | : ['Value' => $metric->getValue()] 66 | ); 67 | } 68 | 69 | protected function formatTimestamp($timestamp): int 70 | { 71 | if (is_numeric($timestamp) && strlen($timestamp) === 10) { 72 | // This appears to be in seconds already 73 | return $timestamp; 74 | } 75 | 76 | if ($timestamp instanceof \DateTime) { 77 | return $timestamp->getTimestamp(); 78 | } 79 | 80 | if (preg_match("/\d{10}\.\d{4}/", $timestamp)) { 81 | // This looks like a microtime float 82 | return (int)$timestamp; 83 | } 84 | 85 | // I don't know what you have, just going to generate a new timestamp 86 | return time(); 87 | } 88 | 89 | protected function formatDimensions(array $dimensions): array 90 | { 91 | return array_map(function ($key, $value) { 92 | return [ 93 | 'Name' => $key, 94 | 'Value' => $value 95 | ]; 96 | }, array_keys($dimensions), $dimensions); 97 | } 98 | 99 | /** 100 | * Pass through to the CloudWatch client anything we don't handle. 101 | * 102 | * @param $method 103 | * @param $parameters 104 | * 105 | * @return mixed 106 | */ 107 | public function __call($method, $parameters) 108 | { 109 | return $this->getClient()->$method(...$parameters); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Drivers/InfluxDB.php: -------------------------------------------------------------------------------- 1 | adapter = $adapter; 23 | } 24 | 25 | public function measurement( 26 | string $measurement, 27 | mixed $value = null, 28 | array $tags = [], 29 | array $fields = [], 30 | mixed $timestamp = null 31 | ): static 32 | { 33 | return $this->point( 34 | $this->adapter->point( 35 | $measurement, 36 | $value, 37 | array_merge($this->tags, $tags), 38 | array_merge($this->extra, $fields), 39 | $timestamp 40 | ) 41 | ); 42 | } 43 | 44 | public function point(IDBPoint|IDB2Point $point): static 45 | { 46 | $this->points[] = $point; 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * @throws Exception 53 | */ 54 | public function flush(): static 55 | { 56 | if (empty($this->getMetrics())) { 57 | return $this; 58 | } 59 | 60 | $this->send($this->getMetrics()); 61 | $this->metrics = []; 62 | 63 | if (count($this->points)) { 64 | $this->adapter->writePoints($this->points); 65 | $this->points = []; 66 | } 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * @throws Database\Exception 73 | */ 74 | public function format(Metric $metric): IDBPoint|IDB2Point 75 | { 76 | return $this->adapter->point( 77 | $metric->getName(), 78 | $metric->getValue() ?? 1, 79 | array_merge($this->tags, $metric->getTags()), 80 | array_merge($this->extra, $metric->getExtra()), 81 | $metric->getTimestamp() 82 | ); 83 | } 84 | 85 | /** 86 | * @throws Exception 87 | * @throws Database\Exception 88 | */ 89 | public function send($metrics): void 90 | { 91 | $this->adapter->writePoints( 92 | array_map(function ($metric) { 93 | return $this->format($metric); 94 | }, (array)$metrics) 95 | ); 96 | } 97 | 98 | public function getPoints(): array 99 | { 100 | return $this->points; 101 | } 102 | 103 | public function getWriteConnection(): Database|WriteApi|UdpWriter 104 | { 105 | return $this->adapter->getWriteConnection(); 106 | } 107 | 108 | /** 109 | * Pass through to the Influx adapter anything we don't handle. 110 | * 111 | * @param $method 112 | * @param $parameters 113 | * 114 | * @return mixed 115 | */ 116 | public function __call($method, $parameters): mixed 117 | { 118 | return $this->adapter->$method(...$parameters); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Drivers/LogDriver.php: -------------------------------------------------------------------------------- 1 | $metric->getName(), 18 | 'value' => $metric->getValue(), 19 | 'resolution' => $metric->getResolution(), 20 | 'unit' => $metric->getUnit(), 21 | 'tags' => $metric->getTags(), 22 | 'extra' => $metric->getExtra(), 23 | 'timestamp' => $metric->getTimestamp() 24 | ]); 25 | } 26 | 27 | public function flush(): static 28 | { 29 | if (!count($this->getMetrics())) { 30 | return $this; 31 | } 32 | 33 | $formatted = array_map([$this, 'format'], $this->getMetrics()); 34 | 35 | $this->logger->info("Metrics", $formatted); 36 | 37 | $this->metrics = []; 38 | 39 | return $this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Drivers/NullDriver.php: -------------------------------------------------------------------------------- 1 | format($metric)); 23 | 24 | return $this; 25 | } 26 | 27 | /** 28 | * PostHog sends batches automatically on __destruct, this really isn't necessary. 29 | * But we're including it in case you ever want to force send earlier on. 30 | */ 31 | public function flush(): static 32 | { 33 | PostHogClient::flush(); 34 | 35 | return $this; 36 | } 37 | 38 | public function format(Metric $metric): array 39 | { 40 | return [ 41 | 'distinctId' => $this->distinctId, 42 | 'event' => $metric->getName(), 43 | 'properties' => array_merge( 44 | $metric->getValue() 45 | ? ['value' => $metric->getValue()] 46 | : [], 47 | $this->extra, 48 | $metric->getExtra() 49 | ), 50 | ]; 51 | } 52 | 53 | /** 54 | * Pass through PostHog anything we don't handle. 55 | * 56 | * @param $method 57 | * @param $parameters 58 | * 59 | * @return mixed 60 | */ 61 | public function __call($method, $parameters) 62 | { 63 | return PostHogClient::$method(...$parameters); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Drivers/PrometheusDriver.php: -------------------------------------------------------------------------------- 1 | getNamespace()); 32 | $name = Str::snake($metric->getName()); 33 | $labelKeys = array_map(fn ($tag) => Str::snake($tag) , array_keys($metric->getTags())); 34 | return match ($metric->getType()) { 35 | MetricType::COUNTER => (function () use ($namespace, $name, $labelKeys, $metric) { 36 | $counter = $this->registry->getOrRegisterCounter($namespace, $name, $metric->getDescription() ?? '', $labelKeys); 37 | $counter->incBy($metric->getValue(), array_values($metric->getTags())); 38 | return $counter; 39 | })(), 40 | MetricType::GAUGE => (function () use ($namespace, $name, $labelKeys, $metric) { 41 | $gauge = $this->registry->getOrRegisterGauge($namespace, $name, $metric->getDescription() ?? '', $labelKeys); 42 | $gauge->set($metric->getValue(), array_values($metric->getTags())); 43 | return $gauge; 44 | })(), 45 | default => throw new UnhandledMatchError($metric->getType()), 46 | }; 47 | } 48 | 49 | public function flush(): static 50 | { 51 | $this->metrics = []; 52 | $this->registry->wipeStorage(); 53 | return $this; 54 | } 55 | 56 | /** 57 | * Renders all collected metrics in prometheus format. 58 | * The result can be directly exposed on HTTP endpoint, for polling by Prometheus. 59 | * 60 | * If execution is thrown no matter in the context of long-running process or http request, 61 | * there are handlers in @see MetricsServiceProvider to call flush and clear the state 62 | * 63 | * @return string 64 | * @throws MetricsRegistrationException 65 | * @throws UnhandledMatchError 66 | */ 67 | public function formatted(): string 68 | { 69 | // Always before formatting all metrics we need to wipe the registry storage and to register the metrics again. 70 | // If we don't, we will increment existing counters instead of replacing them. 71 | $this->registry->wipeStorage(); 72 | 73 | collect($this->getMetrics())->each(function (Metric $metric) { 74 | $this->format($metric); 75 | }); 76 | 77 | return $this->renderer->render($this->registry->getMetricFamilySamples()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Facades/Metrics.php: -------------------------------------------------------------------------------- 1 | setName($name); 62 | $this->setValue($value); 63 | 64 | $this->driver = $driver; 65 | } 66 | 67 | /** 68 | * @return int 69 | */ 70 | public function getResolution() 71 | { 72 | return $this->resolution; 73 | } 74 | 75 | /** 76 | * @param int $resolution 77 | * 78 | * @return $this 79 | */ 80 | public function setResolution($resolution) 81 | { 82 | $this->resolution = $resolution; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * @return string 89 | */ 90 | public function getName() 91 | { 92 | return $this->name; 93 | } 94 | 95 | /** 96 | * @param string $name 97 | * 98 | * @return $this 99 | */ 100 | public function setName($name) 101 | { 102 | $this->name = $name; 103 | 104 | return $this; 105 | } 106 | 107 | public function getValue(): mixed 108 | { 109 | return $this->value; 110 | } 111 | 112 | public function setValue(mixed $value): static 113 | { 114 | $this->value = $value; 115 | 116 | return $this; 117 | } 118 | 119 | public function getUnit(): string|null 120 | { 121 | return $this->unit; 122 | } 123 | 124 | public function setUnit(mixed $unit): static 125 | { 126 | $this->unit = $unit; 127 | 128 | return $this; 129 | } 130 | 131 | public function getTags(): array 132 | { 133 | return $this->tags; 134 | } 135 | 136 | public function setTags(array $tags): static 137 | { 138 | $this->tags = $tags; 139 | 140 | return $this; 141 | } 142 | 143 | public function addTag($key, $value): static 144 | { 145 | $this->tags[$key] = $value; 146 | 147 | return $this; 148 | } 149 | 150 | public function getExtra(): array 151 | { 152 | return $this->extra; 153 | } 154 | 155 | public function setExtra(array $extra): static 156 | { 157 | $this->extra = $extra; 158 | 159 | return $this; 160 | } 161 | 162 | public function addExtra($key, $value): static 163 | { 164 | $this->extra[$key] = $value; 165 | 166 | return $this; 167 | } 168 | 169 | public function getTimestamp(): mixed 170 | { 171 | return $this->timestamp; 172 | } 173 | 174 | public function setTimestamp($timestamp): static 175 | { 176 | $this->timestamp = $timestamp; 177 | 178 | return $this; 179 | } 180 | 181 | public function add(): AbstractDriver 182 | { 183 | return $this->getDriver()->add($this); 184 | } 185 | public function getDriver(): AbstractDriver 186 | { 187 | return $this->driver ?? app('metrics')->driver(); 188 | } 189 | 190 | public function setDriver(AbstractDriver $driver): static 191 | { 192 | $this->driver = $driver; 193 | 194 | return $this; 195 | } 196 | 197 | public function setNamespace(string $namespace): void 198 | { 199 | $this->namespace = $namespace; 200 | } 201 | 202 | public function getNamespace(): string 203 | { 204 | return $this->namespace; 205 | } 206 | 207 | public function format(): mixed 208 | { 209 | return $this->getDriver()->format($this); 210 | } 211 | 212 | public function formatted(): mixed 213 | { 214 | return $this->getDriver()->formatted(); 215 | } 216 | 217 | public function getType(): ?MetricType 218 | { 219 | return $this->type; 220 | } 221 | 222 | public function setType(?MetricType $type): static 223 | { 224 | $this->type = $type; 225 | 226 | return $this; 227 | } 228 | 229 | public function getDescription(): ?string 230 | { 231 | return $this->description; 232 | } 233 | 234 | public function setDescription(?string $description = null): static 235 | { 236 | $this->description = $description; 237 | 238 | return $this; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/MetricType.php: -------------------------------------------------------------------------------- 1 | driverCreatedCallback = $callback; 24 | 25 | return $this; 26 | } 27 | 28 | public function getDefaultDriver(): string 29 | { 30 | return $this->container['config']['metrics.default'] == null 31 | ? 'null' 32 | : $this->container['config']['metrics.default']; 33 | } 34 | 35 | protected function createDriver($driver) 36 | { 37 | $driver = parent::createDriver($driver); 38 | 39 | if($this->driverCreatedCallback) { 40 | call_user_func($this->driverCreatedCallback, $driver); 41 | } 42 | 43 | return $driver; 44 | } 45 | 46 | public function createInfluxdbDriver(): InfluxDB 47 | { 48 | return $this->container->make(InfluxDB::class); 49 | } 50 | 51 | public function createCloudwatchDriver(): CloudWatch 52 | { 53 | return $this->container->make(CloudWatch::class); 54 | } 55 | 56 | public function createPostHogDriver(): PostHog 57 | { 58 | return $this->container->make(PostHog::class); 59 | } 60 | 61 | public function createLogDriver(): LogDriver 62 | { 63 | return $this->container->make(LogDriver::class); 64 | } 65 | 66 | public function createNullDriver(): NullDriver 67 | { 68 | return new NullDriver(); 69 | } 70 | 71 | public function createPrometheusDriver(): PrometheusDriver 72 | { 73 | return $this->container->make(PrometheusDriver::class); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/MetricsServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(MetricsManager::class, function () { 38 | return $this->createManager(); 39 | }); 40 | 41 | $this->app->alias(MetricsManager::class, 'metrics'); 42 | 43 | $this->app->singleton(InfluxDB::class, function () { 44 | return $this->createInfluxDBDriver($this->app['config']['metrics.backends.influxdb']); 45 | }); 46 | 47 | $this->app->singleton(InfluxDB2::class, function () { 48 | return $this->createInfluxDB2Driver($this->app['config']['metrics.backends.influxdb2']); 49 | }); 50 | 51 | $this->app->singleton(CloudWatch::class, function () { 52 | return $this->createCloudWatchDriver($this->app['config']['metrics.backends.cloudwatch']); 53 | }); 54 | 55 | $this->app->singleton(PostHog::class, function () { 56 | return $this->createPostHogDriver($this->app['config']['metrics.backends.posthog']); 57 | }); 58 | 59 | $this->app->singleton(PrometheusDriver::class, function () { 60 | return $this->createPrometheusDriver($this->app['config']['metrics.backends.prometheus']); 61 | }); 62 | } 63 | 64 | /** 65 | * Bootstrap the application services. 66 | */ 67 | public function boot() 68 | { 69 | $this->setupConfig(); 70 | 71 | $this->setupListener(); 72 | } 73 | 74 | /** 75 | * @return array 76 | */ 77 | public function provides() 78 | { 79 | return ['metrics', MetricsManager::class, InfluxDB::class, CloudWatch::class]; 80 | } 81 | 82 | /** 83 | * Make sure we have config setup for Laravel and Lumen apps 84 | */ 85 | protected function setupConfig() 86 | { 87 | $source = realpath(__DIR__ . '/../config/metrics.php'); 88 | 89 | if ($this->app instanceof LaravelApplication) { 90 | $this->publishes([$source => config_path('metrics.php')]); 91 | } elseif ($this->app instanceof LumenApplication) { 92 | $this->app->configure('metrics'); 93 | } 94 | 95 | $this->mergeConfigFrom($source, 'metrics'); 96 | } 97 | 98 | /** 99 | * Global event listener 100 | */ 101 | protected function setupListener() 102 | { 103 | $this->app['events']->listen("*", function ($eventName, $payload) { 104 | $event = array_pop($payload); 105 | 106 | if (is_object($event) && $event instanceof ShouldReportMetric) { 107 | $this->app->make(MetricsManager::class)->driver() 108 | ->add($event->createMetric()); 109 | } 110 | }); 111 | 112 | if (class_exists(\Laravel\Octane\Events\RequestTerminated::class)) { 113 | $this->app['events']->listen(\Laravel\Octane\Events\RequestTerminated::class, FlushMetrics::class); 114 | } 115 | } 116 | 117 | /** 118 | * @return MetricsManager 119 | */ 120 | protected function createManager() 121 | { 122 | $metrics = new MetricsManager($this->app); 123 | 124 | // Flush all queued metrics when PHP shuts down 125 | register_shutdown_function(function () use ($metrics) { 126 | foreach ($metrics->getDrivers() AS $driver) { 127 | $driver->flush(); 128 | } 129 | }); 130 | 131 | return $metrics; 132 | } 133 | 134 | /** 135 | * @return InfluxDB 136 | */ 137 | protected function createInfluxDBDriver(array $config) 138 | { 139 | $version = Arr::get($config, 'version', 2); 140 | 141 | if ($version == 2) { 142 | return new InfluxDB($this->createInfluxDB2Adapter($config)); 143 | } 144 | 145 | return new InfluxDB($this->createInfluxDBAdapter($config)); 146 | } 147 | 148 | /** 149 | * @return InfluxDB2Adapter 150 | */ 151 | protected function createInfluxDB2Adapter(array $config) 152 | { 153 | $opts = [ 154 | 'url' => sprintf("%s:%s", 155 | $config['host'], 156 | $config['tcp_port'] 157 | ), 158 | 'token' => $config['token'], 159 | 'bucket' => $config['database'], 160 | 'org' => $config['org'], 161 | 'precision' => WritePrecision::NS 162 | ]; 163 | 164 | if ($udpPort = Arr::get($config, 'udp_port', null)) { 165 | $opts['udpPort'] = $udpPort; 166 | } 167 | 168 | return new InfluxDB2Adapter( 169 | new \InfluxDB2\Client($opts), 170 | ! empty($udpPort) 171 | ); 172 | } 173 | 174 | /** 175 | * @return InfluxDB1Adapter 176 | */ 177 | protected function createInfluxDBAdapter(array $config) 178 | { 179 | $tcpConnection = Client::fromDSN( 180 | sprintf('influxdb://%s:%s@%s:%s/%s', 181 | $config['username'], 182 | $config['password'], 183 | $config['host'], 184 | $config['tcp_port'], 185 | $config['database'] 186 | ) 187 | ); 188 | 189 | $udpConnection = (Arr::has($config, 'udp_port') && !empty($config['udp_port'])) 190 | ? Client::fromDSN(sprintf('udp+influxdb://%s:%s@%s:%s/%s', 191 | Arr::get($config, 'username', 'default'), // Not required for UDP 192 | Arr::get($config, 'password', 'default'), // Not required for UDP 193 | $config['host'], 194 | $config['udp_port'], 195 | Arr::get($config, 'database', 'default') // Not required for UDP 196 | )) 197 | : null; 198 | 199 | return new InfluxDB1Adapter( 200 | $tcpConnection, 201 | $udpConnection 202 | ); 203 | } 204 | 205 | /** 206 | * Note this assumes you have AWS itself configured properly! 207 | * 208 | * @param array $config 209 | * 210 | * @return CloudWatch 211 | */ 212 | protected function createCloudWatchDriver(array $config) 213 | { 214 | $opts = [ 215 | 'region' => Arr::get($config, 'region'), 216 | 'version' => '2010-08-01', 217 | ]; 218 | 219 | // Add credentials if they've been defined, else fallback to loading 220 | // credentials from the environment. 221 | $key = Arr::get($config, 'key'); 222 | $secret = Arr::get($config, 'secret'); 223 | if ($key !== null && $secret !== null) { 224 | $opts['credentials'] = [ 225 | 'key' => $key, 226 | 'secret' => $secret, 227 | ]; 228 | } 229 | 230 | return new CloudWatch(new CloudWatchClient($opts), $config['namespace']); 231 | } 232 | 233 | protected function createPostHogDriver(array $config) 234 | { 235 | \PostHog\PostHog::init($config['key'], [ 236 | 'host' => $config['host'], 237 | ]); 238 | 239 | return new PostHog( 240 | match(true) { 241 | auth()->check() => $config['distinct_prefix'] . auth()->id(), 242 | session()->isStarted() => sha1(session()->getId()), 243 | default => Str::random() 244 | } 245 | ); 246 | } 247 | 248 | protected function createPrometheusDriver() 249 | { 250 | return new PrometheusDriver(new RenderTextFormat(), new CollectorRegistry(new InMemory(), false)); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/Octane/Listeners/FlushMetrics.php: -------------------------------------------------------------------------------- 1 | getDrivers() as $driver) { 12 | $driver->flush(); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Traits/ComputesNanosecondTimestamps.php: -------------------------------------------------------------------------------- 1 | getTimestamp() * self::TO_NANO; 20 | } elseif (is_int($timestamp)) { 21 | $length = strlen((string) $timestamp); 22 | 23 | return match ($length) { 24 | // Looks like it is already nanosecond precise! 25 | 19 => $timestamp, 26 | // This appears to be in seconds 27 | 10 => $timestamp * self::TO_NANO, 28 | default => $this->generateTimestamp(), 29 | }; 30 | } elseif (is_string($timestamp)) { 31 | if (preg_match("/\d{10}\.\d{1,4}$/", $timestamp)) { 32 | return (int) ($timestamp * self::TO_NANO); 33 | } elseif (ctype_digit($timestamp)) { 34 | $length = strlen($timestamp); 35 | 36 | return match ($length) { 37 | // Looks like it is already nanosecond precise! 38 | 19 => (int) $timestamp, 39 | // This appears to be in seconds 40 | 10 => (int) ($timestamp * self::TO_NANO), 41 | default => $this->generateTimestamp(), 42 | }; 43 | } 44 | } elseif (is_float($timestamp)) { 45 | $integerLength = (int) floor(log10(abs($timestamp))) + 1; 46 | 47 | return match ($integerLength) { 48 | // Looks like it is already nanosecond precise! 49 | 19 => (int) $timestamp, 50 | // This appears to be in seconds 51 | 10 => (int) ($timestamp * self::TO_NANO), 52 | default => $this->generateTimestamp(), 53 | }; 54 | } 55 | 56 | // We weren't given a valid timestamp, generate. 57 | return $this->generateTimestamp(); 58 | } 59 | 60 | /** 61 | * @return int 62 | */ 63 | protected function generateTimestamp(): int 64 | { 65 | return (int) (microtime(true) * self::TO_NANO); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Traits/ProvidesMetric.php: -------------------------------------------------------------------------------- 1 | getMetricName())) 17 | ->setType($this->getMetricType()) 18 | ->setValue($this->getMetricValue()) 19 | ->setUnit($this->getMetricUnit()) 20 | ->setTags($this->getMetricTags()) 21 | ->setExtra($this->getMetricExtra()) 22 | ->setTimestamp($this->getMetricTimestamp()) 23 | ->setResolution($this->getMetricResolution()) 24 | ->setDescription($this->getMetricDescription()); 25 | } 26 | 27 | public function getMetricName(): string 28 | { 29 | return property_exists($this, 'metricName') 30 | ? $this->metricName 31 | : Str::snake((new \ReflectionClass($this))->getShortName()); 32 | } 33 | 34 | public function getMetricValue(): mixed 35 | { 36 | return property_exists($this, 'metricValue') 37 | ? $this->metricValue 38 | : 1; 39 | } 40 | 41 | public function getMetricUnit(): string|null 42 | { 43 | return property_exists($this, 'metricUnit') 44 | ? $this->metricUnit 45 | : null; 46 | } 47 | 48 | public function getMetricTags(): array 49 | { 50 | return property_exists($this, 'metricTags') 51 | ? $this->metricTags 52 | : []; 53 | } 54 | 55 | public function getMetricExtra(): array 56 | { 57 | return property_exists($this, 'metricExtra') 58 | ? $this->metricExtra 59 | : []; 60 | } 61 | 62 | public function getMetricTimestamp() 63 | { 64 | return property_exists($this, 'metricTimestamp') 65 | ? $this->metricTimestamp 66 | : new \DateTime; 67 | } 68 | 69 | public function getMetricResolution(): int|null 70 | { 71 | return property_exists($this, 'metricResolution') 72 | ? $this->metricResolution 73 | : null; 74 | } 75 | 76 | public function getMetricType(): ?MetricType 77 | { 78 | return property_exists($this, 'metricType') 79 | ? $this->metricType 80 | : null; 81 | } 82 | 83 | public function getMetricDescription(): ?string 84 | { 85 | return property_exists($this, 'metricDescription') 86 | ? $this->metricDescription 87 | : null; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/CloudWatchDriverTest.php: -------------------------------------------------------------------------------- 1 | setupCloudWatch(); 9 | 10 | $metric = (new \STS\Metrics\Metric("file_uploaded")) 11 | ->setResolution(1) 12 | ->setValue(50) 13 | ->setUnit('Megabytes') 14 | ->setTags(['user' => 54]); 15 | 16 | $formatted = app(CloudWatch::class)->format($metric); 17 | 18 | $this->assertEquals("file_uploaded", $formatted['MetricName']); 19 | $this->assertEquals(50, $formatted['Value']); 20 | $this->assertEquals(54, $formatted['Dimensions'][0]['Value']); 21 | $this->assertEquals(1, $formatted['StorageResolution']); 22 | $this->assertEquals('Megabytes', $formatted['Unit']); 23 | } 24 | 25 | public function testDefaultTimestampFormatting() 26 | { 27 | $this->setupCloudWatch(); 28 | 29 | $metric = (new \STS\Metrics\Metric("file_uploaded")) 30 | ->setResolution(1) 31 | ->setValue(50) 32 | ->setUnit('Megabytes') 33 | ->setTags(['user' => 54]); 34 | 35 | Metrics::add($metric); 36 | 37 | $this->assertTrue(is_int(Metrics::format($metric)['Timestamp'])); 38 | } 39 | 40 | public function testDefaultTagsExtra() 41 | { 42 | $this->setupCloudWatch(); 43 | $driver = app(CloudWatch::class); 44 | 45 | $driver->setTags(['tag1' => 'tag_value'])->setExtra(['extra1' => 'extra_value']); 46 | 47 | $metric = (new \STS\Metrics\Metric("my_metric")) 48 | ->setTags(['foo' => 'bar']); 49 | 50 | $formatted = $driver->format($metric); 51 | 52 | $this->assertCount(2, $formatted['Dimensions']); 53 | $this->assertEquals("tag1", $formatted['Dimensions'][0]['Name']); 54 | $this->assertEquals("tag_value", $formatted['Dimensions'][0]['Value']); 55 | $this->assertEquals("foo", $formatted['Dimensions'][1]['Name']); 56 | $this->assertEquals("bar", $formatted['Dimensions'][1]['Value']); 57 | } 58 | 59 | public function testPassthru() 60 | { 61 | $this->setupCloudWatch([], false); 62 | $driver = app(CloudWatch::class); 63 | 64 | // This call passes through our driver to the underlying influx CloudWatchClient class 65 | $this->assertInstanceOf(\Aws\Api\Service::class, $driver->getApi()); 66 | } 67 | 68 | public function testZeroValue() 69 | { 70 | $this->setupCloudWatch(); 71 | 72 | $metric = (new \STS\Metrics\Metric("file_uploaded")) 73 | ->setValue(0); 74 | 75 | $formatted = app(CloudWatch::class)->format($metric); 76 | 77 | $this->assertEquals(0, $formatted['Value']); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/CloudWatchEventListeningTest.php: -------------------------------------------------------------------------------- 1 | setupCloudWatch(); 10 | } 11 | 12 | public function testBasicEventMetricAdded() 13 | { 14 | event(new BasicCloudWatchEvent); 15 | Metrics::flush(); 16 | 17 | $this->assertEquals(1, count($GLOBALS['metrics']['MetricData'])); 18 | $this->assertEquals("basic_cloud_watch_event", $GLOBALS['metrics']['MetricData'][0]['MetricName']); 19 | } 20 | 21 | public function testMetricWithAttributes() 22 | { 23 | event(new CloudWatchEventWithAttributes); 24 | Metrics::flush(); 25 | 26 | /** @var \InfluxDB\Point $metric */ 27 | $metric = $GLOBALS['metrics']['MetricData'][0]; 28 | $this->assertEquals("order_placed", $metric['MetricName']); 29 | $this->assertEquals("email", $metric['Dimensions'][0]['Value']); 30 | $this->assertEquals(1508701523, $metric['Timestamp']); 31 | } 32 | 33 | public function testMetricWithGetters() 34 | { 35 | event(new CloudWatchEventWithGetters()); 36 | Metrics::flush(); 37 | 38 | /** @var \InfluxDB\Point $metric */ 39 | $metric = $GLOBALS['metrics']['MetricData'][0]; 40 | $this->assertEquals("user_registered", $metric['MetricName']); 41 | $this->assertEquals("1", $metric['Value']); 42 | $this->assertEquals(false, $metric['Dimensions'][0]['Value']); 43 | $this->assertEquals(1508702054, $metric['Timestamp']); 44 | } 45 | } 46 | 47 | class BasicCloudWatchEvent implements \STS\Metrics\Contracts\ShouldReportMetric { 48 | use STS\Metrics\Traits\ProvidesMetric; 49 | } 50 | 51 | class CloudWatchEventWithAttributes implements \STS\Metrics\Contracts\ShouldReportMetric { 52 | use STS\Metrics\Traits\ProvidesMetric; 53 | 54 | protected $metricName = "order_placed"; 55 | protected $metricValue = 5; 56 | protected $metricTags = ["source" => "email"]; 57 | protected $metricExtra = ["item_count" => 10]; 58 | protected $metricTimestamp = 1508701523; 59 | } 60 | 61 | class CloudWatchEventWithGetters implements \STS\Metrics\Contracts\ShouldReportMetric { 62 | use STS\Metrics\Traits\ProvidesMetric; 63 | 64 | public function getMetricName() 65 | { 66 | return "user_registered"; 67 | } 68 | 69 | public function getMetricValue() 70 | { 71 | return 1; 72 | } 73 | 74 | public function getMetricTags() 75 | { 76 | return ["admin" => false]; 77 | } 78 | 79 | public function getMetricExtra() 80 | { 81 | return ["company_id" => 50]; 82 | } 83 | 84 | public function getMetricTimestamp() 85 | { 86 | return 1508702054; 87 | } 88 | } 89 | ; 90 | -------------------------------------------------------------------------------- /tests/InfluxDBAdapterTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(1508713728000000000, $stub->getNanoSecondTimestamp(1508713728000000000)); 26 | $this->assertEquals(1508713728000000000, $stub->getNanoSecondTimestamp(1508713728)); 27 | $this->assertEquals(1508713728000000000, $stub->getNanoSecondTimestamp(new \DateTime('@1508713728'))); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /tests/InfluxDBDriverTest.php: -------------------------------------------------------------------------------- 1 | setupInfluxDB(['tcp_port' => 123, 'udp_port' => 456]); 8 | 9 | // Since we provided a UDP port, that will be our write client 10 | $this->assertInstanceOf( 11 | \InfluxDB\Driver\UDP::class, 12 | app(InfluxDB::class)->getWriteConnection()->getClient()->getDriver() 13 | ); 14 | } 15 | 16 | public function testTcpWriteClient() 17 | { 18 | $this->setupInfluxDB(['tcp_port' => 123]); 19 | 20 | // Without a UDP port, we will get a TCP client 21 | $this->assertInstanceOf( 22 | \InfluxDB\Driver\Guzzle::class, 23 | app(InfluxDB::class)->getWriteConnection()->getClient()->getDriver() 24 | ); 25 | } 26 | 27 | public function testDefaultTagsExtra() 28 | { 29 | $this->setupInfluxDB(); 30 | 31 | $driver = app(InfluxDB::class); 32 | 33 | $driver->setTags(['tag1' => 'tag_value'])->setExtra(['extra1' => 'extra_value']); 34 | 35 | $metric = (new \STS\Metrics\Metric("my_metric")) 36 | //->setValue(1) 37 | ->setTags(['foo' => 'bar']); 38 | 39 | $point = $driver->format($metric); 40 | 41 | $this->assertCount(2, $point->getTags()); 42 | $this->assertEquals("tag_value", $point->getTags()['tag1']); 43 | 44 | $this->assertCount(2, $point->getFields()); 45 | $this->assertEquals('"extra_value"', $point->getFields()['extra1']); 46 | } 47 | 48 | public function testPassthru() 49 | { 50 | $this->setupInfluxDB(['database' => 'dbname'], false); 51 | $driver = app(InfluxDB::class); 52 | 53 | // This call passes through our driver to the underlying influx Database class 54 | $this->assertEquals("dbname", $driver->getName()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/InfluxDBEventListeningTest.php: -------------------------------------------------------------------------------- 1 | setupInfluxDB(); 10 | } 11 | 12 | public function testBasicEventMetricAdded() 13 | { 14 | event(new BasicEvent); 15 | Metrics::flush(); 16 | 17 | $this->assertEquals(1, count($GLOBALS['points'])); 18 | $this->assertEquals("basic_event", $GLOBALS['points'][0]->getMeasurement()); 19 | } 20 | 21 | public function testMetricWithAttributes() 22 | { 23 | event(new EventWithAttributes); 24 | Metrics::flush(); 25 | 26 | /** @var \InfluxDB\Point $point */ 27 | $point = $GLOBALS['points'][0]; 28 | $this->assertEquals("order_placed", $point->getMeasurement()); 29 | $this->assertEquals("5i", $point->getFields()['value']); 30 | $this->assertEquals("email", $point->getTags()['source']); 31 | $this->assertEquals('10i', $point->getFields()['item_count']); 32 | $this->assertEquals(1508701523000000000, $point->getTimestamp()); 33 | } 34 | 35 | public function testMetricWithGetters() 36 | { 37 | event(new EventWithGetters()); 38 | Metrics::flush(); 39 | 40 | /** @var \InfluxDB\Point $point */ 41 | $point = $GLOBALS['points'][0]; 42 | $this->assertEquals("user_registered", $point->getMeasurement()); 43 | $this->assertEquals("1i", $point->getFields()['value']); 44 | $this->assertEquals("false", $point->getTags()['admin']); 45 | $this->assertEquals('50i', $point->getFields()['company_id']); 46 | $this->assertEquals(1508702054000000000, $point->getTimestamp()); 47 | } 48 | } 49 | 50 | class BasicEvent implements \STS\Metrics\Contracts\ShouldReportMetric { 51 | use STS\Metrics\Traits\ProvidesMetric; 52 | } 53 | 54 | class EventWithAttributes implements \STS\Metrics\Contracts\ShouldReportMetric { 55 | use STS\Metrics\Traits\ProvidesMetric; 56 | 57 | protected $metricName = "order_placed"; 58 | protected $metricValue = 5; 59 | protected $metricTags = ["source" => "email"]; 60 | protected $metricExtra = ["item_count" => 10]; 61 | protected $metricTimestamp = 1508701523; 62 | } 63 | 64 | class EventWithGetters implements \STS\Metrics\Contracts\ShouldReportMetric { 65 | use STS\Metrics\Traits\ProvidesMetric; 66 | 67 | public function getMetricName() 68 | { 69 | return "user_registered"; 70 | } 71 | 72 | public function getMetricValue() 73 | { 74 | return 1; 75 | } 76 | 77 | public function getMetricTags() 78 | { 79 | return ["admin" => false]; 80 | } 81 | 82 | public function getMetricExtra() 83 | { 84 | return ["company_id" => 50]; 85 | } 86 | 87 | public function getMetricTimestamp() 88 | { 89 | return 1508702054; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/InfluxDBV2DriverTest.php: -------------------------------------------------------------------------------- 1 | setupInfluxDB2(['tcp_port' => 123, 'udp_port' => 456], false); 11 | 12 | $this->assertInstanceOf( 13 | \InfluxDB2\UdpWriter::class, 14 | app(InfluxDB::class)->getWriteConnection() 15 | ); 16 | } 17 | 18 | public function testTcpWriteClient() 19 | { 20 | $this->setupInfluxDB2(['tcp_port' => 123]); 21 | 22 | $this->assertInstanceOf( 23 | \InfluxDB2\WriteApi::class, 24 | app(InfluxDB::class)->getWriteConnection() 25 | ); 26 | } 27 | 28 | /** 29 | * @dataProvider nanoSecondTimestampInvalid 30 | */ 31 | public function testNanoSecondTimestampInvalid($input) 32 | { 33 | $this->setupInfluxDB(); 34 | 35 | $influx = app(InfluxDB::class); 36 | 37 | $now = $influx->getNanoSecondTimestamp(); 38 | $result = $influx->getNanoSecondTimestamp($input); 39 | 40 | $this->assertTrue(is_int($result)); 41 | $this->assertEquals(19, strlen((string) $result)); 42 | $this->assertGreaterThanOrEqual($now, $result); 43 | } 44 | 45 | /** 46 | * @dataProvider nanoSecondTimestampValid 47 | */ 48 | public function testNanoSecondTimestamp($expected, $input) 49 | { 50 | $this->setupInfluxDB(); 51 | 52 | $influx = app(InfluxDB::class); 53 | 54 | $result = $influx->getNanoSecondTimestamp($input); 55 | 56 | $this->assertTrue(is_int($result)); 57 | $this->assertEquals(19, strlen((string) $result)); 58 | $this->assertEquals($expected, $result); 59 | } 60 | 61 | public static function nanoSecondTimestampValid() 62 | { 63 | $expected = 1508713728000000000; 64 | $expectedPrecise = 1508713728123400000; 65 | 66 | return [ 67 | [$expected, 1508713728000000000,], 68 | [$expected, 1508713728,], 69 | [$expected, '1508713728000000000',], 70 | [$expected, '1508713728',], 71 | [$expected, new \DateTime('@1508713728'),], 72 | [$expected, '1508713728.0000',], 73 | [$expected, '1508713728.000',], 74 | [$expected, '1508713728.00',], 75 | [$expected, '1508713728.0',], 76 | [$expected, 1508713728.0000,], 77 | [$expected, 1508713728.000,], 78 | [$expected, 1508713728.00,], 79 | [$expected, 1508713728.0,], 80 | [$expectedPrecise, 1508713728123400000,], 81 | [$expectedPrecise, '1508713728123400000',], 82 | // [$expectedPrecise, '1508713728.1234',], // PHP float precision breaks this 83 | // [1508713728123000000, '1508713728.123',], // PHP float precision breaks this 84 | [1508713728120000000, '1508713728.12',], 85 | [1508713728100000000, '1508713728.1',], 86 | // [1508713728123400000, 1508713728.1234,], // PHP float precision breaks this 87 | // [1508713728123000000, 1508713728.123,], // PHP float precision breaks this 88 | [1508713728120000000, 1508713728.12,], 89 | [1508713728100000000, 1508713728.1,], 90 | ]; 91 | } 92 | 93 | public static function nanoSecondTimestampInvalid() 94 | { 95 | return [ 96 | ['abc'], // letters 97 | ['150871372800000000a',], // numbers with letters 98 | [150871372800000000,], // 18 digits 99 | [15087137281,], // 11 digits 100 | [150871372,], // 9 digits 101 | [15087137,], // 8 digits 102 | [0,], 103 | [0.0,], 104 | ['000000000.1',], 105 | ['000000000.0',], 106 | ]; 107 | } 108 | } -------------------------------------------------------------------------------- /tests/LogDriverTest.php: -------------------------------------------------------------------------------- 1 | set('metrics.default', 'log'); 10 | 11 | $manager = app(\STS\Metrics\MetricsManager::class); 12 | 13 | $this->assertInstanceOf(LogDriver::class, $manager->driver()); 14 | } 15 | 16 | public function testFormat() 17 | { 18 | $driver = app(LogDriver::class); 19 | $metric = (new \STS\Metrics\Metric("my_metric")); 20 | $this->assertEquals( 21 | array_filter([ 22 | 'name' => $metric->getName(), 23 | 'value' => $metric->getValue(), 24 | 'resolution' => $metric->getResolution(), 25 | 'unit' => $metric->getUnit(), 26 | 'tags' => $metric->getTags(), 27 | 'extra' => $metric->getExtra(), 28 | 'timestamp' => $metric->getTimestamp() 29 | ]), 30 | $driver->format($metric) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/MetricTest.php: -------------------------------------------------------------------------------- 1 | setupInfluxDB(); 10 | 11 | (new Metric("my_metric", 5)) 12 | ->setTags(['foo' => 'bar']) 13 | ->add(); 14 | 15 | $this->assertEquals(1, count(Metrics::getMetrics())); 16 | $this->assertEquals("my_metric", Metrics::getMetrics()[0]->getName()); 17 | $this->assertEquals(5, Metrics::getMetrics()[0]->getValue()); 18 | } 19 | 20 | public function testCreatedFromDriver() 21 | { 22 | $this->setupInfluxDB(); 23 | 24 | // Since it is created from the driver, there is no need to call add() at the end; 25 | Metrics::create("my_metric") 26 | ->setValue(5) 27 | ->setTags(['foo' => 'bar']); 28 | 29 | // But it won't be a 'point' until we flush. This happens at the end of the PHP process. 30 | Metrics::flush(); 31 | 32 | $this->assertEquals(1, count($GLOBALS['points'])); 33 | $this->assertEquals("my_metric", $GLOBALS['points'][0]->getMeasurement()); 34 | } 35 | 36 | public function testDefaultTimestampWhenAdding() 37 | { 38 | $metric = new Metric('my_metric', 1); 39 | 40 | $this->assertNull($metric->getTimestamp()); 41 | 42 | Metrics::add($metric); 43 | 44 | $this->assertInstanceOf(\DateTime::class, $metric->getTimestamp()); 45 | } 46 | 47 | public function testDefaultTimestampWhenCreatingFromDriver() 48 | { 49 | Metrics::create("my_metric") 50 | ->setValue(5) 51 | ->setTags(['foo' => 'bar']); 52 | 53 | $this->assertInstanceOf(\DateTime::class, Metrics::getMetrics()[0]->getTimestamp()); 54 | } 55 | 56 | public function testGivenTimestampIsntChanged() 57 | { 58 | $metric = new Metric('my_metric', 1); 59 | $time = time(); 60 | 61 | $metric->setTimestamp($time); 62 | 63 | $this->assertEquals($time, $metric->getTimestamp()); 64 | 65 | Metrics::add($metric); 66 | 67 | $this->assertEquals($time, $metric->getTimestamp()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/NullDriverTest.php: -------------------------------------------------------------------------------- 1 | set('metrics.default', null); 11 | 12 | $manager = app(MetricsManager::class); 13 | 14 | $this->assertInstanceOf(NullDriver::class, $manager->driver()); 15 | } 16 | 17 | public function testEmptyFormat() 18 | { 19 | $driver = app(NullDriver::class); 20 | 21 | $metric = (new Metric("my_metric")); 22 | 23 | $this->assertEquals([], $driver->format($metric)); 24 | } 25 | 26 | public function testDoesntFlush() 27 | { 28 | $driver = app(NullDriver::class); 29 | 30 | $metric = (new Metric("my_metric")); 31 | $driver->add($metric); 32 | 33 | // Make sure we DO keep track of metrics 34 | $this->assertEquals(1, count($driver->getMetrics())); 35 | 36 | // But nothing happens when we flush 37 | $driver->flush(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/PostHogDriverTest.php: -------------------------------------------------------------------------------- 1 | setupPostHog(); 9 | 10 | $metric = (new \STS\Metrics\Metric("file_uploaded")) 11 | ->setExtra(["foo" => "bar"]) 12 | ->setValue(5); 13 | 14 | $formatted = app(PostHog::class)->format($metric); 15 | 16 | $this->assertEquals("file_uploaded", $formatted['event']); 17 | $this->assertEquals('bar', $formatted['properties']['foo']); 18 | $this->assertEquals(5, $formatted['properties']['value']); 19 | 20 | } 21 | 22 | public function testNoDefaultValue() 23 | { 24 | $this->setupPostHog(); 25 | 26 | $metric = (new \STS\Metrics\Metric("file_uploaded")); 27 | 28 | $formatted = app(PostHog::class)->format($metric); 29 | 30 | $this->assertFalse(isset($formatted['properties']['value'])); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/PrometheusDriverTest.php: -------------------------------------------------------------------------------- 1 | setDriver($driver) 17 | ->setType(MetricType::COUNTER) 18 | ->setTags(['foo' => 'bar', 'test' => 1]) 19 | ->setValue(5); 20 | 21 | $formatted = app(PrometheusDriver::class)->format($metric); 22 | 23 | $this->assertTrue($formatted instanceof Counter); 24 | $this->assertEquals(['foo', 'test'], $formatted->getLabelNames()); 25 | $this->assertEquals('counter', $formatted->getType()); 26 | 27 | } 28 | 29 | public function testItThrowsOnUnknownType() 30 | { 31 | /** @var PrometheusDriver $driver */ 32 | $driver = app(PrometheusDriver::class); 33 | 34 | $metric = (new Metric("file_uploaded")) 35 | ->setDriver($driver) 36 | ->setTags(['foo' => 'bar', 'test' => 1]) 37 | ->setValue(5); 38 | 39 | $this->expectException(\UnhandledMatchError::class); 40 | app(PrometheusDriver::class)->format($metric); 41 | } 42 | 43 | public function testItCanFormatCounterMetricsCorrectly() 44 | { 45 | /** @var PrometheusDriver $driver */ 46 | $driver = app(PrometheusDriver::class); 47 | 48 | $this->assertEquals("\n", $driver->formatted()); 49 | 50 | (new Metric("file_uploaded")) 51 | ->setType(MetricType::COUNTER) 52 | ->setDriver($driver) 53 | ->setTags(['foo' => 'bar', 'test' => 1]) 54 | ->setValue(5) 55 | ->setDescription('description') 56 | ->add(); 57 | 58 | $this->assertEquals("# HELP app_file_uploaded description\n# TYPE app_file_uploaded counter\napp_file_uploaded{foo=\"bar\",test=\"1\"} 5\n", $driver->formatted()); 59 | 60 | // Second format of all metrics produces the same result, no increment on the counter or flushing the registry 61 | $this->assertEquals("# HELP app_file_uploaded description\n# TYPE app_file_uploaded counter\napp_file_uploaded{foo=\"bar\",test=\"1\"} 5\n", $driver->formatted()); 62 | 63 | // flushing the driver wipes out all metrics and cleanups the underlying registry 64 | $driver->flush(); 65 | $this->assertEmpty($driver->getMetrics()); 66 | $this->assertEquals("\n", $driver->formatted()); 67 | } 68 | 69 | 70 | public function testItCanFormatGaugeMetricsCorrectly() 71 | { 72 | /** @var PrometheusDriver $driver */ 73 | $driver = app(PrometheusDriver::class); 74 | 75 | $this->assertEquals("\n", $driver->formatted()); 76 | 77 | (new Metric("file_uploaded")) 78 | ->setType(MetricType::GAUGE) 79 | ->setDriver($driver) 80 | ->setTags(['foo' => 'bar', 'test' => 1]) 81 | ->setDescription('Average time file upload took in seconds') 82 | ->setValue(5) 83 | ->add(); 84 | 85 | $this->assertEquals("# HELP app_file_uploaded Average time file upload took in seconds\n# TYPE app_file_uploaded gauge\napp_file_uploaded{foo=\"bar\",test=\"1\"} 5\n", $driver->formatted()); 86 | 87 | // Second format of all metrics produces the same result, no increment on the counter or flushing the registry 88 | $this->assertEquals("# HELP app_file_uploaded Average time file upload took in seconds\n# TYPE app_file_uploaded gauge\napp_file_uploaded{foo=\"bar\",test=\"1\"} 5\n", $driver->formatted()); 89 | 90 | // flushing the driver wipes out all metrics and cleanups the underlying registry 91 | $driver->flush(); 92 | $this->assertEmpty($driver->getMetrics()); 93 | $this->assertEquals("\n", $driver->formatted()); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /tests/PrometheusEventListeningTest.php: -------------------------------------------------------------------------------- 1 | setupPrometheus(); 11 | } 12 | 13 | public function testBasicCounterMetricAdded() 14 | { 15 | event(new BasicPrometheusCounterEvent); 16 | 17 | $driver = app(\STS\Metrics\Drivers\PrometheusDriver::class); 18 | $this->assertEquals("# HELP app_order_placed \n# TYPE app_order_placed counter\napp_order_placed{deployment=\"local\",tenant=\"develop\",source=\"email\"} 5\n", $driver->formatted()); 19 | 20 | // flushing, wipes out the stored metrics 21 | \STS\Metrics\Facades\Metrics::flush(); 22 | $this->assertEquals("\n", $driver->formatted()); 23 | } 24 | } 25 | 26 | class BasicPrometheusCounterEvent implements \STS\Metrics\Contracts\ShouldReportMetric { 27 | use STS\Metrics\Traits\ProvidesMetric; 28 | 29 | protected \STS\Metrics\MetricType $metricType = \STS\Metrics\MetricType::COUNTER; 30 | 31 | protected $metricName = "order_placed"; 32 | protected $metricValue = 5; 33 | protected $metricTags = ["Deployment" => "local", "tenant" => "develop", "source" => "email"]; 34 | protected $metricExtra = ["item_count" => 10]; 35 | protected $metricTimestamp = 1508701523; 36 | } 37 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | Metrics::class 16 | ]; 17 | } 18 | 19 | protected function setupInfluxDB($config = [], $mock = true) 20 | { 21 | app('config')->set('metrics.default', 'influxdb'); 22 | app('config')->set('metrics.backends.influxdb', array_merge([ 23 | 'username' => 'foo', 24 | 'password' => 'bar', 25 | 'host' => 'localhost', 26 | 'database' => 'baz', 27 | 'version' => 1, 28 | 'tcp_port' => 8086 29 | ], $config)); 30 | 31 | if($mock) { 32 | $mock = Mockery::mock(\InfluxDB\Database::class, ["db_name", Metrics::getWriteConnection()->getClient()])->makePartial(); 33 | $mock->shouldReceive('writePoints') 34 | ->andReturnUsing(function ($points) { 35 | $GLOBALS['points'] = $points; 36 | }); 37 | 38 | Metrics::setWriteConnection($mock); 39 | } 40 | } 41 | 42 | protected function setupInfluxDB2($config = [], $mock = true) 43 | { 44 | app('config')->set('metrics.default', 'influxdb'); 45 | app('config')->set('metrics.backends.influxdb', array_merge([ 46 | 'token' => 'foo', 47 | 'host' => 'localhost', 48 | 'tcp_port' => 8086, 49 | 'database' => 'baz', 50 | 'version' => 2, 51 | 'org' => 'bar' 52 | ], $config)); 53 | 54 | if ($mock) { 55 | $mock = Mockery::mock(\InfluxDB2\WriteApi::class); 56 | $mock->shouldReceive('write') 57 | ->andReturnUsing(function ($points) { 58 | $GLOBALS['points'] = $points; 59 | }); 60 | Metrics::setWriteConnection($mock); 61 | } 62 | } 63 | 64 | protected function setupCloudWatch($config = [], $mock = true) 65 | { 66 | app('config')->set('metrics.default', 'cloudwatch'); 67 | app('config')->set('metrics.backends.cloudwatch.namespace', 'Testing'); 68 | app('config')->set('metrics.backends.cloudwatch.key', 'Testing'); 69 | app('config')->set('metrics.backends.cloudwatch.secret', 'Testing'); 70 | 71 | if($mock) { 72 | $mock = Mockery::mock(\Aws\CloudWatch\CloudWatchClient::class)->makePartial(); 73 | $mock->shouldReceive('putMetricData') 74 | ->andReturnUsing(function($args) { 75 | $GLOBALS['metrics'] = $args; 76 | }); 77 | Metrics::setClient($mock); 78 | } 79 | } 80 | 81 | protected function setupPostHog($config = [], $mock = true) 82 | { 83 | app('config')->set('metrics.default', 'posthog'); 84 | app('config')->set('metrics.backends.posthog.key', 'Testing'); 85 | } 86 | 87 | protected function setupLogDriver($config = [], $mock = true) 88 | { 89 | app('config')->set('metrics.default', 'log'); 90 | 91 | if ($mock) { 92 | $mock = Mockery::mock(\Monolog\Logger::class)->makePartial(); 93 | $mock->shouldRecive('info') 94 | ->andReturnUsing(function ($args) { 95 | $GLOBALS['metrics'] = $args; 96 | }); 97 | Metrics::setClient($mock); 98 | } 99 | } 100 | 101 | protected function setupPrometheus($config = [], $mock = true) 102 | { 103 | app('config')->set('metrics.default', 'prometheus'); 104 | } 105 | } 106 | --------------------------------------------------------------------------------