diff --git a/src/Psr/Tracing/RelayOpenTelemetry.php b/src/Psr/Tracing/RelayOpenTelemetry.php index ac3587a..ade59ad 100644 --- a/src/Psr/Tracing/RelayOpenTelemetry.php +++ b/src/Psr/Tracing/RelayOpenTelemetry.php @@ -62,6 +62,13 @@ class RelayOpenTelemetry '_prefix', ]; + /** + * Maximum string length when formatting commands for OpenTelemetry. + * + * @var int + */ + protected const MAX_STR_LEN = 100; + /** * Creates a new instance. * @@ -112,9 +119,13 @@ public function __call(string $name, array $arguments) return $this->relay->{$name}(...$arguments); } + $operation = strtoupper($name); + $stmt = $this->fmtCommand($operation, $arguments); + $span = $this->tracer->spanBuilder('Relay::' . strtolower($name)) - ->setAttribute('db.operation', strtoupper($name)) ->setAttribute('db.system', 'redis') + ->setAttribute('db.operation', $operation) + ->setAttribute('db.statement', $stmt) ->setSpanKind(SpanKind::KIND_CLIENT) ->startSpan(); @@ -152,9 +163,11 @@ public static function __callStatic(string $name, array $arguments) */ public function scan(&$iterator, $match = null, int $count = 0, ?string $type = null) { + $stmt = $this->fmtScan('SCAN', null, $iterator, $match, $count, $type); $span = $this->tracer->spanBuilder('Relay::scan') - ->setAttribute('db.operation', 'SCAN') ->setAttribute('db.system', 'redis') + ->setAttribute('db.operation', 'SCAN') + ->setAttribute('db.statement', $stmt) ->setSpanKind(SpanKind::KIND_CLIENT) ->startSpan(); @@ -180,9 +193,11 @@ public function scan(&$iterator, $match = null, int $count = 0, ?string $type = */ public function hscan($key, &$iterator, $match = null, int $count = 0) { + $stmt = $this->fmtScan('HSCAN', $key, $iterator, $match, $count); $span = $this->tracer->spanBuilder('Relay::hscan') - ->setAttribute('db.operation', 'HSCAN') ->setAttribute('db.system', 'redis') + ->setAttribute('db.operation', 'HSCAN') + ->setAttribute('db.statement', $stmt) ->setSpanKind(SpanKind::KIND_CLIENT) ->startSpan(); @@ -208,9 +223,11 @@ public function hscan($key, &$iterator, $match = null, int $count = 0) */ public function sscan($key, &$iterator, $match = null, int $count = 0) { + $stmt = $this->fmtScan('SSCAN', $key, $iterator, $match, $count); $span = $this->tracer->spanBuilder('Relay::sscan') - ->setAttribute('db.operation', 'SSCAN') ->setAttribute('db.system', 'redis') + ->setAttribute('db.operation', 'SSCAN') + ->setAttribute('db.statement', $stmt) ->setSpanKind(SpanKind::KIND_CLIENT) ->startSpan(); @@ -236,9 +253,11 @@ public function sscan($key, &$iterator, $match = null, int $count = 0) */ public function zscan($key, &$iterator, $match = null, int $count = 0) { + $stmt = $this->fmtScan('ZSCAN', $key, $iterator, $match, $count); $span = $this->tracer->spanBuilder('Relay::zscan') - ->setAttribute('db.operation', 'ZSCAN') ->setAttribute('db.system', 'redis') + ->setAttribute('db.operation', 'ZSCAN') + ->setAttribute('db.statement', $stmt) ->setSpanKind(SpanKind::KIND_CLIENT) ->startSpan(); @@ -298,9 +317,11 @@ public function executeBufferedTransaction(Transaction $transaction) ? 'pipeline' : 'multi'; - $span = $this->tracer->spanBuilder('Relay::exec') - ->setAttribute('db.operation', 'EXEC') + $stmt = $this->fmtCommands($transaction->commands); + $span = $this->tracer->spanBuilder('Relay::' . $method) ->setAttribute('db.system', 'redis') + ->setAttribute('db.operation', $method) + ->setAttribute('db.statement', $stmt) ->setSpanKind(SpanKind::KIND_CLIENT) ->startSpan(); @@ -320,4 +341,99 @@ public function executeBufferedTransaction(Transaction $transaction) $span->end(); } } + + /** + * Formats multiple commands for db.statement attribute. + * + * @param array $commands + * @return string + */ + protected function fmtCommands(array $commands) + { + $acc = []; + $len = 0; + foreach ($commands as $command) { + $command = $this->fmtCommand($command[0], $command[1]); + $len += strlen($command); + if ($len > 5000) { + break; + } + array_push($acc, $command); + } + return implode(PHP_EOL, $acc); + } + + /** + * Formats a single command for db.statement attribute. + * + * @param string $name + * @param array $arguments + * @return string + */ + protected function fmtCommand(string $operation, array $arguments) + { + $acc = [$operation]; + $len = 0; + $this->fmtArguments($acc, $len, $arguments); + return implode(' ', $acc); + } + + /** + * Formats passed arguments as strings. + * + * @param array $acc + * @param array $arguments + * @return void + */ + protected function fmtArguments(&$acc, int &$len, $arguments) + { + foreach ($arguments as $value) { + if (is_array($value)) { + $this->fmtArguments($acc, $len, $value); + continue; + } + + $str = strval($value); + if (strlen($str) > self::MAX_STR_LEN) { + $str = substr($str, 0, self::MAX_STR_LEN - 3) + '...'; + } + + $len += strlen($str); + if ($len > 1000) { + array_push($acc, '...'); + break; + } + array_push($acc, $str); + } + } + + /** + * Formats SCAN command arguments. + * + * @param string $name + * @param mixed $key + * @param mixed $iterator + * @param mixed $match + * @param int $count + * @param ?string $type + * @return string + */ + protected function fmtScan(string $name, $key, $iterator, $match = null, int $count = 0, ?string $type = null) + { + $args = [$name]; + if ($key) { + array_push($args, $key); + } + array_push($args, $iterator); + if ($match) { + array_push($args, 'MATCH', $match); + } + if ($count > 0) { + array_push($args, 'COUNT', $count); + } + if ($type) { + array_push($args, 'TYPE', $type); + } + return implode(' ', $args); + } }