PHP Custom Instrumentation
This guide explains how to implement Custom Instrumentation in PHP applications using the Motadata Custom Instrumentation package.
Custom instrumentation enables you to inject business-specific attributes into the currently active span using OTel-compatible APIs, without affecting the existing auto-instrumentation flow. The package ensures attribute validation, consistent namespacing, and safe runtime behavior across concurrent PHP requests.
Prerequisites
- The application must already be instrumented using Motadata Auto Instrumentation so that span context is available.
- PHP version >= 8.1 and <= 8.4
- Motadata Agent 8.1.2 or higher
- The application must already be generating traces in APM
Configuration Steps
Step 1: Install the PHP Custom Instrumentation Package
Option 1: Using Composer CLI
If the package is available on Package list:
composer require motadata-apm/custom-instrumentation
Option 2: Manual Installation (composer.json)
{
"require": {
"motadata-apm/custom-instrumentation": "^1.0"
}
}
Then run:
composer update motadata-apm/custom-instrumentation
Option 3: Using VCS Repository (Recommended for Private/Internal Use)
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/motadata2025/motadata-apm-custom-instrumentation-php"
}
],
"require": {
"motadata-apm/custom-instrumentation": "dev-main"
}
}
Then run:
composer update
Step 2: Import the Package
use Motadata\Apm\CustomInstrumentation;
Step 3: Add Custom Attributes to the Active Span
Use the package APIs to inject custom attributes into the currently active span.
Basic Example
<?php
use Motadata\Apm\CustomInstrumentation;
try {
CustomInstrumentation::setInt('apm.user.id', 98765);
CustomInstrumentation::setInt('apm.order.id', 12345);
CustomInstrumentation::setFloat('apm.order.amount', 199.99);
CustomInstrumentation::set('apm.request.success', true);
CustomInstrumentation::setString('apm.user.name', 'john.doe');
CustomInstrumentation::setStringArray('apm.tags', ['api', 'production', 'critical']);
} catch (Exception $exception) {
error_log('Failed to set custom attributes: ' . $exception->getMessage());
}
Keys are automatically prefixed with apm. when missing. However, explicitly providing the prefix ensures consistency across services.
Since all APIs throw exceptions for invalid input or missing span context, wrap calls in try/catch blocks.
Recommended Production Pattern
try {
CustomInstrumentation::setInt('apm.order.id', $orderId);
} catch (Exception $exception) {
error_log('Failed to set apm.order.id: ' . $exception->getMessage());
}
Supported Methods
Scalar Attribute Setters
| Method | Parameter |
|---|---|
set(string $key, bool $value) | bool |
setInt(string $key, int $value) | int |
setFloat(string $key, float $value) | float |
setString(string $key, string $value) | string |
Array Attribute Setters
| Method | Parameter |
|---|---|
setBoolArray(string $key, array $values) | bool[] |
setIntArray(string $key, array $values) | int[] |
setFloatArray(string $key, array $values) | float[] |
setStringArray(string $key, array $values) | string[] |
Attribute Behavior and Validation Rules
The PHP Custom Instrumentation package validates both keys and values before attaching attributes to the span.
Key Handling
- Keys are trimmed, normalized to lowercase, and prefixed with
apm.if missing. - Keys allow only alphanumeric characters and dots.
- Keys must not be null, empty, or whitespace-only.
Value Handling
- Scalar values must not be null.
- Empty string values are ignored and not added to traces.
- Float values must be finite (
NaNandInfinityare not allowed). - Arrays remove null values and must retain at least one valid value.
- Array values must match the expected type.
Runtime Behavior
- Throws
Exceptionfor invalid input. - Throws
Exceptionwhen no active span is present. - Safe to use in concurrent PHP request environments.
Example Scenarios
Add Business Context
try {
CustomInstrumentation::setInt('apm.customer.id', 1001);
CustomInstrumentation::setString('apm.customer.name', 'john.doe');
CustomInstrumentation::setString('apm.customer.tier', 'gold');
} catch (Exception $exception) {
error_log('Failed to set customer attributes: ' . $exception->getMessage());
}
Add Transaction Details
try {
CustomInstrumentation::setInt('apm.order.id', 987654);
CustomInstrumentation::setFloat('apm.order.amount', 2599.75);
CustomInstrumentation::setString('apm.order.currency', 'USD');
CustomInstrumentation::set('apm.order.success', true);
} catch (Exception $exception) {
error_log('Failed to set order attributes: ' . $exception->getMessage());
}
Add Collection Attributes
try {
CustomInstrumentation::setStringArray(
'apm.order.items',
['router', 'switch', 'firewall']
);
} catch (Exception $exception) {
error_log('Failed to set order items: ' . $exception->getMessage());
}
Add Failure Context
try {
processPayment($paymentRequest);
} catch (Exception $exception) {
try {
CustomInstrumentation::setString('apm.payment.status', 'failed');
CustomInstrumentation::setString('apm.payment.error_message', $exception->getMessage());
} catch (Exception $instrumentationError) {
error_log('Failed to set payment error attributes: ' . $instrumentationError->getMessage());
}
throw $exception;
}
Advanced Example
The following example demonstrates enterprise-level instrumentation across a request lifecycle.
<?php
use Motadata\Apm\CustomInstrumentation;
function processOrder($order) {
try {
CustomInstrumentation::setInt('apm.order.id', $order['id']);
CustomInstrumentation::setInt('apm.order.customer_id', $order['customer_id']);
CustomInstrumentation::setFloat('apm.order.amount', $order['amount']);
CustomInstrumentation::setString('apm.order.currency', $order['currency']);
CustomInstrumentation::setStringArray(
'apm.order.tags',
['sales', 'priority', 'online']
);
if ($order['amount'] > 10000) {
CustomInstrumentation::set('apm.order.high_value', true);
}
CustomInstrumentation::setString('apm.order.stage', 'validation');
validateOrder($order);
CustomInstrumentation::setString('apm.order.stage', 'inventory_check');
checkInventory($order);
CustomInstrumentation::setString('apm.order.stage', 'payment_processing');
processPayment($order);
CustomInstrumentation::setString('apm.order.stage', 'dispatch');
dispatchOrder($order);
CustomInstrumentation::setString('apm.order.status', 'completed');
} catch (Exception $exception) {
try {
CustomInstrumentation::setString('apm.order.status', 'failed');
CustomInstrumentation::setString('apm.order.error_message', $exception->getMessage());
} catch (Exception $instrumentationError) {
error_log('Failed to set failure attributes: ' . $instrumentationError->getMessage());
}
throw $exception;
}
}
This enables full correlation of business metadata (order lifecycle, customer context, failure reasons) directly within traces.
Best Practices
- Use consistent naming such as
apm.order.id. - Prefer explicit
apm.prefixing. - Use correct data types (
int,float,bool,string). - Use array setters for collections.
- Keep naming consistent across services for observability queries.
- Always handle exceptions to prevent instrumentation failures from impacting application flow.
What to Avoid
- Do not inject sensitive data such as passwords or tokens.
- Do not use inconsistent or unclear key naming.
- Do not pass invalid or empty values.
- Do not assume span context is always available.
Verify the Attributes
After implementing:
- Navigate to APM Explorer
- Open the trace
- Select a span
- Verify custom attributes in span attributes section
Support
- Email:
engg@motadata.com - Issues: https://github.com/motadata2025/motadata-apm-custom-instrumentation-php/issues
License
Copyright (c) Motadata 2026. All rights reserved.
Proprietary software; see LICENSE for full terms.
Summary
The Motadata PHP Custom Instrumentation package provides a structured, validated, and enterprise-ready way to inject business-specific attributes into traces. It preserves auto-instrumentation behavior, and enhances trace observability without impacting application performance.