Help us improve
Share bugs, ideas, or general feedback.
From acc
Analyzes PHP code for observability gaps: detects unstructured logging, missing correlation IDs, health endpoints, and OpenTelemetry setup. Improves production debugging and monitoring.
npx claudepluginhub dykyi-roman/awesome-claude-code --plugin accHow this skill is triggered โ by the user, by Claude, or both
Slash command
/acc:check-observability-coverageThe summary Claude sees in its skill listing โ used to decide when to auto-load this skill
Analyze PHP code for observability gaps that hinder debugging, monitoring, and incident response in production.
Provides observability knowledge base covering three pillars (logs, metrics, traces), structured logging with Monolog, distributed tracing via OpenTelemetry, RED/USE metrics collection, and SLI/SLO/SLA definitions for PHP apps.
Audits code for observability gaps like leftover debug logs, unlogged errors, missing log context, and untracked slow operations using existing tooling.
Audits existing observability instrumentation and designs structured logging, metrics, distributed tracing, and alerting for production services. Use for coverage gaps, SLIs/SLOs.
Share bugs, ideas, or general feedback.
Analyze PHP code for observability gaps that hinder debugging, monitoring, and incident response in production.
<?php
declare(strict_types=1);
// BAD: Raw error_log with unstructured output
final class PaymentService
{
public function charge(Money $amount): void
{
error_log('Payment failed for amount ' . $amount->cents());
}
}
// BAD: echo/print_r/var_dump in production code
final class OrderService
{
public function process(Order $order): void
{
echo 'Processing order: ' . $order->id();
print_r($order->items());
var_dump($order->status());
}
}
// GOOD: Structured logging with context
final readonly class PaymentService
{
public function __construct(
private LoggerInterface $logger,
) {}
public function charge(Money $amount, UserId $userId): void
{
$this->logger->error('Payment charge failed', [
'user_id' => $userId->toString(),
'amount_cents' => $amount->cents(),
'currency' => $amount->currency()->value,
'gateway' => 'stripe',
'timestamp' => (new DateTimeImmutable())->format(DateTimeInterface::RFC3339),
]);
}
}
<?php
declare(strict_types=1);
// BAD: No request tracing across services
final readonly class ApiClient
{
public function call(string $endpoint, array $payload): Response
{
return $this->httpClient->post($endpoint, [
'json' => $payload,
// No correlation ID forwarded
]);
}
}
// BAD: Logger without correlation context
final readonly class OrderHandler
{
public function handle(CreateOrderCommand $command): void
{
$this->logger->info('Order created');
// Which request triggered this? No way to trace.
}
}
// GOOD: Correlation ID propagated across services
final readonly class ApiClient
{
public function __construct(
private HttpClientInterface $httpClient,
private CorrelationIdProvider $correlationProvider,
) {}
public function call(string $endpoint, array $payload): Response
{
return $this->httpClient->post($endpoint, [
'json' => $payload,
'headers' => [
'X-Request-ID' => $this->correlationProvider->get(),
'X-Correlation-ID' => $this->correlationProvider->get(),
],
]);
}
}
// GOOD: Logger processor adds correlation ID automatically
final readonly class CorrelationIdProcessor
{
public function __construct(
private CorrelationIdProvider $correlationProvider,
) {}
public function __invoke(LogRecord $record): LogRecord
{
return $record->with(extra: [
'correlation_id' => $this->correlationProvider->get(),
]);
}
}
<?php
declare(strict_types=1);
// BAD: No health check endpoints -- load balancer cannot detect unhealthy instances
// Application has NO /health, /ready, or /live routes
// GOOD: Comprehensive health check
final readonly class HealthAction
{
public function __construct(
private PDO $database,
private \Redis $redis,
private AMQPConnection $queue,
) {}
public function __invoke(): JsonResponse
{
$checks = [
'database' => $this->checkDatabase(),
'redis' => $this->checkRedis(),
'queue' => $this->checkQueue(),
];
$healthy = !in_array(false, $checks, true);
return new JsonResponse(
data: $checks,
status: $healthy ? 200 : 503,
);
}
private function checkDatabase(): bool
{
try {
$this->database->query('SELECT 1');
return true;
} catch (\PDOException) {
return false;
}
}
private function checkRedis(): bool
{
try {
return $this->redis->ping() === true;
} catch (\RedisException) {
return false;
}
}
private function checkQueue(): bool
{
try {
return $this->queue->isConnected();
} catch (\AMQPException) {
return false;
}
}
}
<?php
declare(strict_types=1);
// BAD: No Prometheus metrics exposed
// No /metrics endpoint, no counter/histogram tracking
// GOOD: Prometheus metrics endpoint with business metrics
final readonly class MetricsAction
{
public function __construct(
private CollectorRegistry $registry,
) {}
public function __invoke(): Response
{
$renderer = new RenderTextFormat();
$result = $renderer->render($this->registry->getMetricFamilySamples());
return new Response($result, 200, [
'Content-Type' => RenderTextFormat::MIME_TYPE,
]);
}
}
// GOOD: Track business metrics
final readonly class OrderService
{
public function __construct(
private Counter $ordersCreated,
private Histogram $orderProcessingDuration,
) {}
public function create(OrderData $data): Order
{
$start = microtime(true);
try {
$order = $this->processOrder($data);
$this->ordersCreated->incBy(1, ['status' => 'success']);
return $order;
} catch (\Throwable $e) {
$this->ordersCreated->incBy(1, ['status' => 'failure']);
throw $e;
} finally {
$this->orderProcessingDuration->observe(
microtime(true) - $start,
['operation' => 'create'],
);
}
}
}
<?php
declare(strict_types=1);
// BAD: No tracing spans in cross-service communication
final readonly class OrderService
{
public function create(OrderData $data): Order
{
$order = $this->repository->save(Order::create($data));
$this->paymentService->charge($order->total());
$this->inventoryService->reserve($order->items());
// Cannot trace which step failed or which was slow
return $order;
}
}
// GOOD: OpenTelemetry spans for cross-service calls
final readonly class OrderService
{
public function __construct(
private TracerInterface $tracer,
private OrderRepository $repository,
private PaymentService $paymentService,
private InventoryService $inventoryService,
) {}
public function create(OrderData $data): Order
{
$span = $this->tracer->spanBuilder('order.create')
->setAttribute('order.items_count', count($data->items))
->startSpan();
try {
$order = $this->repository->save(Order::create($data));
$span->setAttribute('order.id', $order->id()->toString());
$paymentSpan = $this->tracer->spanBuilder('payment.charge')->startSpan();
try {
$this->paymentService->charge($order->total());
} finally {
$paymentSpan->end();
}
$inventorySpan = $this->tracer->spanBuilder('inventory.reserve')->startSpan();
try {
$this->inventoryService->reserve($order->items());
} finally {
$inventorySpan->end();
}
return $order;
} catch (\Throwable $e) {
$span->recordException($e);
$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());
throw $e;
} finally {
$span->end();
}
}
}
# Unstructured logging
Grep: "error_log\(|print_r\(|var_dump\(|var_export\(" --glob "**/*.php"
Grep: "echo\s+['\"]|echo\s+\\\$" --glob "**/src/**/*.php"
# Check for PSR-3 Logger usage
Grep: "LoggerInterface|LoggerAwareInterface" --glob "**/*.php"
# Missing correlation ID propagation
Grep: "X-Request-ID|X-Correlation-ID|correlation_id" --glob "**/*.php"
# Health check endpoints
Grep: "/health|/ready|/live|HealthCheck|HealthAction" --glob "**/*.php"
# Metrics endpoints
Grep: "/metrics|PrometheusMetrics|CollectorRegistry|Counter::class|Histogram::class" --glob "**/*.php"
# OpenTelemetry / tracing
Grep: "TracerInterface|spanBuilder|OpenTelemetry|Jaeger|Zipkin" --glob "**/*.php"
# Structured context in log calls
Grep: "->info\(|->error\(|->warning\(|->debug\(|->critical\(" --glob "**/*.php"
| Pattern | Severity |
|---|---|
| var_dump/print_r in production code | ๐ด Critical |
| No health check endpoints | ๐ด Critical |
| Missing correlation ID propagation | ๐ Major |
| No structured logging (error_log only) | ๐ Major |
| No metrics/Prometheus endpoint | ๐ Major |
| Missing OpenTelemetry tracing | ๐ก Minor |
| Log messages without context array | ๐ก Minor |
### Observability Gap: [Brief Description]
**Severity:** ๐ด/๐ /๐ก
**Location:** `file.php:line`
**Type:** [Unstructured Log|Missing Correlation|No Health Check|No Metrics|No Tracing]
**Issue:**
[Description of the observability gap]
**Impact:**
- Cannot trace requests across services
- No alerting on failures
- Debugging in production is guesswork
**Code:**
```php
// Current code without observability
Fix:
// With proper observability
## When This Is Acceptable
- **Development/test environment** -- var_dump/print_r may be used temporarily during local debugging
- **CLI commands** -- Console output via `echo` is standard for CLI tools
- **Early prototype stage** -- Before production deployment, minimal logging is acceptable
- **Single-service monolith** -- Correlation IDs are less critical when there are no cross-service calls
### False Positive Indicators
- Code is in a test file or fixture
- echo/print_r is inside a Console Command `execute()` method
- Health check exists but is registered in routing config, not discoverable via grep