Skip to content

Commit

Permalink
feat: Swoole Integration (#2595)
Browse files Browse the repository at this point in the history
* Initial Test Setup

* tests: Add test suite

* tests: Run swoole tests

* Revert stub changes

* tests: Add Swoole 4 Test Suite

* tests: Remove debug logs

* Revert

* Add a note for posterity

* Change test name

* fix: Only load for Swoole 5.0.2+

* Check `Response::header` arg count

Co-authored-by: Bob Weinand <bob.weinand@datadoghq.com>

* Check for `Response::status` arg count

Co-authored-by: Bob Weinand <bob.weinand@datadoghq.com>

* fix: Identify SSL Connections

* Move `$scheme` before `install_hook`

---------

Co-authored-by: Bob Weinand <bob.weinand@datadoghq.com>
  • Loading branch information
PROFeNoM and bwoebi committed Mar 28, 2024
1 parent 3d4313f commit 7b4a640
Show file tree
Hide file tree
Showing 17 changed files with 727 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ TEST_INTEGRATIONS_80 := \
test_integrations_pcntl \
test_integrations_predis1 \
test_integrations_sqlsrv \
test_integrations_swoole_5 \
test_opentracing_10

TEST_WEB_80 := \
Expand Down Expand Up @@ -843,6 +844,7 @@ TEST_INTEGRATIONS_81 := \
test_integrations_elasticsearch7 \
test_integrations_predis1 \
test_integrations_sqlsrv \
test_integrations_swoole_5 \
test_opentracing_10

TEST_WEB_81 := \
Expand Down Expand Up @@ -891,6 +893,7 @@ TEST_INTEGRATIONS_82 := \
test_integrations_predis1 \
test_integrations_roadrunner \
test_integrations_sqlsrv \
test_integrations_swoole_5 \
test_opentracing_10

TEST_WEB_82 := \
Expand Down Expand Up @@ -942,6 +945,7 @@ TEST_INTEGRATIONS_83 := \
test_integrations_predis1 \
test_integrations_roadrunner \
test_integrations_sqlsrv \
test_integrations_swoole_5 \
test_opentracing_10

TEST_WEB_83 := \
Expand Down Expand Up @@ -1216,6 +1220,9 @@ test_integrations_roadrunner: global_test_run_dependencies
test_integrations_sqlsrv: global_test_run_dependencies
$(MAKE) test_scenario_default
$(call run_tests_debug,tests/Integrations/SQLSRV)
test_integrations_swoole_5: global_test_run_dependencies
$(MAKE) test_scenario_swoole5
$(call run_tests_debug,--testsuite=swoole-test)
test_web_cakephp_28: global_test_run_dependencies
$(call run_composer_with_retry,tests/Frameworks/CakePHP/Version_2_8,)
$(call run_tests_debug,--testsuite=cakephp-28-test)
Expand Down
1 change: 1 addition & 0 deletions bridge/_files_integrations.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
__DIR__ . '/../src/Integrations/Integrations/Mongo/MongoIntegration.php',
__DIR__ . '/../src/Integrations/Integrations/MongoDB/MongoDBIntegration.php',
__DIR__ . '/../src/Integrations/Integrations/Slim/SlimIntegration.php',
__DIR__ . '/../src/Integrations/Integrations/Swoole/SwooleIntegration.php',
__DIR__ . '/../src/Integrations/Integrations/SQLSRV/SQLSRVIntegration.php',
__DIR__ . '/../src/Integrations/Integrations/Symfony/SymfonyIntegration.php',
__DIR__ . '/../src/Integrations/Integrations/ElasticSearch/V1/ElasticSearchCommon.php',
Expand Down
3 changes: 3 additions & 0 deletions ext/integrations/integrations.c
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@ void ddtrace_integrations_minit(void) {
DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_SLIM, "Slim\\App", "__construct",
"DDTrace\\Integrations\\Slim\\SlimIntegration");

DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_SWOOLE, "Swoole\\Http\\Server", "on",
"DDTrace\\Integrations\\Swoole\\SwooleIntegration");

DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_LARAVELQUEUE, "Illuminate\\Queue\\Worker", "__construct",
"DDTrace\\Integrations\\LaravelQueue\\LaravelQueueIntegration");
DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_LARAVELQUEUE, "Illuminate\\Contracts\\Queue\\Queue", "push",
Expand Down
1 change: 1 addition & 0 deletions ext/integrations/integrations.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
INTEGRATION(ROADRUNNER, "roadrunner") \
INTEGRATION(SQLSRV, "sqlsrv") \
INTEGRATION(SLIM, "slim") \
INTEGRATION(SWOOLE, "swoole") \
INTEGRATION(SYMFONY, "symfony") \
INTEGRATION(WEB, "web") \
INTEGRATION(WORDPRESS, "wordpress") \
Expand Down
194 changes: 194 additions & 0 deletions src/Integrations/Integrations/Swoole/SwooleIntegration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<?php

namespace DDTrace\Integrations\Swoole;

use DDTrace\HookData;
use DDTrace\Integrations\Integration;
use DDTrace\SpanStack;
use DDTrace\Tag;
use DDTrace\Type;
use DDTrace\Util\Normalizer;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Http\Server;
use function DDTrace\consume_distributed_tracing_headers;
use function DDTrace\extract_ip_from_headers;

class SwooleIntegration extends Integration
{
const NAME = 'swoole';

public function getName()
{
return self::NAME;
}

/**
* {@inheritdoc}
*/
public function requiresExplicitTraceAnalyticsEnabling()
{
return false;
}

public function instrumentRequestStart(callable $callback, SwooleIntegration $integration, Server $server)
{
$scheme = $server->ssl ? 'https://' : 'http://';

\DDTrace\install_hook(
$callback,
function (HookData $hook) use ($integration, $server, $scheme) {
$rootSpan = $hook->span(new SpanStack());
$rootSpan->name = "web.request";
$rootSpan->service = \ddtrace_config_app_name('swoole');
$rootSpan->type = Type::WEB_SERVLET;
$rootSpan->meta[Tag::COMPONENT] = SwooleIntegration::NAME;
$rootSpan->meta[Tag::SPAN_KIND] = Tag::SPAN_KIND_VALUE_SERVER;
$integration->addTraceAnalyticsIfEnabled($rootSpan);

$args = $hook->args;
/** @var Request $request */
$request = $args[0];

$headers = [];
$allowedHeaders = \dd_trace_env_config('DD_TRACE_HEADER_TAGS');
foreach ($request->header as $name => $value) {
$headers[strtolower($name)] = $value;
$normalizedHeader = preg_replace("([^a-z0-9-])", "_", strtolower($name));
if (\array_key_exists($normalizedHeader, $allowedHeaders)) {
$rootSpan->meta["http.request.headers.$normalizedHeader"] = $value;
}
}
consume_distributed_tracing_headers(function ($key) use ($headers) {
return $headers[$key] ?? null;
});

if (\dd_trace_env_config("DD_TRACE_CLIENT_IP_ENABLED")) {
$res = extract_ip_from_headers($headers + ['REMOTE_ADDR' => $request->server['remote_addr']]);
$rootSpan->meta += $res;
}

if (isset($headers["user-agent"])) {
$rootSpan->meta["http.useragent"] = $headers["user-agent"];
}

$rawContent = $request->rawContent();
if ($rawContent) {
// The raw content will always be populated if the request is a POST request, independent of the
// Content-Type header.
// However, it may not be json-decodable
$postFields = json_decode($rawContent, true);
if (is_null($postFields)) {
// Fallback to the post fields, which is an array
// This array is not always populated, depending on the Content-Type header
$postFields = $request->post;
}
}
if (!empty($postFields)) {
$postFields = Normalizer::sanitizePostFields($postFields);
foreach ($postFields as $key => $value) {
$rootSpan->meta["http.request.post.$key"] = $value;
}
}

$normalizedPath = Normalizer::uriNormalizeincomingPath(
$request->server['request_uri']
?? $request->server['path_info']
?? '/'
);
$rootSpan->resource = $request->server['request_method'] . ' ' . $normalizedPath;
$rootSpan->meta[Tag::HTTP_METHOD] = $request->server['request_method'];

$host = $headers['host'] ?? ($request->server['remote_addr'] . ':' . $request->server['server_port']);
$path = $request->server['request_uri'] ?? $request->server['path_info'] ?? '';
$query = isset($request->server['query_string']) ? '?' . $request->server['query_string'] : '';
$url = $scheme . $host . $path . $query;
$rootSpan->meta[Tag::HTTP_URL] = Normalizer::uriNormalizeincomingPath($url);

unset($rootSpan->meta['closure.declaration']);
}
);
}

public function init()
{
if (version_compare(swoole_version(), '5.0.2', '<')) {
return Integration::NOT_LOADED;
}

$integration = $this;

ini_set("datadog.trace.auto_flush_enabled", 1);
ini_set("datadog.trace.generate_root_span", 0);

\DDTrace\hook_method(
'Swoole\Http\Server',
'on',
null,
function ($server, $scope, $args, $retval) use ($integration) {
if ($retval === false) {
return; // Callback wasn't set
}

list($eventName, $callback) = $args;

if ($eventName === 'request') {
$integration->instrumentRequestStart($callback, $integration, $server);
}
}
);

\DDTrace\hook_method(
'Swoole\Http\Response',
'end',
function ($response, $scope, $args) use ($integration) {
$rootSpan = \DDTrace\root_span();
if ($rootSpan === null) {
return;
}

// Note: The response's body can be retrieved here, from the args

if (!$rootSpan->exception
&& ((int)$rootSpan->meta[Tag::HTTP_STATUS_CODE]) >= 500
&& $ex = \DDTrace\find_active_exception()
) {
$rootSpan->exception = $ex;
}
}
);

\DDTrace\hook_method(
'Swoole\Http\Response',
'header',
function ($response, $scope, $args) use ($integration) {
$rootSpan = \DDTrace\root_span();
if ($rootSpan === null || \count($args) < 2) {
return;
}

/** @var string[] $args */
list($key, $value) = $args;

$allowedHeaders = \dd_trace_env_config("DD_TRACE_HEADER_TAGS");
$normalizedHeader = preg_replace("([^a-z0-9-])", "_", strtolower($key));
if (\array_key_exists($normalizedHeader, $allowedHeaders)) {
$rootSpan->meta["http.response.headers.$normalizedHeader"] = $value;
}
}
);

\DDTrace\hook_method(
'Swoole\Http\Response',
'status',
function ($response, $scope, $args) use ($integration) {
$rootSpan = \DDTrace\root_span();
if ($rootSpan && \count($args) > 0) {
$rootSpan->meta[Tag::HTTP_STATUS_CODE] = $args[0];
}
}
);

return Integration::LOADED;
}
}
8 changes: 8 additions & 0 deletions tests/Common/WebFrameworkTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ protected static function getRoadrunnerVersion()
return null;
}

protected static function isSwoole()
{
return false;
}

/**
* Get additional envs to be set in the web server.
* @return array
Expand Down Expand Up @@ -130,6 +135,9 @@ protected static function setUpWebServer(array $additionalEnvs = [], array $addi
if ($version = static::getRoadrunnerVersion()) {
self::$appServer->setRoadrunner($version);
}
if ($version = static::isSwoole()) {
self::$appServer->setSwoole($version);
}
self::$appServer->start();
}
}
Expand Down
23 changes: 23 additions & 0 deletions tests/Frameworks/Swoole/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

require __DIR__ . '/../../vendor/autoload.php';

$http = new Swoole\Http\Server("0.0.0.0", 9999);

$http->on('request', function ($request, $response) {
$requestUri = $request->server['request_uri'];

try {
if ($requestUri == "/error") {
throw new \Exception("Error page");
}

$response->status(200);
$response->end('Hello Swoole!');
} catch (\Throwable $e) {
$response->status(500);
$response->end('Something Went Wrong!');
}
});

$http->start();

0 comments on commit 7b4a640

Please sign in to comment.