diff --git a/.circleci/config.yml b/.circleci/config.yml index 371c526..6549dfb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2 # use CircleCI 2.0 -x-dockerbuild-7: &dockerbuild-7 +x-dockerbuild-phpdbg: &dockerbuild-phpdbg steps: - checkout - run: sudo docker-php-ext-install sockets @@ -19,16 +19,28 @@ x-dockerbuild-5: &dockerbuild-5 - run: composer lint jobs: + test-8.0: + <<: *dockerbuild-phpdbg + docker: + - image: circleci/php:8.0-node-browsers + test-7.4: + <<: *dockerbuild-phpdbg + docker: + - image: circleci/php:7.4-node-browsers + test-7.3: + <<: *dockerbuild-phpdbg + docker: + - image: circleci/php:7.3-node-browsers test-7.2: - <<: *dockerbuild-7 + <<: *dockerbuild-phpdbg docker: - image: circleci/php:7.2-node-browsers test-7.1: - <<: *dockerbuild-7 + <<: *dockerbuild-phpdbg docker: - image: circleci/php:7.1-node-browsers test-7.0: - <<: *dockerbuild-7 + <<: *dockerbuild-phpdbg docker: - image: circleci/php:7.0-node-browsers test-5.6: @@ -40,6 +52,9 @@ workflows: version: 2 check_compile: jobs: + - test-8.0 + - test-7.4 + - test-7.3 - test-7.2 - test-7.1 - test-7.0 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2c64479 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..780e683 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.php text eol=lf \ No newline at end of file diff --git a/README.md b/README.md index 2d13ef1..330464a 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,64 @@ $statsd = new DogStatsd( DogStatsd constructor, takes a configuration array. See the full list of available [DogStatsD Client instantiation parameters](https://docs.datadoghq.com/developers/dogstatsd/?code-lang=php#client-instantiation-parameters). +#### Migrating from legacy configuration + +In some setups, the use of environment variables can cause surprises, such as warnings, test-failures, etc. + +There is a new argument `transport_factory`, which takes an argument that _implements_ `DataDog\TransportFactory` interface. This has one method `create`, which can return, one or more classes implementing `DataDog\TransportMechanism`. This can be particularly useful if you experience warnings, errors or other output from the PHP built-in's this library uses for communicating stats. + +##### Defining custom implementation of transport factory + +```php +host, + $this->port + ); + } +} +``` + +##### Instantiating datadog with your new class +```php +, new CustomTransportFactory()) +); +``` + +#### Why this structure? + +The reason a factory is used to create an instance, is that DogStatsD opens sockets on the machine running this code. If sockets are left open and unclosed, then the second time a socket is to be created, more problems arise. + ### Origin detection over UDP in Kubernetes Origin detection is a method to detect which pod DogStatsD packets are coming from in order to add the pod's tags to the tag list. diff --git a/composer.json b/composer.json index 0879208..df52530 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,7 @@ "sort-packages": true }, "require-dev": { - "phpunit/phpunit": "5.7.27", + "yoast/phpunit-polyfills": "^1.0.1", "squizlabs/php_codesniffer": "^3.3" }, "scripts": { diff --git a/src/BatchedDogStatsd.php b/src/BatchedDogStatsd.php index ffb7822..1c0e84f 100644 --- a/src/BatchedDogStatsd.php +++ b/src/BatchedDogStatsd.php @@ -19,13 +19,13 @@ class BatchedDogStatsd extends DogStatsd public static $maxBufferLength = 50; - public function __construct(array $config = array()) + public function __construct(array $config = array(), $transportFactory = null) { // by default the telemetry is enabled for BatchedDogStatsd if (!isset($config["disable_telemetry"])) { $config["disable_telemetry"] = false; } - parent::__construct($config); + parent::__construct($config, $transportFactory); } /** diff --git a/src/BridgingTransportFactory.php b/src/BridgingTransportFactory.php new file mode 100644 index 0000000..d836eab --- /dev/null +++ b/src/BridgingTransportFactory.php @@ -0,0 +1,32 @@ +socketPath = $socketPath; + $this->host = $host; + $this->port = $port; + } + + /** + * @return TransportMechanism + */ + public function create() + { + if (is_null($this->socketPath)) { + return new Ipv4UdpTransport( + $this->host, + $this->port + ); + } + + return new UnixSocketTransport($this->socketPath); + } +} diff --git a/src/DogStatsd.php b/src/DogStatsd.php index 20e0187..14dfac6 100644 --- a/src/DogStatsd.php +++ b/src/DogStatsd.php @@ -61,6 +61,11 @@ class DogStatsd */ private $decimalPrecision; + /** + * @var TransportFactory + */ + private $transportFactory; + private static $eventUrl = '/api/v1/events'; // Used for the telemetry tags @@ -75,10 +80,11 @@ class DogStatsd * curl_ssl_verify_peer, * api_key and app_key, * decimal_precision + * transport_factory * * @param array $config */ - public function __construct(array $config = array()) + public function __construct(array $config = array(), $transportFactory = null) { $this->host = isset($config['host']) ? $config['host'] : (getenv('DD_AGENT_HOST') @@ -90,6 +96,12 @@ public function __construct(array $config = array()) $this->socketPath = isset($config['socket_path']) ? $config['socket_path'] : null; + $this->transportFactory = ( + isset($config['transport_factory']) ? + $transportFactory : + new BridgingTransportFactory($this->socketPath, $this->host, $this->port) + ); + $this->datadogHost = isset($config['datadog_host']) ? $config['datadog_host'] : 'https://app.datadoghq.com'; $this->curlVerifySslHost = isset($config['curl_ssl_verify_host']) ? $config['curl_ssl_verify_host'] : 2; $this->curlVerifySslPeer = isset($config['curl_ssl_verify_peer']) ? $config['curl_ssl_verify_peer'] : 1; @@ -465,15 +477,7 @@ public function flush($message) $message .= $this->flushTelemetry(); // Non - Blocking UDP I/O - Use IP Addresses! - $socket = is_null($this->socketPath) ? socket_create(AF_INET, SOCK_DGRAM, SOL_UDP) - : socket_create(AF_UNIX, SOCK_DGRAM, 0); - socket_set_nonblock($socket); - - if (!is_null($this->socketPath)) { - $res = socket_sendto($socket, $message, strlen($message), 0, $this->socketPath); - } else { - $res = socket_sendto($socket, $message, strlen($message), 0, $this->host, $this->port); - } + $res = $this->transportFactory->create()->sendMessage($message); if ($res !== false) { $this->resetTelemetry(); @@ -483,8 +487,6 @@ public function flush($message) $this->bytes_dropped += strlen($message); $this->packets_dropped += 1; } - - socket_close($socket); } /** diff --git a/src/Ipv4UdpTransport.php b/src/Ipv4UdpTransport.php new file mode 100644 index 0000000..406f4fe --- /dev/null +++ b/src/Ipv4UdpTransport.php @@ -0,0 +1,28 @@ +host = $host; + $this->port = $port; + $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + socket_set_nonblock($this->socket); + } + + public function sendMessage($message) + { + return (bool) socket_sendto($this->socket, $message, strlen($message), 0, $this->host, $this->port); + } + + public function __destruct() + { + socket_close($this->socket); + } +} diff --git a/src/TransportFactory.php b/src/TransportFactory.php new file mode 100644 index 0000000..859fb09 --- /dev/null +++ b/src/TransportFactory.php @@ -0,0 +1,16 @@ +socket = socket_create(AF_UNIX, SOCK_DGRAM, 0); + socket_set_nonblock($this->socket); + $this->socketPath = $socketPath; + } + + public function sendMessage($message) + { + return (bool) socket_sendto($this->socket, $message, strlen($message), 0, $this->socketPath); + } + + public function __destruct() + { + socket_close($this->socket); + } +} diff --git a/tests/TestHelpers/CurlSpyTestCase.php b/tests/TestHelpers/CurlSpyTestCase.php index 050afcf..09f6e2f 100644 --- a/tests/TestHelpers/CurlSpyTestCase.php +++ b/tests/TestHelpers/CurlSpyTestCase.php @@ -2,7 +2,7 @@ namespace DataDog\TestHelpers; -use PHPUnit\Framework\TestCase; +use Yoast\PHPUnitPolyfills\TestCases\TestCase; $curlSpy = new CurlSpy(); @@ -11,13 +11,12 @@ class CurlSpyTestCase extends TestCase /** * Set up a spy object to capture calls to built in curl functions */ - protected function setUp() + protected function set_up() { global $curlSpy; $curlSpy = new CurlSpy(); - - parent::setUp(); + parent::set_up(); } /** diff --git a/tests/TestHelpers/SocketSpyTestCase.php b/tests/TestHelpers/SocketSpyTestCase.php index 24b1514..7b85dda 100644 --- a/tests/TestHelpers/SocketSpyTestCase.php +++ b/tests/TestHelpers/SocketSpyTestCase.php @@ -3,7 +3,7 @@ namespace DataDog\TestHelpers; use DataDog\DogStatsd; -use PHPUnit\Framework\TestCase; +use Yoast\PHPUnitPolyfills\TestCases\TestCase; /** * Making this variable global to this file is necessary for interacting with @@ -24,13 +24,13 @@ class SocketSpyTestCase extends TestCase /** * Set up a spy object to capture calls to global built in socket functions */ - protected function setUp() + protected function set_up() { global $socketSpy; $socketSpy = new SocketSpy(); - parent::setUp(); + parent::set_up(); } /** diff --git a/tests/UnitTests/BatchedDogStatsdTest.php b/tests/UnitTests/BatchedDogStatsdTest.php index ae707de..7594d91 100644 --- a/tests/UnitTests/BatchedDogStatsdTest.php +++ b/tests/UnitTests/BatchedDogStatsdTest.php @@ -8,9 +8,9 @@ class BatchedDogStatsdTest extends SocketSpyTestCase { - protected function setUp() + protected function set_up() { - parent::setUp(); + parent::set_up(); // Flush the buffer to reset state for next test BatchedDogStatsd::$maxBufferLength = 50; diff --git a/tests/UnitTests/DogStatsd/SocketsTest.php b/tests/UnitTests/DogStatsd/SocketsTest.php index 7163884..41ee0a4 100644 --- a/tests/UnitTests/DogStatsd/SocketsTest.php +++ b/tests/UnitTests/DogStatsd/SocketsTest.php @@ -9,9 +9,9 @@ class SocketsTest extends SocketSpyTestCase { - public function setUp() + public function set_up() { - parent::setUp(); + parent::set_up(); // Reset the stubs for mt_rand() and mt_getrandmax() global $mt_rand_stub_return_value; diff --git a/tests/curl_and_error_log_function_stubs.php b/tests/curl_and_error_log_function_stubs.php index 4d964a4..ebf7c64 100644 --- a/tests/curl_and_error_log_function_stubs.php +++ b/tests/curl_and_error_log_function_stubs.php @@ -24,7 +24,7 @@ function curl_init($url = null) $curlSpy->curlInitWasCalledWithArg($url); - $resource = fopen('/dev/null', 'r'); + $resource = tmpfile(); $curlSpy->curlInitDidReturn($resource); diff --git a/tests/socket_function_stubs.php b/tests/socket_function_stubs.php index cc2c7d7..554f877 100644 --- a/tests/socket_function_stubs.php +++ b/tests/socket_function_stubs.php @@ -29,7 +29,7 @@ function socket_create($domain, $type, $protocol) // A PHP resource of unimportance, useful primarily to assert that our stubs // of the global socket functions return or take a deterministic value. - $resource = fopen('/dev/null', 'r'); + $resource = tmpfile(); $socketSpy->socketCreateDidReturn($resource);