Envoyer des logs avec une bibliothèque de logs - Python 3.x - logging-ldp
Envoyez les logs de vos applications Python 3.x vers Logs Data Platform avec logging-ldp.
Objectif
Ce guide vous montre comment envoyer vos logs vers Logs Data Platform en utilisant Python 3.x.
logging-ldp est conçu comme un formatter et un handler de journalisation haute performance permettant d'envoyer des entrées de log vers Logs Data Platform.
Ce package inclut :
- un handler TCP/TLS pour envoyer des entrées de log via TCP avec prise en charge de TLS.
- un formatter pour convertir un enregistrement de log au format GELF(1.1).
- une fonctionnalité pour s'assurer que les champs respectent les conventions de nommage LDP.
Prérequis
Pour suivre ce guide, vous aurez besoin de :
En pratique
Installation
Via pip
Vous pouvez utiliser pip pour installer logging-ldp ; veillez à utiliser la dernière version :
$ pip3 install --upgrade pip
[...]
Successfully installed pip-<version>
$ pip3 install --upgrade logging-ldp
[...]
Successfully installed logging-ldp-<version> setuptools-18.3.1
Via les sources
logging-ldp est disponible sur le dépôt GitHub d'OVH et peut être installé manuellement :
$ git clone git@github.com:ovh/python-logging-ldp.git
Cloning into 'python-logging-ldp'...
remote: Counting objects: 58, done.
remote: Compressing objects: 100% (53/53), done.
remote: Total 58 (delta 26), reused 0 (delta 0)
Receiving objects: 100% (58/58), 9.62 KiB | 0 bytes/s, done.
Resolving deltas: 100% (26/26), done.
Checking connectivity... done.
$ cd python-logging-ldp
$ python3 setup.py install
[...]
Using /usr/lib/python3.x/site-packages
Finished processing dependencies for logging-ldp==<version>
L'exemple suivant montre comment envoyer un log vers un input TCP Graylog :
import logging
from logging_ldp.formatters import LDPGELFFormatter
from logging_ldp.handlers import LDPGELFTCPSocketHandler
def setup_logging():
handler = LDPGELFTCPSocketHandler(hostname="gra1.logs.ovh.com")
handler.setFormatter(LDPGELFFormatter(token="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"))
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
if __name__ == '__main__':
setup_logging()
logging.info("Test !")
Envoyer des métadonnées statiques supplémentaires
Pour ajouter automatiquement des métadonnées à tous vos logs, vous pouvez implémenter
un schéma alternatif :
import logging
from marshmallow import fields
from logging_ldp.formatters import LDPGELFFormatter
from logging_ldp.handlers import LDPGELFTCPSocketHandler
from logging_ldp.schemas import LDPSchema
def setup_logging():
# Load you config there
config = dict(name="myapp", version="0.0.1")
# Define a custom Schema
class MyApp(LDPSchema):
app_name = fields.Constant(config['name'])
app_version = fields.Constant(config['version'])
handler = LDPGELFTCPSocketHandler(hostname="gra1.logs.ovh.com")
handler.setFormatter(LDPGELFFormatter(token="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", schema=MyApp))
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
if __name__ == '__main__':
setup_logging()
logging.info("Test !")
L'entrée de log envoyée à Graylog ressemblera à ceci :
{
"_app_name": "myapp",
"_app_version": "0.0.1",
"_X-OVH-TOKEN": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"file": "test_thread.py",
"host": "cdumay-desk",
"level": 6,
"line": 36,
"short_message": "Test !",
"timestamp": 1556036745.6493497,
"version": "1.1"
}
Remarque : le résultat n'est présenté « pretty printed » que pour la documentation.
Envoyer des métadonnées intermittentes supplémentaires
Pour définir des métadonnées occasionnelles, vous pouvez définir un schéma avec des sous-éléments imbriqués (Nested) :
import logging
from marshmallow import Schema, fields
from logging_ldp.formatters import LDPGELFFormatter
from logging_ldp.handlers import LDPGELFTCPSocketHandler
from logging_ldp.schemas import LDPSchema
# Define a sub-schema
class User(Schema):
name = fields.String(required=True)
age = fields.Integer()
# Définir un schéma personnalisé à appliquer aux entrées de log
class AccountInfo(LDPSchema):
user = fields.Nested(User, required=True)
manager = fields.Nested(User)
def setup_logging():
handler = LDPGELFTCPSocketHandler(hostname="gra1.logs.ovh.com")
handler.setFormatter(LDPGELFFormatter(
token="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
schema=AccountInfo
))
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
if __name__ == '__main__':
setup_logging()
current_user = dict(name="John Doe")
boss = dict(name="Roger Smith", age=51)
logging.info("User has logged", extra=dict(user=current_user, manager=boss))
L'entrée de log envoyée sera :
{
"_manager_age_int": 51,
"_manager_name": "Roger Smith",
"_user_name": "John Doe",
"_X-OVH-TOKEN": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"file": "test_thread.py",
"host": "cdumay-desk",
"level": 6,
"line": 40,
"short_message": "User has logged",
"timestamp": 1556037587.4444475,
"version": "1.1"
}
Comme on peut le constater :
- Les objets sont aplatis en dictionnaires :
manager.name devient manager_name.
- Les champs sont typés selon la convention de nommage LDP :
manager.age devient manager_age_int.
- Les valeurs nulles ne sont pas envoyées :
user.age.
- Vous pouvez définir
required=True pour rendre une métadonnée obligatoire, ou default=xxx pour ajouter une donnée automatiquement.
Aller plus loin