Exemple d'auto-instrumentation

Cette page montre comment utiliser l’auto-instrumentation Python dans OpenTelemetry. L’exemple est basé sur un exemple OpenTracing. Vous pouvez télécharger ou voir les fichiers sources utilisés dans cette page depuis le dépôt opentelemetry-python.

Cet exemple utilise trois scripts différents. La principale différence entre eux est la manière dont ils sont instrumentés :

  1. server_manual.py est instrumenté manuellement.
  2. server_automatic.py est instrumenté automatiquement.
  3. server_programmatic.py est instrumenté programmatiquement.

L’instrumentation programmatique est un type d’instrumentation qui nécessite un minimum de code d’instrumentation à ajouter à l’application. Seules quelques bibliothèques d’instrumentation offrent des capacités supplémentaires qui vous donnent un plus grand contrôle sur le processus d’instrumentation lorsqu’elles sont utilisées programmatiquement.

Exécutez le premier script sans l’agent d’instrumentation automatique et le second avec l’agent. Ils devraient tous deux produire les mêmes résultats, démontrant que l’agent d’instrumentation automatique fait exactement la même chose que l’instrumentation manuelle.

L’instrumentation automatique utilise le monkey-patching pour réécrire dynamiquement les méthodes et les classes à l’exécution via des bibliothèques d’instrumentation. Cela réduit la quantité de travail nécessaire pour intégrer OpenTelemetry dans le code de votre application. Ci-dessous, vous verrez la différence entre une route Flask instrumentée manuellement, automatiquement et programmatiquement.

Serveur instrumenté manuellement

server_manual.py

@app.route("/server_request")
def server_request():
    with tracer.start_as_current_span(
        "server_request",
        context=extract(request.headers),
        kind=trace.SpanKind.SERVER,
        attributes=collect_request_attributes(request.environ),
    ):
        print(request.args.get("param"))
        return "served"

Serveur instrumenté automatiquement

server_automatic.py

@app.route("/server_request")
def server_request():
    print(request.args.get("param"))
    return "served"

Serveur instrumenté programmatiquement

server_programmatic.py

instrumentor = FlaskInstrumentor()

app = Flask(__name__)

instrumentor.instrument_app(app)
# instrumentor.instrument_app(app, excluded_urls="/server_request") {#instrumentorinstrument_appapp-excluded_urlsserver_request}
@app.route("/server_request")
def server_request():
    print(request.args.get("param"))
    return "served"

Préparation

Exécutez l’exemple suivant dans un environnement virtuel séparé. Exécutez les commandes suivantes pour préparer l’auto-instrumentation :

mkdir auto_instrumentation
cd auto_instrumentation
python -m venv venv
source ./venv/bin/activate

Installation

Exécutez les commandes suivantes pour installer les paquets appropriés. Le paquet opentelemetry-distro dépend de quelques autres, comme opentelemetry-sdk pour l’instrumentation personnalisée de votre propre code et opentelemetry-instrumentation qui fournit plusieurs commandes qui aident à instrumenter automatiquement un programme.

pip install opentelemetry-distro
pip install flask requests

Exécutez la commande opentelemetry-bootstrap :

opentelemetry-bootstrap -a install

Les exemples qui suivent envoient les résultats de l’instrumentation à la console. Apprenez-en plus sur l’installation et la configuration de la Distribution OpenTelemetry pour envoyer la télémétrie à d’autres destinations, comme un Collecteur OpenTelemetry.

Note: Pour utiliser l’instrumentation automatique via opentelemetry-instrument, vous devez la configurer via des variables d’environnement ou la ligne de commande. L’agent crée un pipeline de télémétrie qui ne peut être modifié que par ces moyens. Si vous avez besoin de plus de personnalisation pour vos pipelines de télémétrie, alors vous devez renoncer à l’agent et importer le SDK OpenTelemetry et les bibliothèques d’instrumentation dans votre code et les configurer dans votre code. Vous pouvez également étendre l’instrumentation automatique en important l’API OpenTelemetry. Pour plus de détails, voir la référence de l’API.

Exécution

Cette section vous guide à travers le processus manuel d’instrumentation d’un serveur ainsi que le processus d’exécution d’un serveur instrumenté automatiquement.

Exécuter le serveur instrumenté manuellement

Exécutez le serveur dans deux consoles séparées, une pour chacun des scripts à exécuter qui composent cet exemple :

source ./venv/bin/activate
python server_manual.py
source ./venv/bin/activate
python client.py testing

La console exécutant server_manual.py affichera les spans générés par l’instrumentation au format JSON. Les spans devraient ressembler à l’exemple suivant :

{
  "name": "server_request",
  "context": {
    "trace_id": "0xfa002aad260b5f7110db674a9ddfcd23",
    "span_id": "0x8b8bbaf3ca9c5131",
    "trace_state": "{}"
  },
  "kind": "SpanKind.SERVER",
  "parent_id": null,
  "start_time": "2020-04-30T17:28:57.886397Z",
  "end_time": "2020-04-30T17:28:57.886490Z",
  "status": {
    "status_code": "OK"
  },
  "attributes": {
    "http.method": "GET",
    "http.server_name": "127.0.0.1",
    "http.scheme": "http",
    "host.port": 8082,
    "http.host": "localhost:8082",
    "http.target": "/server_request?param=testing",
    "net.peer.ip": "127.0.0.1",
    "net.peer.port": 52872,
    "http.flavor": "1.1"
  },
  "events": [],
  "links": [],
  "resource": {
    "telemetry.sdk.language": "python",
    "telemetry.sdk.name": "opentelemetry",
    "telemetry.sdk.version": "0.16b1"
  }
}

Exécuter le serveur instrumenté automatiquement

Arrêtez l’exécution de server_manual.py en appuyant sur Control+C et exécutez la commande suivante à la place :

opentelemetry-instrument --traces_exporter console --metrics_exporter none --logs_exporter none python server_automatic.py

Dans la console où vous avez précédemment exécuté client.py, exécutez à nouveau la commande suivante :

python client.py testing

La console exécutant server_automatic.py affichera les spans générés par l’instrumentation au format JSON. Les spans devraient ressembler à l’exemple suivant :

{
  "name": "server_request",
  "context": {
    "trace_id": "0x9f528e0b76189f539d9c21b1a7a2fc24",
    "span_id": "0xd79760685cd4c269",
    "trace_state": "{}"
  },
  "kind": "SpanKind.SERVER",
  "parent_id": "0xb4fb7eee22ef78e4",
  "start_time": "2020-04-30T17:10:02.400604Z",
  "end_time": "2020-04-30T17:10:02.401858Z",
  "status": {
    "status_code": "OK"
  },
  "attributes": {
    "http.method": "GET",
    "http.server_name": "127.0.0.1",
    "http.scheme": "http",
    "host.port": 8082,
    "http.host": "localhost:8082",
    "http.target": "/server_request?param=testing",
    "net.peer.ip": "127.0.0.1",
    "net.peer.port": 48240,
    "http.flavor": "1.1",
    "http.route": "/server_request",
    "http.status_text": "OK",
    "http.status_code": 200
  },
  "events": [],
  "links": [],
  "resource": {
    "telemetry.sdk.language": "python",
    "telemetry.sdk.name": "opentelemetry",
    "telemetry.sdk.version": "0.16b1",
    "service.name": ""
  }
}

Vous pouvez voir que les deux sorties sont les mêmes car l’instrumentation automatique fait exactement ce que fait l’instrumentation manuelle.

Exécuter le serveur instrumenté programmatiquement

Il est également possible d’utiliser les bibliothèques d’instrumentation (telles que opentelemetry-instrumentation-flask) par elles-mêmes, ce qui peut avoir l’avantage de personnaliser les options. Cependant, en choisissant de le faire, vous renoncez à utiliser l’auto-instrumentation en démarrant votre application avec opentelemetry-instrument car elles sont mutuellement exclusives.

Exécutez le serveur comme vous le feriez pour l’instrumentation manuelle, dans deux consoles séparées, une pour exécuter chacun des scripts qui composent cet exemple :

source ./venv/bin/activate
python server_programmatic.py
source ./venv/bin/activate
python client.py testing

Les résultats devraient être les mêmes que lors de l’exécution avec l’instrumentation manuelle.

Utilisation des fonctionnalités d’instrumentation programmatique

Certaines bibliothèques d’instrumentation incluent des fonctionnalités qui permettent un contrôle plus précis lors de l’instrumentation programmatique, la bibliothèque d’instrumentation pour Flask en est une.

Cet exemple a une ligne commentée, changez-la comme ceci :

# instrumentor.instrument_app(app) {#instrumentorinstrument_appapp}
instrumentor.instrument_app(app, excluded_urls="/server_request")

Après avoir réexécuté l’exemple, aucune instrumentation ne devrait apparaître côté serveur. Ceci est dû à l’option excluded_urls passée à instrument_app qui empêche effectivement la fonction server_request d’être instrumentée car son URL correspond à l’expression régulière passée à excluded_urls.

Instrumentation pendant le débogage

Le mode de débogage peut être activé dans l’application Flask comme ceci :

if __name__ == "__main__":
    app.run(port=8082, debug=True)

Le mode de débogage peut interrompre l’instrumentation car il active un rechargeur. Pour exécuter l’instrumentation lorsque le mode de débogage est activé, définissez l’option use_reloader sur False :

if __name__ == "__main__":
    app.run(port=8082, debug=True, use_reloader=False)

Configuration

L’auto-instrumentation peut consommer la configuration à partir des variables d’environnement.

Capturer les en-têtes de requête et de réponse HTTP

Vous pouvez capturer les en-têtes HTTP prédéfinis en tant qu’attributs de span, conformément à la convention sémantique.

Pour définir les en-têtes HTTP que vous souhaitez capturer, fournissez une liste de noms d’en-têtes HTTP séparés par des virgules via les variables d’environnement OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST et OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE, par exemple :

export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept-Encoding,User-Agent,Referer"
export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Last-Modified,Content-Type"
opentelemetry-instrument --traces_exporter console --metrics_exporter none --logs_exporter none python app.py

Ces options de configuration sont prises en charge par les instrumentations HTTP suivantes :

  • Django
  • Falcon
  • FastAPI
  • Pyramid
  • Starlette
  • Tornado
  • WSGI

Si ces en-têtes sont disponibles, ils seront inclus dans votre span :

{
  "attributes": {
    "http.request.header.user-agent": [
      "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)"
    ],
    "http.request.header.accept_encoding": ["gzip, deflate, br"],
    "http.response.header.last_modified": ["2022-04-20 17:07:13.075765"],
    "http.response.header.content_type": ["text/html; charset=utf-8"]
  }
}