diff --git a/src/Instrumentation/IO/README.md b/src/Instrumentation/IO/README.md index 0ffb86b02..c63ceca6d 100644 --- a/src/Instrumentation/IO/README.md +++ b/src/Instrumentation/IO/README.md @@ -22,6 +22,10 @@ following functions: - `file_put_contents` - `curl_init` - `curl_exec` +- `ob_start` +- `ob_clean` +- `ob_flush` +- `flush` ## Configuration @@ -29,4 +33,4 @@ The extension can be disabled via [runtime configuration](https://opentelemetry. ```shell OTEL_PHP_DISABLED_INSTRUMENTATIONS=io -``` \ No newline at end of file +``` diff --git a/src/Instrumentation/IO/src/IOInstrumentation.php b/src/Instrumentation/IO/src/IOInstrumentation.php index 0582960f7..04821991e 100644 --- a/src/Instrumentation/IO/src/IOInstrumentation.php +++ b/src/Instrumentation/IO/src/IOInstrumentation.php @@ -33,6 +33,12 @@ public static function register(): void self::_hook($instrumentation, null, 'file_get_contents', 'file_get_contents'); self::_hook($instrumentation, null, 'file_put_contents', 'file_put_contents'); + // Output buffer functions + self::_hook($instrumentation, null, 'ob_start', 'ob_start'); + self::_hook($instrumentation, null, 'ob_clean', 'ob_clean'); + self::_hook($instrumentation, null, 'ob_flush', 'ob_flush'); + self::_hook($instrumentation, null, 'flush', 'flush'); + self::_hook($instrumentation, null, 'curl_init', 'curl_init'); self::_hook($instrumentation, null, 'curl_exec', 'curl_exec'); } @@ -109,6 +115,19 @@ private static function addParams(SpanBuilderInterface $builder, string $functio case 'file_put_contents': $builder->setAttribute('code.params.filename', $params[0]); + break; + case 'ob_start': + if (isset($params[0]) && is_callable($params[0])) { + // We can't directly serialize the callback, so we'll just note that one was provided + $builder->setAttribute('code.params.has_callback', true); + } + if (isset($params[1])) { + $builder->setAttribute('code.params.chunk_size', $params[1]); + } + if (isset($params[2])) { + $builder->setAttribute('code.params.flags', $params[2]); + } + break; } } diff --git a/src/Instrumentation/IO/tests/Integration/IOInstrumentationTest.php b/src/Instrumentation/IO/tests/Integration/IOInstrumentationTest.php index f9f49044e..d23564c16 100644 --- a/src/Instrumentation/IO/tests/Integration/IOInstrumentationTest.php +++ b/src/Instrumentation/IO/tests/Integration/IOInstrumentationTest.php @@ -37,6 +37,10 @@ public function setUp(): void public function tearDown(): void { $this->scope->detach(); + // Clean up any output buffers that might have been started during tests + while (ob_get_level() > 0) { + ob_end_clean(); + } } public function test_io_calls(): void @@ -83,4 +87,44 @@ public function test_io_calls(): void $this->span = $this->storage->offsetGet(6); $this->assertSame('curl_exec', $this->span->getName()); } + + public function test_output_buffer_calls(): void + { + // Make sure we start with a clean state + while (ob_get_level() > 0) { + ob_end_clean(); + } + // Test ob_start with parameters + $callback = function ($buffer) { + return $buffer; + }; + ob_start($callback, 4096, PHP_OUTPUT_HANDLER_STDFLAGS); + $this->assertCount(1, $this->storage); + $this->span = $this->storage->offsetGet(0); + $this->assertSame('ob_start', $this->span->getName()); + $this->assertTrue($this->span->getAttributes()->get('code.params.has_callback')); + $this->assertSame(4096, $this->span->getAttributes()->get('code.params.chunk_size')); + $this->assertSame(PHP_OUTPUT_HANDLER_STDFLAGS, $this->span->getAttributes()->get('code.params.flags')); + + // Test ob_clean + ob_clean(); + $this->assertCount(2, $this->storage); + $this->span = $this->storage->offsetGet(1); + $this->assertSame('ob_clean', $this->span->getName()); + + // Test ob_flush + ob_flush(); + $this->assertCount(3, $this->storage); + $this->span = $this->storage->offsetGet(2); + $this->assertSame('ob_flush', $this->span->getName()); + + // Test flush + flush(); + $this->assertCount(4, $this->storage); + $this->span = $this->storage->offsetGet(3); + $this->assertSame('flush', $this->span->getName()); + + // Clean up + ob_end_clean(); + } }