Skip to main content
observability-instrumentation Add business metrics using AWS EMF (Embedded Metric Format) to API endpoints. Focus on domain-specific metrics only - AWS AppRunner provides default SLO/SLA metrics. Use when implementing new endpoints, adding command handlers, or instrumenting business events.
Aller à l'installation Skills Marketplace Découvrez et explorez les compétences IA créées par la communauté.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Copier le promptAfficher les détails du prompt Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
npx skills add https://github.com/VilnaCRM-Org/core-service --skill observability-instrumentationLa commande reste sur une seule ligne. Faites défiler horizontalement pour la vérifier avant de la copier.
Vous préférez une copie locale ? Téléchargez les fichiers actuellement disponibles dans SkillsMP.
Télécharger Zip Téléchargement... Métiers associés SOC
Basé sur la classification professionnelle SOC
Plus depuis ce dépôt Run comprehensive CI checks before committing changes. Use when the user asks to run CI, run quality checks, validate code quality, or before finishing any task that involves code changes.
Ensure proper code organization with class names, directories, namespaces, and naming consistency following the principle "Directory X contains ONLY class type X".
implementing-ddd-architecture Design and implement DDD patterns (entities, value objects, aggregates, CQRS). Use when creating new domain objects, implementing bounded contexts, designing repository interfaces, or learning proper layer separation. For fixing existing Deptrac violations, use the deptrac-fixer skill instead.
Explorateur de fichiers
6 fichiers name observability-instrumentation description Add business metrics using AWS EMF (Embedded Metric Format) to API endpoints. Focus on domain-specific metrics only - AWS AppRunner provides default SLO/SLA metrics. Use when implementing new endpoints, adding command handlers, or instrumenting business events.
Business Metrics with AWS EMF
Instrument API endpoints with business metrics using AWS CloudWatch Embedded Metric Format (EMF). This skill focuses exclusively on domain-specific metrics - AWS AppRunner already provides infrastructure SLO/SLA metrics automatically.
What This Skill Covers
Business metrics - Domain events (customers created, orders placed, payments processed)
AWS EMF format - Logs that automatically become CloudWatch metrics
Event subscribers - Metrics emitted via domain event subscribers (not in handlers)
Type-safe metrics - Concrete metric classes instead of arrays
SOLID principles - Single Responsibility (subscribers) + Open/Closed (new metric classes)
What This Skill Does NOT Cover
Infrastructure metrics - Latency, error rates, RPS (AWS AppRunner provides these)
SLO/SLA metrics - Availability, response times (AWS AppRunner provides these)
Distributed tracing - Use AWS X-Ray integration instead
When to Use This Skill
Use this skill when:
Implementing new API endpoints that have business significance
Adding domain events that should trigger metric emission
Tracking domain events for analytics and business intelligence
Building dashboards for business KPIs
Architecture Overview
Business metrics follow these patterns:
- Each metric type is a concrete class extending
Metric classes
BusinessMetric
Event subscribers - Metrics are emitted via domain event subscribers (not hardcoded in handlers)
Symfony logger - EMF output goes through Monolog with a custom EMF formatter
No arrays - All metric configuration uses typed objects, not arrays
Collections - Multiple metrics use MetricCollection, not arrays
SOLID Principles in Observability
Single Responsibility Principle (SRP) Each class has ONE responsibility:
Class Responsibility CustomersCreatedMetricDefine metric name, value, dimensions CustomerCreatedMetricsSubscriberListen to event, emit metric AwsEmfBusinessMetricsEmitterFormat and write EMF logs MetricCollectionHold multiple metrics for batch emission
Anti-pattern : Metrics emitted directly in command handlers (violates SRP - handler should only handle commands)
Open/Closed Principle (OCP)
Open for extension : Add new metrics via new classes
Closed for modification : Don't change existing metric/emitter code
final readonly class OrdersPlacedMetric extends EndpointOperationBusinessMetric { ... }
Why Event Subscribers (Not Handler Injection)
final class CreateCustomerHandler
{
public function __construct (
private CustomerRepository $repository ,
private BusinessMetricsEmitterInterface $metrics // Wrong!
) {}
}
final class CustomerCreatedMetricsSubscriber implements DomainEventSubscriberInterface
{
public function __invoke (CustomerCreatedEvent $event ): void
{
$this ->metricsEmitter->emit ($this ->metricFactory->create ());
}
}
Handler focuses on domain logic only
Metrics emission is decoupled and testable
Easy to add/remove metrics without touching business logic
Multiple subscribers can react to same event
Type-Safe Metric Class Hierarchy BusinessMetric (abstract)
├── EndpointOperationBusinessMetric (abstract) - for metrics with Endpoint/Operation dimensions
│ ├── CustomersCreatedMetric
│ ├── CustomersUpdatedMetric
│ ├── CustomersDeletedMetric
│ └── EndpointInvocationsMetric
└── (other base classes for different dimension patterns)
MetricDimensionsInterface
├── EndpointOperationMetricDimensions - Endpoint + Operation
└── (custom dimensions for specific metrics)
MetricDimensions - typed collection of MetricDimension objects
MetricDimension - key/value pair
MetricUnit (enum)
├── COUNT, NONE, SECONDS, MILLISECONDS, BYTES, PERCENT
MetricCollection - typed collection implementing IteratorAggregate, Countable
Arrays Typed Classes No type safety Full type checking No IDE autocomplete IDE support Runtime errors Compile-time errors Hard to refactor Easy to refactor No encapsulation Validation in constructor
Current Implementation
Metric Base Class (Application Layer)
abstract readonly class BusinessMetric
{
public function __construct (
private float |int $value ,
private MetricUnit $unit
) {}
abstract public function name ( ): string ;
abstract public function dimensions ( ): MetricDimensionsInterface ;
public function value ( ): float |int { return $this ->value; }
public function unit ( ): MetricUnit { return $this ->unit; }
}
Concrete Metric Example
final readonly class CustomersCreatedMetric extends EndpointOperationBusinessMetric
{
private const ENDPOINT = 'Customer' ;
private const OPERATION = 'create' ;
public function __construct (
MetricDimensionsFactoryInterface $dimensionsFactory ,
float |int $value = 1
) {
parent ::__construct ($dimensionsFactory , $value , MetricUnit ::COUNT );
}
public function name ( ): string
{
return 'CustomersCreated' ;
}
protected function endpoint ( ): string
{
return self ::ENDPOINT ;
}
protected function operation ( ): string
{
return self ::OPERATION ;
}
}
Emitter Interface (Application Layer)
interface BusinessMetricsEmitterInterface
{
public function emit (BusinessMetric $metric ): void ;
public function emitCollection (MetricCollection $metrics ): void ;
}
Metrics Event Subscriber
final readonly class CustomerCreatedMetricsSubscriber implements DomainEventSubscriberInterface
{
public function __construct (
private BusinessMetricsEmitterInterface $metricsEmitter ,
private CustomersCreatedMetricFactoryInterface $metricFactory
) {
}
public function __invoke (CustomerCreatedEvent $event ): void
{
$this ->metricsEmitter->emit ($this ->metricFactory->create ());
}
public function subscribedTo ( ): array
{
return [CustomerCreatedEvent ::class ];
}
}
AWS EMF Format AWS Embedded Metric Format allows you to embed custom metrics in structured log events. CloudWatch automatically extracts metrics from EMF-formatted logs.
EMF Log Structure {
"_aws" : {
"Timestamp" : 1702425600000 ,
"CloudWatchMetrics" : [
{
"Namespace" : "CCore/BusinessMetrics" ,
"Dimensions" : [ [ "Endpoint" , "Operation" ] ] ,
"Metrics" : [ { "Name" : "CustomersCreated" , "Unit" : "Count" } ]
}
]
} ,
"Endpoint" : "Customer" ,
"Operation" : "create" ,
"CustomersCreated" : 1
}
When this log is written to stdout via the EMF Monolog channel, CloudWatch automatically:
Extracts CustomersCreated as a metric
Associates it with the CCore/BusinessMetrics namespace
Applies dimensions Endpoint and Operation
Creating New Business Metrics
Step 1: Create the Metric Class
namespace App \Core \Order \Application \Metric ;
use App \Shared \Application \Observability \Metric \BusinessMetric ;
use App \Shared \Application \Observability \Metric \MetricDimension ;
use App \Shared \Application \Observability \Metric \MetricDimensions ;
use App \Shared \Application \Observability \Metric \MetricDimensionsFactoryInterface ;
use App \Shared \Application \Observability \Metric \MetricDimensionsInterface ;
use App \Shared \Application \Observability \Metric \MetricUnit ;
final readonly class OrdersPlacedMetricDimensions implements MetricDimensionsInterface
{
public function __construct (
private MetricDimensionsFactoryInterface $dimensionsFactory ,
private string $paymentMethod
) {
}
public function values ( ): MetricDimensions
{
return $this ->dimensionsFactory->endpointOperationWith (
'Order' ,
'create' ,
new MetricDimension ('PaymentMethod' , $this ->paymentMethod)
);
}
}
final readonly class OrdersPlacedMetric extends BusinessMetric
{
public function __construct (
private MetricDimensionsFactoryInterface $dimensionsFactory ,
private string $paymentMethod ,
float |int $value = 1
) {
parent ::__construct ($value , MetricUnit ::COUNT );
}
public function name ( ): string
{
return 'OrdersPlaced' ;
}
public function dimensions ( ): MetricDimensionsInterface
{
return new OrdersPlacedMetricDimensions (
dimensionsFactory: $this ->dimensionsFactory,
paymentMethod: $this ->paymentMethod
);
}
}
Step 2: Create the Event Subscriber
namespace App \Core \Order \Application \EventSubscriber ;
use App \Core \Order \Application \Factory \OrdersPlacedMetricFactoryInterface ;
use App \Core \Order \Domain \Event \OrderPlacedEvent ;
use App \Shared \Application \Observability \Emitter \BusinessMetricsEmitterInterface ;
use App \Shared \Domain \Bus \Event \DomainEventSubscriberInterface ;
final readonly class OrderPlacedMetricsSubscriber implements DomainEventSubscriberInterface
{
public function __construct (
private BusinessMetricsEmitterInterface $metricsEmitter ,
private OrdersPlacedMetricFactoryInterface $metricFactory
) {}
public function __invoke (OrderPlacedEvent $event ): void
{
$this ->metricsEmitter->emit ($this ->metricFactory->create ($event ->paymentMethod ()));
}
public function subscribedTo ( ): array
{
return [OrderPlacedEvent ::class ];
}
}
Step 3: For Multiple Metrics - Use MetricCollection
$this ->metricsEmitter->emitCollection (new MetricCollection (
$this ->ordersPlacedMetricFactory->create ($event ->paymentMethod ()),
$this ->orderValueMetricFactory->create ($event ->totalAmount ())
));
Dimension Best Practices
Recommended Dimensions Dimension Description Cardinality EndpointAPI resource name Low OperationCRUD action Very Low PaymentMethodPayment type Low CustomerTypeCustomer segment Low
Avoid High-Cardinality Dimensions
Customer IDs
Order IDs
Session IDs
Timestamps
These create too many unique metric streams and increase CloudWatch costs.
Metric Naming Conventions
Format {Entity}{Action} # PascalCase
Examples Good Bad CustomersCreatedcustomer_createdOrdersPlacedorders.placed.countPaymentsProcessedpayment-processed
Guidelines
Use PascalCase for metric names
Use plural nouns for counters (CustomersCreated not CustomerCreated)
Use past tense for completed actions
Testing Business Metrics
Use the Spy in Tests use App \Shared \Application \Observability \Metric \MetricDimension ;
use App \Shared \Infrastructure \Observability \Factory \MetricDimensionsFactory ;
use App \Tests \Unit \Shared \Infrastructure \Observability \BusinessMetricsEmitterSpy ;
final class CustomerCreatedMetricsSubscriberTest extends TestCase
{
public function testEmitsMetricOnCustomerCreated ( ): void
{
$metricsSpy = new BusinessMetricsEmitterSpy ();
$dimensionsFactory = new MetricDimensionsFactory ();
$metricFactory = new CustomersCreatedMetricFactory ($dimensionsFactory );
$logger = $this ->createMock (LoggerInterface ::class );
$subscriber = new CustomerCreatedMetricsSubscriber (
$metricsSpy ,
$metricFactory ,
$logger
);
$event = new CustomerCreatedEvent ($customerId , $email );
($subscriber )($event );
self ::assertSame (1 , $metricsSpy ->count ());
foreach ($metricsSpy ->emitted () as $metric ) {
self ::assertSame ('CustomersCreated' , $metric ->name ());
self ::assertSame (1 , $metric ->value ());
self ::assertSame ('Customer' , $metric ->dimensions ()->values ()->get ('Endpoint' ));
self ::assertSame ('create' , $metric ->dimensions ()->values ()->get ('Operation' ));
}
$metricsSpy ->assertEmittedWithDimensions (
'CustomersCreated' ,
new MetricDimension ('Endpoint' , 'Customer' ),
new MetricDimension ('Operation' , 'create' )
);
}
}
Test Service Configuration In config/services_test.yaml, the spy is configured:
App\Shared\Application\Observability\Emitter\BusinessMetricsEmitterInterface: '@App\Tests\Unit\Shared\Infrastructure\Observability\BusinessMetricsEmitterSpy'
App\Tests\Unit\Shared\Infrastructure\Observability\BusinessMetricsEmitterSpy:
public: true
CloudWatch Queries After deploying, query your business metrics:
SELECT SUM (EndpointInvocations)
FROM "CCore/BusinessMetrics"
GROUP BY Endpoint
SELECT SUM (CustomersCreated)
FROM "CCore/BusinessMetrics"
WHERE Endpoint = 'Customer'
What NOT to Track Remember: AWS AppRunner already provides infrastructure metrics.
Request latency
Error rates
Response times
HTTP status codes
Memory usage
CPU usage
Business events (orders placed, customers created)
Business values (order amounts, payment totals)
Domain-specific actions (logins, uploads, exports)
Success Criteria After implementing business metrics:
Each domain event that needs tracking has a corresponding metric subscriber
Metrics use typed classes (not arrays)
Metrics are emitted via event subscribers (not hardcoded in handlers)
Dimensions provide meaningful segmentation
Unit tests verify metric emission
No infrastructure metrics (AppRunner handles those)
SOLID Compliance Checklist
Type Safety Checklist
Files Reference
Metric Classes
src/Shared/Application/Observability/Metric/BusinessMetric.php - Base class
src/Shared/Application/Observability/Metric/MetricUnit.php - Unit enum
src/Shared/Application/Observability/Metric/MetricDimension.php - Dimension key/value
src/Shared/Application/Observability/Metric/MetricDimensions.php - Dimension collection
src/Shared/Application/Observability/Metric/MetricCollection.php - Metrics collection
src/Shared/Application/Observability/Metric/EndpointInvocationsMetric.php - Endpoint metric
src/Core/Customer/Application/Metric/CustomersCreatedMetric.php - Customer create metric
src/Core/Customer/Application/Metric/CustomersUpdatedMetric.php - Customer update metric
src/Core/Customer/Application/Metric/CustomersDeletedMetric.php - Customer delete metric
Infrastructure
src/Shared/Application/Observability/Emitter/BusinessMetricsEmitterInterface.php - Interface
src/Shared/Infrastructure/Observability/AwsEmfBusinessMetricsEmitter.php - EMF implementation
src/Shared/Infrastructure/Observability/EmfLogFormatter.php - Monolog formatter
Event Subscribers
src/Shared/Infrastructure/Observability/ApiEndpointBusinessMetricsSubscriber.php - HTTP metrics
src/Core/Customer/Application/EventSubscriber/CustomerCreatedMetricsSubscriber.php
src/Core/Customer/Application/EventSubscriber/CustomerUpdatedMetricsSubscriber.php
src/Core/Customer/Application/EventSubscriber/CustomerDeletedMetricsSubscriber.php
Configuration
config/packages/monolog.yaml - EMF channel configuration
config/services.yaml - Production wiring
config/services_test.yaml - Test spy wiring
AWS Documentation