Flow PHP - Telemetría
La observabilidad - una forma de monitorear el comportamiento de un sistema basándose en las señales que emite.
Desde hace algunos años, un estándar domina el mercado y gana una adopción cada vez más amplia en los servicios APM: OpenTelemetry.
OpenTelemetry proporciona no solo una especificación, protocolos o convenciones de nomenclatura. También es un conjunto de bibliotecas y SDK que permiten una adopción "fácil" en prácticamente cualquier sistema.
Entonces, ¿por qué decidí escribir mi propia biblioteca en lugar de usar las soluciones existentes?
Todo comenzó cuando quise integrar el DataFrame de Flow PHP (junto con algunas bibliotecas como postgresql o filesystem) con OpenTelemetry.
El primer problema que encontré fueron las dependencias. El SDK de OpenTelemetry requiere:
php: ^8.1ext-json: *nyholm/psr7-server: ^1.1open-telemetry/api: ^1.7open-telemetry/context: ^1.4open-telemetry/sem-conv: ^1.0php-http/discovery: ^1.14psr/http-client: ^1.0psr/http-client-implementation: ^1.0psr/http-factory-implementation: ^1.0psr/http-message: ^1.0.1|^2.0psr/log: ^1.1|^2.0|^3.0ramsey/uuid: ^3.0 || ^4.0symfony/polyfill-mbstring: ^1.23symfony/polyfill-php82: ^1.26tbachert/spi: ^1.0.5
Considerando que actualmente Flow PHP (en su versión base) solo requiere:
php: ~8.3.0 || ~8.4.0 || ~8.5.0composer-runtime-api: ^2.0ext-json: *brick/math: ^0.14.2flow-php/array-dot: 0.32.0flow-php/filesystem: 0.32.0flow-php/types: 0.32.0psr/clock: ^1.0psr/simple-cache: ^1.0 || ^2.0 || ^3.0symfony/string: ^6.4 || ^7.3 || ^8.0webmozart/glob: ^3.0 || ^4.0
El número de dependencias prácticamente se duplicaría. Para un framework que los usuarios deben agregar a la lista de dependencias de sus sistemas, esto se vuelve algo problemático debido a los posibles conflictos de versiones.
Teóricamente, podría hacer que Flow dependiera solo de open-telemetry/api y agregar el SDK como dependencia
opcional. Sin embargo, cuando comencé a examinar más de cerca cómo está construida la API, me di cuenta de que no era
compatible con la arquitectura que adopté en Flow.
El problema principal para mí eran todos esos estados globales, singletons y otros mecanismos diseñados para facilitar la auto-instrumentación. Ejemplo: OpenTelemetry Globals.
Probablemente podría intentar eludir Globals, pero mirando las implementaciones de instrumentación basadas en OpenTelemetry, rápidamente me encontraría con problemas - el SDK está muy estrechamente acoplado con la API, y la API está muy estrechamente acoplada con el estado global.
Aquí tuve que tomar una decisión. O separar el código de Flow usando una abstracción adicional, o construir mi propia API que también serviría como abstracción para la telemetría.
Dado que la API en sí no es tan compleja, decidí construir una API independiente y una
capa de transporte conformes con el protocolo OTLP. Así nació una pequeña biblioteca llamada
flow-php/telemetry
La API flow-php/telemetry es muy no invasiva, no depende de estados globales, se basa
principalmente en contratos, y proporciona implementaciones muy ligeras InMemory y Void
útiles en pruebas.
Pero la API sola no es suficiente para enviar señales a ningún lugar. Necesitamos un serializador y un transporte para eso (esto es lo que hace el SDK de OpenTelemetry).
Flow proporciona algo muy similar:
flow-php/telemetry-otlp-bridge.
La tarea de esta biblioteca es:
- Serializar señales según el protocolo OTLP
- Enviar señales a OpenTelemetry Collector
flow-php/telemetry-otlp-bridge puede serializar señales a:
- JSON
- Protobuf - requiere la extensión protobuf
Las señales pueden transmitirse a través de:
- curl - transporte completamente asíncrono incorporado
- gRPC - requiere la extensión gRPC
- HTTP - requiere implementaciones de
psr/http-clientypsr/http-factory
Señales
¿Qué son las señales?
Métricas - datos numéricos sobre el estado del sistema, por ej. solicitudes por segundo, tiempo de respuesta promedio, etc.
Traces / Spans - representan operaciones individuales en el sistema, junto con sus atributos, duración, etc.
Una Trace es en realidad una colección de Spans vinculados entre sí, formando un árbol de operaciones.
Además, una Trace puede vincularse a métricas, logs u otras Traces, proporcionando una imagen más completa de lo que está sucediendo en el sistema.
Logs - datos textuales sobre eventos en el sistema, que a menudo contienen atributos adicionales como el nivel de log o el contexto del evento.
Auto-instrumentación
Uno de los principales beneficios de OpenTelemetry es la auto-instrumentación.
Según el diseño, los autores de bibliotecas (como yo) deberían integrar sus herramientas con la API/SDK de OpenTelemetry. De esta manera, si configuramos la serialización y la capa de transporte en un sistema que usa tales bibliotecas, obtenemos automáticamente telemetría básica sin esfuerzo adicional.
Si la comunidad PHP compartiera la visión de los creadores de la API/SDK - en casi todos los proyectos bastaría con configurar el SDK para tener una idea de cómo funciona el sistema en producción.
Por el momento, sin embargo, no existe una sola biblioteca nativamente integrada con OpenTelemetry.
No sé por qué. Entornos como Java o .NET tienen una adopción de OpenTelemetry mucho mejor que PHP.
Quizás si el SDK de OpenTelemetry fuera más popular, no habría decidido escribir mi propia biblioteca.
Sin embargo, lenta pero firmemente, he comenzado a integrar todas las bibliotecas que mantengo con flow-php/telemetry.
También he creado varias extensiones para frameworks/bibliotecas existentes - permitiendo una auto-instrumentación igualmente efectiva.
Bibliotecas con integración nativa:
flow-php/etlflow-php/filesystemflow-php/postgresql
Extensiones para bibliotecas existentes:
flow-php/psr7-telemetry-bridgeflow-php/psr18-telemetry-bridgeflow-php/monolog-telemetry-bridgeflow-php/symfony-http-foundation-telemetry-bridgeflow-php/symfony-telemetry-bundleflow-php/phpunit-telemetry-bridge
Nota: Si estás desarrollando tu propio proyecto open source - contáctame.
Juntos encontraremos la forma más sencilla de integrar tu herramienta con la telemetría.
Podemos hacerlo mediante integración nativa o extensión.
OTEL Collector
OTEL Collector es una herramienta fantástica que sirve como hub central para la recepción / procesamiento /
reenvío de señales.
Colocando el collector entre tu sistema y el APM (Application Performance Monitoring System), evitas el vendor lock-in.
También evitamos operaciones I/O innecesarias y bloqueantes. Nuestras señales se almacenan en memoria, y cuando el buffer se llena o el script termina, se envían al collector.
A continuación, ejemplos de configuración del collector desde el monorepo de Flow:
APM
Existen muchas soluciones populares en el mercado para monitorear sistemas compatibles con
el protocolo OTLP.
Estas incluyen herramientas tanto de pago como completamente gratuitas, disponibles como servicios u open source.
Sin embargo, antes de elegir el APM adecuado, vale la pena familiarizarse con el protocolo OTLP en sí.
Por supuesto, la mayoría de los APM proporcionan sus propias bibliotecas para monitorear sistemas, pero usarlas te vincula muy estrechamente a un proveedor específico.
Mi solución preferida (al menos por ahora) para el desarrollo local es Aspire
Aspire también está configurado como APM en el monorepo de Flow. Puedes encontrar el ejemplo de configuración aquí.
Demo
Puedes encontrar ejemplos de código flow-php/telemetry en
https://flow-php.com.
Cada uno de ellos se puede ejecutar en el navegador usando el Playground de Flow.
Pero no hay mejor manera de aprender una herramienta que poniéndole las manos encima, así que animo a todos los interesados
a instalar Aspire, OTEL Collector localmente, y agregar flow-php/telemetry
junto con flow-php/telemetry-otlp-bridge.
¡Después solo queda empezar a emitir señales!
A continuación, una captura de pantalla de Aspire local mostrando la telemetría recopilada durante la ejecución de pruebas de todo el monorepo de Flow.