Nova postagem

Encontrar

Artigo
· jan 21 7min de leitura

Protegiendo un servidor FHIR con OAuth 2.0 mediante IAM

InterSystems API Manager (IAM) es un componente clave de la plataforma de datos InterSystems IRIS y ofrece una gestión centralizada de APIs con un fuerte enfoque en la seguridad. IAM simplifica todo el ciclo de vida de las APIs, desde su creación hasta su retirada, y proporciona un portal de desarrolladores para facilitar el descubrimiento y la integración de APIs. Las funciones de control de acceso permiten a los administradores definir permisos precisos, y IAM se integra de forma fluida con la plataforma de datos IRIS, mejorando las capacidades de gestión e integración de datos.

Las características de IAM incluyen:

  • Pasarela de APIs: centro centralizado de gestión y seguridad de APIs.
  • Gestión del ciclo de vida de las APIs: control completo del ciclo de vida desde la creación hasta la retirada.
  • Seguridad: autenticación, autorización y cifrado de datos.
  • Supervisión y analítica: herramientas para la supervisión del uso y el análisis de patrones.
  • Portal de desarrolladores: portal de descubrimiento de APIs con documentación y pruebas.
  • Control de acceso: control granular sobre el acceso y las acciones de las APIs.
  • Integración con InterSystems IRIS: integración fluida con la plataforma de datos IRIS.

Caso de uso: El caso de uso en este informe es Gestión de Identidad y Acceso.

Autenticación y autorización siguiendo el estándar OAuth 2.0, asegurando un servidor FHIR mediante IAM.

En este documento aprenderéis cómo asegurar un servidor FHIR con OAuth 2.0 usando InterSystems API Manager. OAuth 2.0 es un estándar ampliamente utilizado para la autorización que permite a las aplicaciones acceder a recursos protegidos en un servidor FHIR. InterSystems API Manager es una herramienta que simplifica la creación, gestión y supervisión de APIs FHIR. Siguiendo los pasos de este documento, podréis configurar InterSystems API Manager para que actúe como servidor de autorización OAuth 2.0 y otorgue tokens de acceso a clientes autorizados. También aprenderéis a usar bibliotecas de cliente para conectar vuestra aplicación al servidor FHIR usando OAuth 2.0.

Nota: el servidor FHIR solo admite tokens JWT para la autenticación OAuth 2.0, no admite tokens opacos.

Instrucciones para ejecutar la demo localmente:

  1. Ejecutad el siguiente comando en el Símbolo del sistema para clonar el repositorio correspondiente:
    git clone https://github.com/isc-padhikar/IAM_FHIRServer
  2. Entrad en el directorio del repositorio recién clonado, cread un nuevo directorio y llamadlo 'key'. A continuación, copiád un archivo iris.key, que es la licencia de InterSystems IRIS for Health que soporta la gestión de APIs.
  3. Luego, volved al Símbolo del sistema y ejecutad los siguientes comandos uno por uno:
    docker-compose build
    docker-compose up
  4. Acceded a localhost:8002, donde está ejecutándose IAM.
  5. Usando IAM, puedo poner un servidor FHIR disponible como un servicio, como se ve en la imagen siguiente:
  6. Definid una ruta que será el proxy del servidor FHIR (yo he definido /fhir como el proxy), como se muestra en la imagen siguiente:
  7. Y definid los plugins que gestionarán las solicitudes entrantes al servidor FHIR, autenticando y autorizando el acceso al servidor FHIR. Debemos definir el emisor del token JWT (el servidor de autorización) y la clave pública que obtenemos al decodificar la clave privada (consultad la sección «Servidor de autorización» que sigue para esta parte), en el plugin JWT, dentro de la sección «Credenciales», como se muestra en las imágenes siguientes:  

Autenticación usando el servidor Auth0 y autorización mediante tokens JWT a través de IAM. Obteniendo un token JWT del servidor de autorización: Usando el token JWT para acceder al servidor FHIR a través de la ruta proxy definida en IAM:

Servidor de autorización:

Se utiliza un servidor de autorización externo, que es Auth0. Las instrucciones para configurar un servidor de autorización se encuentran en el README de la demo #1 (FHIROktaIntegration), mencionada en la próxima sección «Demos usadas como referencia».

Endpoint para obtener el JSON Web Key Set (JWKS): https://dev-bi2i05hvuzmk52dm.au.auth0.com/.well-known/jwks.json

Nos proporciona un par de claves para el servidor de autorización que hemos configurado y que se pueden usar para obtener la clave privada mediante un algoritmo de decodificación.

Usaremos la clave privada en IAM para verificar las firmas de los tokens JWT.

La mejor práctica para obtener la clave pública de un JWKS es usar un lenguaje de programación. Yo utilicé el siguiente código en Python:

import base64
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
import requests
# Replace 'YOUR_DOMAIN' with your actual Auth0 domain
jwks_url = 'https://dev-bi2i05hvuzmk52dm.au.auth0.com/.well-known/jwks.json'
response = requests.get(jwks_url)
jwks = response.json()
# Choose a specific key from the JWKS (e.g., the first key)
selected_key = jwks['keys'][0]
# Decode 'AQAB' (exponent 'e') from Base64 URL-safe to integer
decoded_exponent = int.from_bytes(base64.urlsafe_b64decode(selected_key['e'] + '==='), byteorder='big')
decoded_modulus = int.from_bytes(base64.urlsafe_b64decode(selected_key['n'] + '==='), byteorder='big')
# Construct the RSA public key
public_key = rsa.RSAPublicNumbers(
    decoded_exponent,
    decoded_modulus
).public_key(default_backend())
# Convert the public key to PEM format
public_key_pem = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)
print(public_key_pem.decode('utf-8'))

 

Demos usadas como referencia:

  1. FHIROktaIntegration: https://openexchange.intersystems.com/package/FHIROktaIntegration Esta demo muestra cómo configurar OAuth 2.0 directamente en InterSystems IRIS for Health y usar esa configuración para un servidor FHIR. Seguid las instrucciones que incluye para configurar los detalles del servidor de autorización. Sin embargo, las configuraciones se ven así en el portal de administración una vez completadas:
  2. Incluye una aplicación Angular que se autentica con el servidor de autorización, con una interfaz que muestra los recursos FHIR tras la autorización. Esto demuestra cómo se puede configurar OAuth 2.0 dentro de InterSystems IRIS for Health para asegurar un servidor FHIR.
  3. IAM Zero-to-Hero: https://openexchange.intersystems.com/package/iam-zero-to-hero

La demo está compuesta por IAM y formación relacionada con IAM. La modificaré para incluir un servidor FHIR y usar la instancia de IAM en esta demo para autenticar con el servidor de autorización Auth0 y autorizar el acceso mediante el plugin JWT.

A diferencia de la demo anterior, esta demuestra el uso de IAM para exponer un endpoint de un servidor FHIR y asegurarlo según el estándar OAuth 2.0 usando la biblioteca de plugins que ofrece IAM.

Cambios realizados en esta demo:

  1. He añadido un servidor FHIR en la instancia de IRIS for Health de esta demo. Sustituid el código del archivo iris.script por el siguiente código:
;do $System.OBJ.LoadDir("/opt/irisapp/src","ck",,1)
zn "%SYS"
Do ##class(Security.Users).UnExpireUserPasswords("*")
set $namespace="%SYS", name="DefaultSSL" do:'##class(Security.SSLConfigs).Exists(name) ##class(Security.SSLConfigs).Create(name) set url="https://pm.community.intersystems.com/packages/zpm/latest/installer" Do ##class(%Net.URLParser).Parse(url,.comp) set ht = ##class(%Net.HttpRequest).%New(), ht.Server = comp("host"), ht.Port = 443, ht.Https=1, ht.SSLConfiguration=name, st=ht.Get(comp("path")) quit:'st $System.Status.GetErrorText(st) set xml=##class(%File).TempFilename("xml"), tFile = ##class(%Stream.FileBinary).%New(), tFile.Filename = xml do tFile.CopyFromAndSave(ht.HttpResponse.Data) do ht.%Close(), $system.OBJ.Load(xml,"ck") do ##class(%File).Delete(xml)

//init FHIR Server
zn "HSLIB"
set namespace="FHIRSERVER"
Set appKey = "/csp/healthshare/fhirserver/fhir/r4"
Set strategyClass = "HS.FHIRServer.Storage.Json.InteractionsStrategy"
set metadataPackages = $lb("hl7.fhir.r4.core@4.0.1")
set importdir="/opt/irisapp/src"
//Install a Foundation namespace and change to it
Do ##class(HS.Util.Installer.Foundation).Install(namespace)
zn namespace

// Install elements that are required for a FHIR-enabled namespace
Do ##class(HS.FHIRServer.Installer).InstallNamespace()

// Install an instance of a FHIR Service into the current namespace
Do ##class(HS.FHIRServer.Installer).InstallInstance(appKey, strategyClass, metadataPackages)

// Configure FHIR Service instance to accept unauthenticated requests
set strategy = ##class(HS.FHIRServer.API.InteractionsStrategy).GetStrategyForEndpoint(appKey)
set config = strategy.GetServiceConfigData()
set config.DebugMode = 4
do strategy.SaveServiceConfigData(config)

zw ##class(HS.FHIRServer.Tools.DataLoader).SubmitResourceFiles("/opt/irisapp/fhirdata/", "FHIRSERVER", appKey)

zn "USER"
zpm "load /opt/irisbuild/ -v":1:1
zpm 
load /opt/irisapp/ -v
q
do ##class(Sample.Person).AddTestData()
halt

2. En el archivo docker-compose.yml, actualizad la imagen de IAM a la última versión (containers.intersystems.com/intersystems/iam:3.2.1.0-4), porque solo las versiones de IAM (Kong) a partir de la 3.1 soportan JSON draft-6, que es el formato que proporciona la especificación FHIR.

Discussão (0)1
Entre ou crie uma conta para continuar
Artigo
· jan 21 9min de leitura

Personnalisation des routeurs de messages

Introduction

Dans cet article, nous explorerons différentes approches permettant d'étendre et de personnaliser le comportement des routeurs de messages d'interopérabilité intégrés à InterSystems IRIS (et IRIS Health).

Les routeurs de messages remplissent l'une des fonctions essentielles de l'intégration d'applications d'entreprise (EAI) et font partie des processus métier les plus fréquemment utilisés dans les productions d'interopérabilité.

Après un bref aperçu des classes de routeurs de messages intégrés dans InterSystems IRIS et IRIS for Health, cet article expliquera comment améliorer leurs capacités afin d'obtenir des résultats spécifiques, sans avoir à développer un processus métier à partir de zéro.

Une mise en garde s'impose : la plupart de ces techniques impliquent de remplacer les méthodes de l'implémentation actuelle des classes de routeurs de messages dans IRIS Data Platform et IRIS for Health 2025.x. Elles peuvent ne pas s'appliquer à d'autres versions antérieures ou futures. 

Le référentiel GitHub qui accompagne cet article contient une collection d'exemples simples, minimalistes et volontairement abstraits illustrant les techniques abordées.

Nous vous invitons à nous faire part de vos avis, commentaires et retours constructifs!

Classes de routeurs

IRIS est fourni avec plusieurs classes de routeurs de messages de processus métier intégrées. La classe de base pour toutes ces classes est EnsLib.MsgRouter.RoutingEngine

Dans la plate-forme de données IRIS, plusieurs sous-classes spécialisées s’étendent à cette base:

  • EnsLib.MsgRouter.RoutingEngineST: Améliore le routage en remplissant une table de recherche (consultez Ens.VDoc.SearchTable) pour chaque message entrant.
  • EnsLib.MsgRouter.VDocRoutingEngine: gère le routage des messages, en implémentant Ens.VDoc.Interface.
  • EnsLib.EDI.MsgRouter.SegmentedRoutingEngine: Routes segmented EDI messages.
  • EnsLib.EDI.X12.MsgRouter.RoutingEngine: achemine les messages X12.

IRIS for Health dispose d'une sous-classe supplémentaire:

  • EnsLib.HL7.MsgRouter.RoutingEngine qui est une extension de EnsLib.EDI.MsgRouter.SegmentedRoutingEngine pour prendre en charge le routage des messages HL7 v2.x, qui sont des instances de EnsLib.HL7.Message.

Toutes les classes de routeurs partagent le cycle de vie des processus métier hérité de Ens.BusinessProcess et ont un flux d'exécution commun implémenté par la méthode EnsLib.MsgRouter.RoutingEngine OnRequest(), qui exécute la séquence suivante: 

  • Si la propriété Validation est définie, validez le message en appelant OnValidate().
  • Si la validation échoue, traitez le message mal formé.
  • Évaluez les règles et effectuez les actions résultantes en appelant doOneAction().

L'implémentation par défaut de OnValidate() ne fait rien. Des sous-classes spécialisées implémentent ce message pour valider les messages entrants. Par exemple, EnsLib.HL7.MsgRouter.RoutingEngine OnValidate() utilise EnsLib.HL7.Util.Validator pour vérifier le message HL7 entrant.

Si la validation échoue, les messages mal formés sont envoyés de manière asynchrone à la configuration cible BadMessageHandler, et OnError() est appelé, en passant le statut d'erreur et “0_!_Validation” comme clé d'achèvement.

L'évaluation des règles est implémentée de manière récursive par doOneAction().

Vous pouvez personnaliser le comportement de routage en remplaçant les méthodes clés suivantes dans votre classe de routage de messages personnalisée (qui étend une classe de routage intégrée):

  • OnRequest() et autres méthodes de rappel du cycle de vie des processus métier: pour ajouter un prétraitement ou un post-traitement, ou définir des propriétés de routeur pouvant être utilisées lors de l'évaluation des règles.
  • OnValidate(): pour personnaliser la validation des messages entrants.
  • doOneAction(): pour modifier le comportement des actions.
  • OnError(): pour implémenter une gestion personnalisée des erreurs.
  • OnResponse(): pour implémenter une gestion personnalisée des réponses.
  • OnTimeout(): pour implémenter une gestion personnalisée des délais d'expiration.

Implémentation du prétraitement ou du post-traitement

Comme le routeur est un processus métier, vous pouvez remplacer ses méthodes de rappel pour effectuer un prétraitement ou un post-traitement, ou une gestion personnalisée des erreurs:

  • OnRequest()
  • OnResponse()
  • OnComplete()
  • OnTimeout()

Ajout de propriétés au routeur en remplaçant OnRequest()

Lors de l'évaluation des règles, les propriétés du routeur sont disponibles à la fois dans la logique des règles métier (par exemple, dans les expressions booléennes des conditions “when”) et lors de l'exécution de la transformation des données. Pour cela, le routeur transmet une référence à lui-même via l'argument auxiliaire aux. aux.

Les classes de routeurs intégrées n'exposent qu'un contexte limité : le message entrant et les propriétés liées à l'ensemble de règles, telles que RuleActionData ou les variables @ temporaires définies dans la règle (consultez Utilisation des règles).

Pour intégrer des données de contexte supplémentaires lors du routage des messages, vous pouvez créer une sous-classe d'un routeur intégré, ajouter des propriétés personnalisées et remplacer sa méthode 

 afin de définir ces propriétés avant l'exécution de la logique réelle du routeur. Cela vous permet d'enrichir la logique de routage en injectant des propriétés personnalisées ou des métadonnées dans le processus d'évaluation des règles et de transformation des données. 

Vous pouvez, par exemple, récupérer des données supplémentaires en interrogeant la base de données, ou envoyer une requête synchrone à un autre processus métier ou à une autre opération métier, en utilisant la réponse pour enrichir le contexte de routage.

Remplaçons OnRequest() pour montrer comment ajouter une propriété de contexte disponible pendant l'évaluation des règles:

Class dc.routing.examples.VariableTargetRouter Extends EnsLib.MsgRouter.RoutingEngine
{

Method doOneAction(pRequest As %Persistent, pOneAction As %String, Output pQuitProcessing As %Boolean, pLevel As %Integer = 1, Output pSyncResponse As %Persistent) As %Status
{
  #Dim sc as %Status
  #Dim ex as %Exception.AbstractException
  s sc = $$$OK
  try {
    if $piece(pOneAction,":",1)="send" {
      s $piece(pOneAction,":",2) = ##class(Ens.Rule.ExpressionParser).Evaluate($piece(pOneAction,":",2),$this,.errorMsg)
    }
    $$$TOE(sc,##super(pRequest,pOneAction,.pQuitProcessing,pLevel,.pSyncResponse))
  } catch (ex) {
    s sc = ex.AsStatus()
  }
  return sc
}

Storage Default
{
<Type>%Storage.Persistent</Type>
}

}

J'ai appliqué cette technique à plusieurs reprises dans des scénarios concrets. Un exemple concerne la récupération d'informations détaillées sur des produits médicaux à partir d'un système de gestion de pharmacie afin d'orienter les décisions de routage et de modifier les demandes sortantes. Le routeur remplace la méthode OnRequest() lançant un appel vers une opération qui s'interface avec l'API du système de pharmacie. L'opération renvoie des données enrichies sur les produits, qui sont ensuite soumises aux règles de routage et aux transformations de données en tant que propriété objet du routeur.

Exécution d'une validation de message personnalisée

Lors de l'exécution de la méthode OnRequest() pendant le traitement des messages entrants, le routeur appelle OnValidate(). Bien que l'implémentation par défaut n'effectue aucune action dans EnsLib.MsgRouter.RoutingEngine, les classes comme EnsLib.HL7.MsgRouter.RoutingEngine remplaçent OnValidate() pour permettre la vérification des messages HL7 v2.x.

Personnalisons la validation des messages HL7 en remplaçant OnValidate()et en utilisant différentes spécifications de vérification, en fonction de la source du message:

Class dc.routing.examples.CustomValidationRouter Extends EnsLib.HL7.MsgRouter.RoutingEngine
{

Parameter SETTINGS = "CustomValidation";
Property CustomValidation As %String(MAXLEN = 240);
Method OnValidate(pDoc As EnsLib.HL7.Message, pValSpec As %String, Output pStatus As %Status = {$$$OK}) As %Boolean
{
  #Dim ex as %Exception.AbstractException
  #Dim result as %Boolean
  s pStatus = $$$OK
  s result = 0
  try {    
   s result = ##super(pDoc,..GetCustomValidationSpec(pDoc,pValSpec),.pStatus)
  } catch (ex) {
    s pStatus = ex.AsStatus()
  }
  return result
}

Method GetCustomValidationSpec(request As EnsLib.HL7.Message, defaultSpec As %String) As %String
{
  s result = defaultSpec
  s specList = $listfromstring(..CustomValidation)
  s ptr = 0
  while $listnext(specList,ptr,spec) {
    if $piece(spec,"=",1)=..%PrimaryRequestHeader.SourceConfigName {
      s result = $piece(spec,"=",2)
      quit
    }
  }
  return result
}

}

En pratique, lorsque vous travaillez avec le routage HL7, le remplacement de la méthode OnValidate() existante et non vide vous permet, par exemple, d'accepter comme valides les messages comportant des segments personnalisés (Zxx) non finaux, sans avoir à recourir à un schéma HL7 personnalisé.

Modification du comportement de l'action

Pendant l'évaluation de la règle, le routeur appelle la méthode doOneAction() de manière récursive pour exécuter des actions telles que send, delete, delegate et rule, où l'action "rule" elle-même peut déclencher un autre appel récursif de doOneAction().

La signature de la méthode est la suivante:

Method doOneAction(pRequest As %Persistent, pOneAction As %String, Output pQuitProcessing As %Boolean, pLevel As %Integer = 1, Output pSyncResponse As %Persistent) As %Status

Les arguments sont transmis lors de l'exécution de OnRequest() et ont les valeurs suivantes:

  • pRequest est un message entrant.
  • pOneAction est une liste de jetons délimités par “:” décrivant l'action à entreprendre: 
    • $piece(pOneAction,”:”,1) est le type d'action (envoyer, supprimer, règler ou déléguer).
    • $piece(pOneAction,”:”,2) est l'argument de chaîne de l'action ‘send’ (envoyer).
    • $piece(pOneAction,”:”,3) est l'argument de transformation de l'action ‘send’ (envoyer).
    • $piece(pOneAction,”:”,4) est le texte expliquant la raison de l'action.

Pour personnaliser le comportement des actions, nous pouvons remplacer cette méthode. Par exemple, nous pouvons faire en sorte que l'action “send” évalue dynamiquement une expression et exploite toutes les propriétés disponibles pendant l'évaluation de la règle de routage:

Toute modification dans la façon dont le routeur interprète les actions doit, bien sûr, être prise en compte dans la règle de routage.

Dans cet exemple, les littéraux de chaîne doivent désormais être placés entre guillemets doubles, et comme l'argument entier est considéré comme une chaîne par le générateur de code de règle, les guillemets doivent être doublés pour éviter tout conflit.

Dans la règle de routage, l'argument send est une expression qui est évaluée comme la cible de l'action send.

 

Class dc.routing.examples.VariableTargetRouter Extends EnsLib.MsgRouter.RoutingEngine
{

Method doOneAction(pRequest As %Persistent, pOneAction As %String, Output pQuitProcessing As %Boolean, pLevel As %Integer = 1, Output pSyncResponse As %Persistent) As %Status
{
  #Dim sc as %Status
  #Dim ex as %Exception.AbstractException
  s sc = $$$OK
  try {
    if $piece(pOneAction,":",1)="send" {
      s $piece(pOneAction,":",2) = ##class(Ens.Rule.ExpressionParser).Evaluate($piece(pOneAction,":",2),$this,.errorMsg)
    }
    $$$TOE(sc,##super(pRequest,pOneAction,.pQuitProcessing,pLevel,.pSyncResponse))
  } catch (ex) {
    s sc = ex.AsStatus()
  }
  return sc
}

Storage Default
{
<Type>%Storage.Persistent</Type>
}

}

@Thomas Haig ,cette technique répond à votre question sur la possibilité d'avoir une règle de routage avec une cible variable.

Cette technique peut également être utilisée lorsque la décision de router le message résultant ne peut être prise qu'après la transformation des données. 

En pratique, j'ai appliqué cette technique aux messages entrants provenant d'un système de commande de médicaments qui comprenait un mélange de demandes de réapprovisionnement en produits médicaux.

Certaines demandes étaient destinées au système central de pharmacie, tandis que d'autres ciblaient le système autonome de gestion des armoires.

Plutôt que de déployer deux routeurs distincts avec des ensembles de règles distincts, j'ai utilisé une logique conditionnelle pour acheminer le message transformé sur la base d'une expression évaluée à partir du message résultant de la transformation des données. 

Discussão (0)2
Entre ou crie uma conta para continuar
Discussão (0)1
Entre ou crie uma conta para continuar
Pergunta
· jan 20

SQL - TOP/LIMIT/FETCH cause query to hang

Why do these clauses affect SQL performance?

select ID from some_table where row_status in ('I','U') order by ID limit 5 - makes the query infinite
select top 10 ID from some_table where row_status in ('I','U') order by ID - the same
select ID from some_table where row_status in ('I','U') order by ID - is fast

Actually there are no rows in the table having row_status 'I' or 'U'.

I asked Gemini and it recommended me rewrite the query as

13 novos comentários
Discussão (13)4
Entre ou crie uma conta para continuar
Artigo
· jan 20 5min de leitura

Observability for InterSystems IRIS with OpenTelemetry: Prometheus, Loki, Jaeger, and Tempo/Grafana

Modern platforms usually treat observability as three core signals:

  • Metrics
  • Logs
  • Traces

OpenTelemetry (OTel) is the standard way to produce and ship all three signals. This article explains a practical setup for InterSystems IRIS running in Docker Compose, with a full local observability stack:

  • Prometheus (metrics)
  • Loki (logs)
  • Tempo (traces, for Grafana Traces Drilldown)
  • Jaeger (optional, alternate trace viewer)
  • Grafana (unified UI)
  • OpenTelemetry Collector

What you’ll build

  • IRIS emits OTLP/HTTP telemetry to the OpenTelemetry Collector
  • Collector routes:
    • traces -> Tempo (for Grafana)/Jaeger (alternate trace UI)
    • metrics -> Prometheus
    • logs -> Loki
  • Grafana reads from Prometheus + Tempo + Jaeger + Loki and shows everything in one place

You can download the full working Docker Compose example here: https://github.com/isc-jyin/iris-otel-example

Prerequisites

  • Docker

Step-by-Step Guide

1. Create a folder structure

otel-minimal-config/
  iris/
    cpf-merge.cpf
  configs/
    otel-collector-config.yaml
    prometheus.yaml
    tempo.yaml
    grafana/
      provisioning/
        datasources/
          datasources.yaml
  docker-compose.yaml
  README.md
  README.pdf

2. Configure the OpenTelemetry Collector

The Collector is the router that receives OTLP from IRIS, then exports to each backend in the correct format. 

Create otel-collector-config.yaml:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
exporters:
  # export Metrics to Prometheus for viewing
  prometheus:
    endpoint: "0.0.0.0:8889"
  # export traces to Jaeger for viewing
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true
  otlphttp/loki:
    endpoint: "http://loki:3100/otlp"
    tls:
      insecure: true
  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true
  debug:
    verbosity: detailed
processors:
  batch:
service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/loki, debug]
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/jaeger, otlp/tempo, debug]

3. Configure Prometheus scraping

Prometheus scrapes metrics from an HTTP endpoint exposed by the Collector. It also stores metrics generated by Tempo.

Create prometheus.yaml:

scrape_configs:
  - job_name: 'otel-collector'
    scrape_interval: 10s
    static_configs:
      - targets: ['otel-collector:8889']
      - targets: ['otel-collector:8888']

4. Configure Tempo

Tempo is the trace backend that Grafana's Traces Drilldown needs. We also enable Tempo's metrics generator so Grafana can build metrics (rate/errors/duration) from traces. 

Create tempo.yaml:

server:
  http_listen_port: 3200
distributor:
  receivers:
    otlp:
      protocols:
        grpc:
          endpoint: "tempo:4317"
storage:
  trace:
    backend: local
    local:
      path: /tmp/tempo/traces
    wal:
      path: /tmp/tempo/wal
metrics_generator:
  registry:
    external_labels:
      source: tempo
      cluster: docker-compose
  storage:
    path: /var/tempo/generator/wal
    remote_write:
      - url: http://prometheus:9090/api/v1/write
        send_exemplars: true
  traces_storage:
    path: /var/tempo/generator/traces
overrides:
  defaults:
    metrics_generator:
      processors: [service-graphs, span-metrics, local-blocks] # enables metrics generator
      generate_native_histograms: both

5. Configure Grafana datasources

Grafana reads from Prometheus + Tempo + Jaeger + Loki and shows everything in one place

Create datasources.yaml:

apiVersion: 1
datasources:
- name: Loki
  type: loki
  access: proxy
  url: http://loki:3100
- name: Prometheus
  type: prometheus
  access: proxy
  url: http://prometheus:9090
  isDefault: true
- name: Jaeger
  type: jaeger
  access: proxy
  url: http://jaeger:16686
- name: Tempo
  type: tempo
  access: proxy
  url: http://tempo:3200

6. Enable OTel metrics and logs in IRIS

Create iris.cpf

[Monitor]
OTELMetrics=1
OTELLogs=1
OTELLogLevel=INFO

7. Create docker-compose.yaml

Compose starts everything consistently under the same network, so DNS names like tempo, loki, otel-collector work.

services:
  iris:
    image: containers.intersystems.com/intersystems/iris-community:2025.3
    environment:
    - ISC_CPF_MERGE_FILE=/iris/cpf-merge.cpf
    - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
    - OTEL_EXPORTER_OTLP_TIMEOUT=500
    - OTEL_SERVICE_NAME=iris
    depends_on:
    - otel-collector
    volumes:
      - ./iris:/iris
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.143.0
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./configs/otel-collector-config.yaml:/etc/otel-collector-config.yaml:ro
  tempo:
    image: grafana/tempo:latest
    command: ["-config.file=/etc/tempo.yaml"]
    volumes:
      - ./configs/tempo.yaml:/etc/tempo.yaml:ro
    ports:
      - 3200
      - 4317
  loki:
    image: grafana/loki:latest
    ports:
      - 3100
  prometheus:
    image: prom/prometheus:v3.9.1
    volumes:
     - ./configs/prometheus.yaml:/etc/prometheus/prometheus.yml:ro
    ports:
      - "9090:9090"
  # Jaeger
  jaeger:
    image: jaegertracing/all-in-one:1.76.0
    ports:
      - "16686:16686"
      - 4317
    environment:
      - LOG_LEVEL=debug
  grafana:
    image: grafana/grafana:12.3.1
    environment:
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_BASIC_ENABLED=false
      - GF_FEATURE_TOGGLES_ENABLE=traceqlEditor
    volumes:
      - ./configs/grafana/provisioning:/etc/grafana/provisioning:ro
    ports:
      - 3000:3000/tcp
    depends_on:
      - prometheus
      - loki
      - jaeger
  

8. Start the stack

Start the environment

docker compose up -d

Open a terminal session in the IRIS container:

docker compose exec iris iris session IRIS

Then run the built-in trace demo:

Do ##class(%Trace.Tracer).Test()

9. Viewing Results

Grafana - Traces + Metrics + Logs

Open http://localhost:3000/drilldown

Metrics:

Logs:

Traces:

Jaeger - Traces

Open http://localhost:16686

Prometheus - Metrics

Open http://localhost:9090

Discussão (0)1
Entre ou crie uma conta para continuar