|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace DDTrace\Integrations\Swoole; |
| 4 | + |
| 5 | +use DDTrace\HookData; |
| 6 | +use DDTrace\Integrations\Integration; |
| 7 | +use DDTrace\SpanStack; |
| 8 | +use DDTrace\Tag; |
| 9 | +use DDTrace\Type; |
| 10 | +use DDTrace\Util\Normalizer; |
| 11 | +use Swoole\Http\Request; |
| 12 | +use Swoole\Http\Response; |
| 13 | +use Swoole\Http\Server; |
| 14 | +use function DDTrace\consume_distributed_tracing_headers; |
| 15 | +use function DDTrace\extract_ip_from_headers; |
| 16 | + |
| 17 | +class SwooleIntegration extends Integration |
| 18 | +{ |
| 19 | + const NAME = 'swoole'; |
| 20 | + |
| 21 | + public function getName() |
| 22 | + { |
| 23 | + return self::NAME; |
| 24 | + } |
| 25 | + |
| 26 | + /** |
| 27 | + * {@inheritdoc} |
| 28 | + */ |
| 29 | + public function requiresExplicitTraceAnalyticsEnabling() |
| 30 | + { |
| 31 | + return false; |
| 32 | + } |
| 33 | + |
| 34 | + public function instrumentRequestStart(callable $callback, SwooleIntegration $integration, Server $server) |
| 35 | + { |
| 36 | + $scheme = $server->ssl ? 'https://' : 'http://'; |
| 37 | + |
| 38 | + \DDTrace\install_hook( |
| 39 | + $callback, |
| 40 | + function (HookData $hook) use ($integration, $server, $scheme) { |
| 41 | + $rootSpan = $hook->span(new SpanStack()); |
| 42 | + $rootSpan->name = "web.request"; |
| 43 | + $rootSpan->service = \ddtrace_config_app_name('swoole'); |
| 44 | + $rootSpan->type = Type::WEB_SERVLET; |
| 45 | + $rootSpan->meta[Tag::COMPONENT] = SwooleIntegration::NAME; |
| 46 | + $rootSpan->meta[Tag::SPAN_KIND] = Tag::SPAN_KIND_VALUE_SERVER; |
| 47 | + $integration->addTraceAnalyticsIfEnabled($rootSpan); |
| 48 | + |
| 49 | + $args = $hook->args; |
| 50 | + /** @var Request $request */ |
| 51 | + $request = $args[0]; |
| 52 | + |
| 53 | + $headers = []; |
| 54 | + $allowedHeaders = \dd_trace_env_config('DD_TRACE_HEADER_TAGS'); |
| 55 | + foreach ($request->header as $name => $value) { |
| 56 | + $headers[strtolower($name)] = $value; |
| 57 | + $normalizedHeader = preg_replace("([^a-z0-9-])", "_", strtolower($name)); |
| 58 | + if (\array_key_exists($normalizedHeader, $allowedHeaders)) { |
| 59 | + $rootSpan->meta["http.request.headers.$normalizedHeader"] = $value; |
| 60 | + } |
| 61 | + } |
| 62 | + consume_distributed_tracing_headers(function ($key) use ($headers) { |
| 63 | + return $headers[$key] ?? null; |
| 64 | + }); |
| 65 | + |
| 66 | + if (\dd_trace_env_config("DD_TRACE_CLIENT_IP_ENABLED")) { |
| 67 | + $res = extract_ip_from_headers($headers + ['REMOTE_ADDR' => $request->server['remote_addr']]); |
| 68 | + $rootSpan->meta += $res; |
| 69 | + } |
| 70 | + |
| 71 | + if (isset($headers["user-agent"])) { |
| 72 | + $rootSpan->meta["http.useragent"] = $headers["user-agent"]; |
| 73 | + } |
| 74 | + |
| 75 | + $rawContent = $request->rawContent(); |
| 76 | + if ($rawContent) { |
| 77 | + // The raw content will always be populated if the request is a POST request, independent of the |
| 78 | + // Content-Type header. |
| 79 | + // However, it may not be json-decodable |
| 80 | + $postFields = json_decode($rawContent, true); |
| 81 | + if (is_null($postFields)) { |
| 82 | + // Fallback to the post fields, which is an array |
| 83 | + // This array is not always populated, depending on the Content-Type header |
| 84 | + $postFields = $request->post; |
| 85 | + } |
| 86 | + } |
| 87 | + if (!empty($postFields)) { |
| 88 | + $postFields = Normalizer::sanitizePostFields($postFields); |
| 89 | + foreach ($postFields as $key => $value) { |
| 90 | + $rootSpan->meta["http.request.post.$key"] = $value; |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + $normalizedPath = Normalizer::uriNormalizeincomingPath( |
| 95 | + $request->server['request_uri'] |
| 96 | + ?? $request->server['path_info'] |
| 97 | + ?? '/' |
| 98 | + ); |
| 99 | + $rootSpan->resource = $request->server['request_method'] . ' ' . $normalizedPath; |
| 100 | + $rootSpan->meta[Tag::HTTP_METHOD] = $request->server['request_method']; |
| 101 | + |
| 102 | + $host = $headers['host'] ?? ($request->server['remote_addr'] . ':' . $request->server['server_port']); |
| 103 | + $path = $request->server['request_uri'] ?? $request->server['path_info'] ?? ''; |
| 104 | + $query = isset($request->server['query_string']) ? '?' . $request->server['query_string'] : ''; |
| 105 | + $url = $scheme . $host . $path . $query; |
| 106 | + $rootSpan->meta[Tag::HTTP_URL] = Normalizer::uriNormalizeincomingPath($url); |
| 107 | + |
| 108 | + unset($rootSpan->meta['closure.declaration']); |
| 109 | + } |
| 110 | + ); |
| 111 | + } |
| 112 | + |
| 113 | + public function init() |
| 114 | + { |
| 115 | + if (version_compare(swoole_version(), '5.0.2', '<')) { |
| 116 | + return Integration::NOT_LOADED; |
| 117 | + } |
| 118 | + |
| 119 | + $integration = $this; |
| 120 | + |
| 121 | + ini_set("datadog.trace.auto_flush_enabled", 1); |
| 122 | + ini_set("datadog.trace.generate_root_span", 0); |
| 123 | + |
| 124 | + \DDTrace\hook_method( |
| 125 | + 'Swoole\Http\Server', |
| 126 | + 'on', |
| 127 | + null, |
| 128 | + function ($server, $scope, $args, $retval) use ($integration) { |
| 129 | + if ($retval === false) { |
| 130 | + return; // Callback wasn't set |
| 131 | + } |
| 132 | + |
| 133 | + list($eventName, $callback) = $args; |
| 134 | + |
| 135 | + if ($eventName === 'request') { |
| 136 | + $integration->instrumentRequestStart($callback, $integration, $server); |
| 137 | + } |
| 138 | + } |
| 139 | + ); |
| 140 | + |
| 141 | + \DDTrace\hook_method( |
| 142 | + 'Swoole\Http\Response', |
| 143 | + 'end', |
| 144 | + function ($response, $scope, $args) use ($integration) { |
| 145 | + $rootSpan = \DDTrace\root_span(); |
| 146 | + if ($rootSpan === null) { |
| 147 | + return; |
| 148 | + } |
| 149 | + |
| 150 | + // Note: The response's body can be retrieved here, from the args |
| 151 | + |
| 152 | + if (!$rootSpan->exception |
| 153 | + && ((int)$rootSpan->meta[Tag::HTTP_STATUS_CODE]) >= 500 |
| 154 | + && $ex = \DDTrace\find_active_exception() |
| 155 | + ) { |
| 156 | + $rootSpan->exception = $ex; |
| 157 | + } |
| 158 | + } |
| 159 | + ); |
| 160 | + |
| 161 | + \DDTrace\hook_method( |
| 162 | + 'Swoole\Http\Response', |
| 163 | + 'header', |
| 164 | + function ($response, $scope, $args) use ($integration) { |
| 165 | + $rootSpan = \DDTrace\root_span(); |
| 166 | + if ($rootSpan === null || \count($args) < 2) { |
| 167 | + return; |
| 168 | + } |
| 169 | + |
| 170 | + /** @var string[] $args */ |
| 171 | + list($key, $value) = $args; |
| 172 | + |
| 173 | + $allowedHeaders = \dd_trace_env_config("DD_TRACE_HEADER_TAGS"); |
| 174 | + $normalizedHeader = preg_replace("([^a-z0-9-])", "_", strtolower($key)); |
| 175 | + if (\array_key_exists($normalizedHeader, $allowedHeaders)) { |
| 176 | + $rootSpan->meta["http.response.headers.$normalizedHeader"] = $value; |
| 177 | + } |
| 178 | + } |
| 179 | + ); |
| 180 | + |
| 181 | + \DDTrace\hook_method( |
| 182 | + 'Swoole\Http\Response', |
| 183 | + 'status', |
| 184 | + function ($response, $scope, $args) use ($integration) { |
| 185 | + $rootSpan = \DDTrace\root_span(); |
| 186 | + if ($rootSpan && \count($args) > 0) { |
| 187 | + $rootSpan->meta[Tag::HTTP_STATUS_CODE] = $args[0]; |
| 188 | + } |
| 189 | + } |
| 190 | + ); |
| 191 | + |
| 192 | + return Integration::LOADED; |
| 193 | + } |
| 194 | +} |
0 commit comments