Skip to content

Commit 7679c87

Browse files
committed
Instrumented mysli::prepare
1 parent d70b53f commit 7679c87

File tree

2 files changed

+206
-37
lines changed

2 files changed

+206
-37
lines changed

src/Instrumentation/MySqli/src/MySqliInstrumentation.php

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,9 @@ public static function register(): void
233233
hook(
234234
null,
235235
'mysqli_prepare',
236-
pre: null,
236+
pre: static function (...$args) use ($instrumentation, $tracker) {
237+
self::preparePreHook('mysqli_prepare', $instrumentation, $tracker, ...$args);
238+
},
237239
post: static function (...$args) use ($instrumentation, $tracker) {
238240
self::preparePostHook($instrumentation, $tracker, ...$args);
239241
}
@@ -242,7 +244,9 @@ public static function register(): void
242244
hook(
243245
mysqli::class,
244246
'prepare',
245-
pre: null,
247+
pre: static function (...$args) use ($instrumentation, $tracker) {
248+
self::preparePreHook('mysqli::prepare', $instrumentation, $tracker, ...$args);
249+
},
246250
post: static function (...$args) use ($instrumentation, $tracker) {
247251
self::preparePostHook($instrumentation, $tracker, ...$args);
248252
}
@@ -397,8 +401,6 @@ public static function register(): void
397401
self::stmtNextResultPostHook($instrumentation, $tracker, ...$args);
398402
}
399403
);
400-
401-
//TODO test to https://www.php.net/manual/en/mysqli.begin-transaction.php
402404
}
403405

404406
/** @param non-empty-string $spanName */
@@ -533,9 +535,8 @@ private static function nextResultPostHook(CachedInstrumentation $instrumentatio
533535

534536
private static function changeUserPostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $retVal, ?\Throwable $exception)
535537
{
536-
537538
if ($retVal != true) {
538-
return; //TODO create error span?
539+
return;
539540
}
540541

541542
$mysqli = $obj ? $obj : $params[0];
@@ -549,30 +550,46 @@ private static function changeUserPostHook(CachedInstrumentation $instrumentatio
549550

550551
private static function selectDbPostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $retVal, ?\Throwable $exception)
551552
{
552-
553553
if ($retVal != true) {
554-
return; //TODO create error span?
554+
return;
555555
}
556556
$tracker->addMySqliAttribute($obj ? $obj : $params[0], TraceAttributes::DB_NAMESPACE, $params[$obj ? 0 : 1]);
557557
}
558558

559+
/** @param non-empty-string $spanName */
560+
private static function preparePreHook(string $spanName, CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, ?string $class, ?string $function, ?string $filename, ?int $lineno): void
561+
{
562+
$span = self::startSpan($spanName, $instrumentation, $class, $function, $filename, $lineno, []);
563+
$mysqli = $obj ? $obj : $params[0];
564+
self::addTransactionLink($tracker, $span, $mysqli);
565+
}
566+
559567
private static function preparePostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $stmtRetVal, ?\Throwable $exception)
560568
{
569+
$mysqli = $obj ? $obj : $params[0];
570+
$query = $params[$obj ? 0 : 1];
561571

562-
if ($exception || !$stmtRetVal instanceof mysqli_stmt) {
563-
self::logDebug('mysqli::prepare failed', ['exception' => $exception, 'obj' => $obj, 'retVal' => $stmtRetVal, 'params' => $params]);
572+
$errorStatus = null;
564573

565-
return;
566-
}
574+
$query = mb_convert_encoding($query, 'UTF-8');
575+
$operation = self::extractQueryCommand($query);
567576

568-
$mysqli = $obj ? $obj : $params[0];
569-
$query = $params[$obj ? 0 : 1];
577+
$attributes = $tracker->getMySqliAttributes($mysqli);
578+
$attributes[TraceAttributes::DB_STATEMENT] = $query;
579+
$attributes[TraceAttributes::DB_OPERATION_NAME] = $operation;
570580

571-
$tracker->trackMySqliFromStatement($mysqli, $stmtRetVal);
581+
if (!$exception && $stmtRetVal instanceof mysqli_stmt) {
582+
$tracker->trackMySqliFromStatement($mysqli, $stmtRetVal);
583+
$tracker->addStatementAttribute($stmtRetVal, TraceAttributes::DB_STATEMENT, $query);
584+
$tracker->addStatementAttribute($stmtRetVal, TraceAttributes::DB_OPERATION_NAME, $operation);
572585

573-
$tracker->addStatementAttribute($stmtRetVal, TraceAttributes::DB_STATEMENT, mb_convert_encoding($query, 'UTF-8'));
574-
$tracker->addStatementAttribute($stmtRetVal, TraceAttributes::DB_OPERATION_NAME, self::extractQueryCommand($query));
586+
} else {
587+
//TODO use constant from comment after sem-conv update
588+
$attributes[/*TraceAttributes::DB_RESPONSE_STATUS_CODE*/ 'db.response.status_code'] = $mysqli->errno;
589+
$errorStatus = !$exception ? $mysqli->error : null;
590+
}
575591

592+
self::endSpan($attributes, $exception, $errorStatus);
576593
}
577594

578595
/** @param non-empty-string $spanName */

src/Instrumentation/MySqli/tests/Integration/MySqliInstrumentationTest.php

Lines changed: 172 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,116 @@ public function test_mysqli_multi_query_procedural(): void
648648
$this->assertDatabaseAttributesForAllSpans($offset);
649649
}
650650

651+
public function test_mysqli_prepare_objective(): void
652+
{
653+
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
654+
655+
$mysqli = new mysqli($this->mysqlHost, $this->user, $this->passwd, $this->database);
656+
657+
$offset = 0;
658+
$this->assertSame('mysqli::__construct', $this->storage->offsetGet($offset)->getName());
659+
660+
try {
661+
$stmt = $mysqli->prepare('SELECT * FROM otel_db.users');
662+
663+
$offset++;
664+
$this->assertSame('mysqli::prepare', $this->storage->offsetGet($offset)->getName());
665+
$this->assertAttributes($offset, [
666+
TraceAttributes::DB_STATEMENT => 'SELECT * FROM otel_db.users',
667+
TraceAttributes::DB_OPERATION_NAME => 'SELECT',
668+
]);
669+
670+
$stmt->execute();
671+
$offset++;
672+
673+
$this->assertSame('mysqli_stmt::execute', $this->storage->offsetGet($offset)->getName());
674+
$this->assertAttributes($offset, [
675+
TraceAttributes::DB_STATEMENT => 'SELECT * FROM otel_db.users',
676+
TraceAttributes::DB_OPERATION_NAME => 'SELECT',
677+
]);
678+
679+
$stmt->fetch();
680+
$stmt->close();
681+
682+
} catch (mysqli_sql_exception $exception) {
683+
$this->fail('Unexpected exception was thrown: ' . $exception->getMessage());
684+
}
685+
686+
try {
687+
$stmt = $mysqli->prepare('SELECT * FROM unknown_db.users');
688+
689+
$this->fail('Should never reach this point');
690+
} catch (mysqli_sql_exception $exception) {
691+
$offset++;
692+
693+
$this->assertSame('mysqli::prepare', $this->storage->offsetGet($offset)->getName());
694+
$this->assertAttributes($offset, [
695+
TraceAttributes::DB_STATEMENT => 'SELECT * FROM unknown_db.users',
696+
TraceAttributes::DB_OPERATION_NAME => 'SELECT',
697+
TraceAttributes::EXCEPTION_TYPE => mysqli_sql_exception::class,
698+
]);
699+
700+
}
701+
702+
$offset++;
703+
$this->assertCount($offset, $this->storage);
704+
$this->assertDatabaseAttributesForAllSpans($offset);
705+
}
706+
707+
public function test_mysqli_prepare_procedural(): void
708+
{
709+
mysqli_report(MYSQLI_REPORT_ERROR);
710+
711+
$mysqli = new mysqli($this->mysqlHost, $this->user, $this->passwd, $this->database);
712+
713+
$offset = 0;
714+
$this->assertSame('mysqli::__construct', $this->storage->offsetGet($offset)->getName());
715+
716+
try {
717+
$stmt = mysqli_prepare($mysqli, 'SELECT * FROM otel_db.users');
718+
719+
$offset++;
720+
$this->assertSame('mysqli_prepare', $this->storage->offsetGet($offset)->getName());
721+
$this->assertAttributes($offset, [
722+
TraceAttributes::DB_STATEMENT => 'SELECT * FROM otel_db.users',
723+
TraceAttributes::DB_OPERATION_NAME => 'SELECT',
724+
]);
725+
726+
mysqli_stmt_execute($stmt);
727+
$offset++;
728+
729+
$this->assertSame('mysqli_stmt_execute', $this->storage->offsetGet($offset)->getName());
730+
$this->assertAttributes($offset, [
731+
TraceAttributes::DB_STATEMENT => 'SELECT * FROM otel_db.users',
732+
TraceAttributes::DB_OPERATION_NAME => 'SELECT',
733+
]);
734+
735+
mysqli_stmt_fetch($stmt);
736+
mysqli_stmt_close($stmt);
737+
} catch (mysqli_sql_exception $exception) {
738+
$this->fail('Unexpected exception was thrown: ' . $exception->getMessage());
739+
}
740+
741+
try {
742+
$stmt = mysqli_prepare($mysqli, 'SELECT * FROM unknown_db.users');
743+
744+
$this->fail('Should never reach this point');
745+
} catch (\Throwable $exception) {
746+
$offset++;
747+
748+
$this->assertSame('mysqli_prepare', $this->storage->offsetGet($offset)->getName());
749+
$this->assertAttributes($offset, [
750+
TraceAttributes::DB_STATEMENT => 'SELECT * FROM unknown_db.users',
751+
TraceAttributes::DB_OPERATION_NAME => 'SELECT',
752+
TraceAttributes::EXCEPTION_TYPE => \PHPUnit\Framework\Error\Warning::class,
753+
]);
754+
}
755+
756+
$offset++;
757+
$this->assertCount($offset, $this->storage);
758+
$this->assertDatabaseAttributesForAllSpans($offset);
759+
}
760+
651761
public function test_mysqli_transaction_rollback_objective(): void
652762
{
653763
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
@@ -685,6 +795,12 @@ public function test_mysqli_transaction_rollback_objective(): void
685795
$language_code = 'FR';
686796
$native_speakers = 'Unknown';
687797
$stmt = $mysqli->prepare('INSERT INTO language(Code, Speakers) VALUES (?,?)');
798+
$offset++;
799+
$this->assertSame('mysqli::prepare', $this->storage->offsetGet($offset)->getName());
800+
$this->assertAttributes($offset, [
801+
TraceAttributes::DB_STATEMENT => 'INSERT INTO language(Code, Speakers) VALUES (?,?)',
802+
TraceAttributes::DB_OPERATION_NAME => 'INSERT',
803+
]);
688804

689805
$stmt->bind_param('ss', $language_code, $native_speakers);
690806
$stmt->execute(); // THROWS HERE
@@ -749,6 +865,12 @@ public function test_mysqli_transaction_rollback_procedural(): void
749865
$language_code = 'FR';
750866
$native_speakers = 'Unknown';
751867
$stmt = mysqli_prepare($mysqli, 'INSERT INTO language(Code, Speakers) VALUES (?,?)');
868+
$offset++;
869+
$this->assertSame('mysqli_prepare', $this->storage->offsetGet($offset)->getName());
870+
$this->assertAttributes($offset, [
871+
TraceAttributes::DB_STATEMENT => 'INSERT INTO language(Code, Speakers) VALUES (?,?)',
872+
TraceAttributes::DB_OPERATION_NAME => 'INSERT',
873+
]);
752874

753875
mysqli_stmt_bind_param($stmt, 'ss', $language_code, $native_speakers);
754876

@@ -816,6 +938,13 @@ public function test_mysqli_transaction_commit_objective(): void
816938
$native_speakers = 66000002;
817939
$stmt = $mysqli->prepare('INSERT INTO language(Code, Speakers) VALUES (?,?)');
818940

941+
$offset++;
942+
$this->assertSame('mysqli::prepare', $this->storage->offsetGet($offset)->getName());
943+
$this->assertAttributes($offset, [
944+
TraceAttributes::DB_STATEMENT => 'INSERT INTO language(Code, Speakers) VALUES (?,?)',
945+
TraceAttributes::DB_OPERATION_NAME => 'INSERT',
946+
]);
947+
819948
$stmt->bind_param('ss', $language_code, $native_speakers);
820949
$stmt->execute();
821950

@@ -879,6 +1008,12 @@ public function test_mysqli_transaction_commit_procedural(): void
8791008
$language_code = 'FR';
8801009
$native_speakers = 66000002;
8811010
$stmt = mysqli_prepare($mysqli, 'INSERT INTO language(Code, Speakers) VALUES (?,?)');
1011+
$offset++;
1012+
$this->assertSame('mysqli_prepare', $this->storage->offsetGet($offset)->getName());
1013+
$this->assertAttributes($offset, [
1014+
TraceAttributes::DB_STATEMENT => 'INSERT INTO language(Code, Speakers) VALUES (?,?)',
1015+
TraceAttributes::DB_OPERATION_NAME => 'INSERT',
1016+
]);
8821017

8831018
mysqli_stmt_bind_param($stmt, 'ss', $language_code, $native_speakers);
8841019
mysqli_stmt_execute($stmt);
@@ -929,6 +1064,7 @@ public function test_mysqli_stmt_execute_objective(): void
9291064

9301065
$stmt = $mysqli->stmt_init();
9311066
$stmt->prepare("SELECT email FROM users WHERE name='John Doe'");
1067+
9321068
$stmt->execute();
9331069
$stmt->fetch();
9341070
$stmt->close();
@@ -1017,6 +1153,13 @@ public function test_mysqli_multiquery_with_calls(): void
10171153
$this->assertStringEndsWith('END;', $span->getAttributes()->get(TraceAttributes::DB_STATEMENT));
10181154

10191155
$stmt = $mysqli->prepare('CALL get_message();');
1156+
$offset++;
1157+
$this->assertSame('mysqli::prepare', $this->storage->offsetGet($offset)->getName());
1158+
$this->assertAttributes($offset, [
1159+
TraceAttributes::DB_STATEMENT => 'CALL get_message();',
1160+
TraceAttributes::DB_OPERATION_NAME => 'CALL',
1161+
]);
1162+
10201163
$stmt->execute();
10211164

10221165
$offset++;
@@ -1195,15 +1338,15 @@ public function test_mysqli_select_db(): void
11951338
$this->assertSame('mysqli::__construct', $this->storage->offsetGet($offset)->getName());
11961339

11971340
$offset++;
1198-
$res = $mysqli->query('SELECT CURRENT_USER();');
1341+
$res = $mysqli->query('SELECT * FROM users;');
11991342
if ($res instanceof mysqli_result) {
12001343
while ($res->fetch_object()) {
12011344
}
12021345
}
12031346

12041347
$this->assertSame('mysqli::query', $this->storage->offsetGet($offset)->getName());
12051348
$this->assertAttributes($offset, [
1206-
TraceAttributes::DB_STATEMENT => 'SELECT CURRENT_USER();',
1349+
TraceAttributes::DB_STATEMENT => 'SELECT * FROM users;',
12071350
TraceAttributes::DB_OPERATION_NAME => 'SELECT',
12081351
TraceAttributes::SERVER_ADDRESS => $this->mysqlHost,
12091352
TraceAttributes::DB_USER => $this->user,
@@ -1213,35 +1356,37 @@ public function test_mysqli_select_db(): void
12131356

12141357
$mysqli->select_db('otel_db2');
12151358

1216-
$offset++;
1217-
$res = $mysqli->query('SELECT CURRENT_USER();');
1218-
if ($res instanceof mysqli_result) {
1219-
while ($res->fetch_object()) {
1220-
}
1359+
try {
1360+
$res = $mysqli->query('SELECT * FROM users;');
1361+
$this->fail('Should never reach this point');
1362+
} catch (\Throwable $e) {
1363+
$offset++;
1364+
1365+
$this->assertSame('mysqli::query', $this->storage->offsetGet($offset)->getName());
1366+
$this->assertAttributes($offset, [
1367+
TraceAttributes::DB_STATEMENT => 'SELECT * FROM users;',
1368+
TraceAttributes::DB_OPERATION_NAME => 'SELECT',
1369+
TraceAttributes::SERVER_ADDRESS => $this->mysqlHost,
1370+
TraceAttributes::DB_USER => $this->user,
1371+
TraceAttributes::DB_NAMESPACE => 'otel_db2',
1372+
TraceAttributes::DB_SYSTEM => 'mysql',
1373+
TraceAttributes::EXCEPTION_TYPE => mysqli_sql_exception::class,
1374+
]);
12211375
}
12221376

1223-
$this->assertSame('mysqli::query', $this->storage->offsetGet($offset)->getName());
1224-
$this->assertAttributes($offset, [
1225-
TraceAttributes::DB_STATEMENT => 'SELECT CURRENT_USER();',
1226-
TraceAttributes::DB_OPERATION_NAME => 'SELECT',
1227-
TraceAttributes::SERVER_ADDRESS => $this->mysqlHost,
1228-
TraceAttributes::DB_USER => $this->user,
1229-
TraceAttributes::DB_NAMESPACE => 'otel_db2',
1230-
TraceAttributes::DB_SYSTEM => 'mysql',
1231-
]);
12321377

12331378
mysqli_select_db($mysqli, $this->database);
12341379

1235-
$offset++;
1236-
$res = $mysqli->query('SELECT CURRENT_USER();');
1380+
$res = $mysqli->query('SELECT * FROM users;');
12371381
if ($res instanceof mysqli_result) {
12381382
while ($res->fetch_object()) {
12391383
}
12401384
}
12411385

1386+
$offset++;
12421387
$this->assertSame('mysqli::query', $this->storage->offsetGet($offset)->getName());
12431388
$this->assertAttributes($offset, [
1244-
TraceAttributes::DB_STATEMENT => 'SELECT CURRENT_USER();',
1389+
TraceAttributes::DB_STATEMENT => 'SELECT * FROM users;',
12451390
TraceAttributes::DB_OPERATION_NAME => 'SELECT',
12461391
TraceAttributes::SERVER_ADDRESS => $this->mysqlHost,
12471392
TraceAttributes::DB_USER => $this->user,
@@ -1255,9 +1400,16 @@ public function test_mysqli_select_db(): void
12551400

12561401
}
12571402

1403+
$res = $mysqli->query('SELECT * FROM users;');
1404+
if ($res instanceof mysqli_result) {
1405+
while ($res->fetch_object()) {
1406+
}
1407+
}
1408+
1409+
$offset++;
12581410
$this->assertSame('mysqli::query', $this->storage->offsetGet($offset)->getName());
12591411
$this->assertAttributes($offset, [
1260-
TraceAttributes::DB_STATEMENT => 'SELECT CURRENT_USER();',
1412+
TraceAttributes::DB_STATEMENT => 'SELECT * FROM users;',
12611413
TraceAttributes::DB_OPERATION_NAME => 'SELECT',
12621414
TraceAttributes::SERVER_ADDRESS => $this->mysqlHost,
12631415
TraceAttributes::DB_USER => $this->user,

0 commit comments

Comments
 (0)