Skip to main content

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
{
"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.

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

MethodParameter
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

MethodParameter
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 (NaN and Infinity are not allowed).
  • Arrays remove null values and must retain at least one valid value.
  • Array values must match the expected type.

Runtime Behavior

  • Throws Exception for invalid input.
  • Throws Exception when 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:

  1. Navigate to APM Explorer
  2. Open the trace
  3. Select a span
  4. Verify custom attributes in span attributes section

Support

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.