Aller au contenu

Ajouter le traçage manuel

Span, log et trace

Voici un exemple simple d'utilisation de Logfire:

import time

import logfire

logfire.configure()

with logfire.span('This is a span'):
    time.sleep(1)
    logfire.info('This is an info log')
    time.sleep(2)

Si vous exécutez ceci, il devrait imprimer quelque chose de similaire:

Logfire project URL: https://logfire.pydantic.dev/my_username/my_project_name
21:02:55.078 This is a span
21:02:56.084   This is an info log

L'URL du projet ouvert devrait apparaître comme suit dans la vue Live:

Exemple Simple en Live view

Une boîte bleue avec 1+ indique que la portée contient 1 enfant direct. Cliquez sur la boîte pour étendre la portée afin d'afficher ses enfants:

Simple exemple en Live view avec span opened

Veuillez noter:

  1. Toute étendue ou journal créé dans un bloc with logfire.span(...): sera un enfant de cette étendue. cela vous permet d'organiser les journaux dans un arbre structuré. vous pouvez également voir cette relation parent-enfant dans les journaux de console basés sur l'indentation.

  2. Une étendue a une heure de début et une heure de fin, et donc une durée. cette étendue prend 3 secondes à compléter.

  3. Pour les logs, les heures de début et de fin sont les mêmes, donc elles n'ont pas de durée. cependant, vous pouvez toujours voir dans l'interface utilisateur que les logs ont été créés 1 seconde après le début de la durée et 2 secondes avant la fin de la durée.

Si vous cliquez sur le lien « Explorer » dans la barre de navigation supérieure, vous pouvez écrire SQL pour explorer davantage, par exemple:

Query in Exploration view: select extract('seconds' from end_timestamp - start_timestamp) comme duration, kind, message, trace_id, span_id, parent_span_id from records order by start_timestamp

Attention:

  1. Les span et les logs sont stockés ensemble dans la même table 记录.

  2. Le log 的parent_span_id C'est le span span_id。

  3. Les deux ont le même trace_id. vous pouvez cliquer dessus pour ouvrir un nouvel onglet dans la vue "Live" qui a été filtré vers ce_tracking_.

_trace_est un arbre de span/log partageant la même racine. chaque fois qu 'une nouvelle span/log est créée sans span actif, une nouvelle trace est créée. s'il s'agit d'une span, tout descendant de cette span fera partie de la même trace. Pour bien organiser les logs dans une trace, il est préférable de créer des span au niveau supérieur pour représenter des opérations avancées, telles que le traitement des requêtes du serveur Web.

Propriétés

Span et log peuvent être joints avec des données structurées telles que:

logfire.info ("Bonjour", nom ="monde")

Si vous cliquez sur le journal « Bonjour » dans la vue en direct, vous devriez voir ce qui suit dans le panneau de détails à droite:

Nom de l'attribut dans Live View

Ces données sont stockées au format JSON dans la colonne 属性 de la table 记录. Par exemple, vous pouvez utiliser attributes->>'name' = 'world' dans le filtre SQL en haut de la vue en direct pour afficher uniquement ce journal. cela sert de clause WHERE pour les requêtes SQL sur la table 记录.

Les deux span et logs peuvent avoir des propriétés qui contiennent des valeurs arbitraires qui seront intelligemment sérialisées en JSON selon les besoins. vous pouvez passer n'importe quel paramètre de mot-clé pour définir les propriétés, à condition qu 'ils ne commencent pas par un trait de soulignement (_). cet espace de noms est réservé aux autres paramètres de mot-clé qui ont une signification spécifique à logfire.

Parfois, il est utile d'attacher des propriétés à une plage après sa création, mais avant qu 'elle ne soit terminée. vous pouvez le faire en appelant la méthode span.set_attribute:

with logfire.span('Calculating...') as span:
    result = 1 + 2
    span.set_attribute('result', result)

Messages et noms de span

Si vous exécutez ce code:

import logfire

logfire.configure()

for name in ['Alice', 'Bob', 'Carol']:
    logfire.info('Hello {name}', name=name)

Query in Explore view: select span_name, attributes->"'name' as name, message from records order by start_timestamp

Ici vous pouvez voir:

  1. Le premier argument,“Hello {name}”, devient la valeur de la colonne span_name. Vous pouvez l'utiliser pour trouver tous les enregistrements du même code, même si les messages sont différents, par exemple en utilisant le filtre SQL span_name = 'Hello {name}'.

  2. Le nom span est également utilisé comme modèle de style str.format, qui est formaté avec des propriétés pour générer des colonnes 消息. le message est ce qui est affiché dans les journaux de la console et dans la vue en direct.

Vous pouvez également configurer span.message après le début de l'étendue mais avant qu 'elle ne soit terminée, par exemple:

with logfire.span('Calculating...') as span:
    result = 1 + 2
    span.message = f'Calculated: {result}'

Vous pouvez utiliser 消息 pour filtrer les enregistrements pertinents, tels que “Hello%”之类的消息 Mais pour span_name Il est plus efficace de filtrer une colonne car elle est indexée. span_name = 'Hello {name}' 和 attributes->>'name' = 'Alice' Plus que l'utilisation message = 'Hello Alice' Mieux encore.

Afin de pouvoir filtrer efficacement les enregistrements pertinents, le nom de la portée_cardinalité_Il devrait être inférieur, ce qui signifie qu 'ils ne devraient pas trop varier…Par exemple, ce serait mauvais:

name = get_username()
logfire.info('Hello ' + name, name=name)

Parce que maintenant la colonne span_name aura une valeur différente pour chaque nom d'utilisateur. mais c'est bien:

Parce que maintenant la colonne span_name n'a que deux valeurs ('Goodbye {name}' et 'Hello {name}') et il est plus facile et plus efficace de filtrer span_name = 'Hello {name}' que span_name = '{word} {name}' 和 attributes->>'word' = 'Hello'.

Lorsque vous souhaitez que le nom span soit différent du modèle de message, vous pouvez utiliser l'argument _span_name, par exemple:

word = 'Goodbye' if leaving else 'Hello'
logfire.info(word + ' {name}', name=name)

Cela définit span_name sur “Hello” et 消息 sur “Hello world”。 Notez que l'argument _span_name commence par un trait de soulignement pour le distinguer de la propriété.

logfire.info('Hello {name}', name='world', _span_name='Hello')

F-strings pour


A leur place:

logfire.info('Hello {name}', name=name)

Il est beaucoup plus pratique d'utiliser F-String pour éviter de répéter NAME trois fois:

logfire.info(f'Hello {name}')

Contrairement à la section précédente, ceci est disponible en Python 3.11+._Effets de_Très bien, car Logfire utilisera la magie spéciale qui sera span_name Set à “Hello {name}” et sera name Les propriétés sont fixées à name La valeur de la variable, donc c'est l'équivalent du fragment de code précédent. voici ce que vous devez savoir:

  • Cette fonctionnalité est activée par défaut dans Python 3.11+. vous pouvez la désactiver en utilisant [logfire.configure(inspect_arguments=False) ][logfire.configure(inspect_arguments)]. vous pouvez également l'activer dans Python 3.9 et 3.10, mais il est plus probable qu 'elle ne fonctionnera pas correctement.

  • Dans des circonstances normales, les paramètres de vérification devraient toujours être valides. il est important de noter que le code source doit être disponible, donc, par exemple, le déploiement du fichier .pyc entraînera son échec.

  • Si la vérification des paramètres échoue, vous recevrez un avertissement et le paramètre f-string sera utilisé comme modèle de formatage. cela signifie que vous obtiendrez un nom de portée à base élevée, par exemple “Hello Alice”, et qu 'il n'y aura pas d'attribut 名称, mais les informations ne seront pas complètement perdues.

  • Si la vérification des paramètres est activée, les paramètres seront vérifiés indépendamment du fait que la chaîne f soit utilisée ou non. Donc, si vous écrivez logfire.info('Hello {name}', name=name) et que la vérification des paramètres échoue, vous recevrez toujours un avertissement.

  • Les valeurs dans f-strings sont à nouveau calculées et formatées par Logfire. cela signifie que si get_username()(ou la conversion de chaînes de caractères de tout ce qu 'il renvoie) est coûteux ou a des effets secondaires, vous devriez éviter d'utiliser du code comme logfire.info(f'Hello {get_username()}').

  • Le premier argument doit être une chaîne f réelle.logfire.info(f'Hello {name}') fonctionnera, mais message = f'Hello {name}'; logfire.info(message) ne le fera pas, et logfire.info('Hello ' + name) non plus.

  • Les paramètres de vérification sont mis en cache de sorte que les frais généraux de performance pour vérifier à plusieurs reprises la même chaîne f soient minimes. Cependant, l'analyse d'un fichier source volumineux entraîne des frais généraux non négligeables la première fois qu 'un paramètre doit être vérifié. De toute façon, éviter ces frais généraux nécessite de désactiver complètement les paramètres de vérification, et pas seulement d'éviter la chaîne f.

Anomalie

logfire.span Le gestionnaire de contexte enregistrera automatiquement toute exception qui a causé sa sortie, telle que:

import logfire

logfire.configure()

with logfire.span('This is a span'):
    raise ValueError('This is an error')

Si vous cliquez sur une étendue dans la vue en direct, le panneau de droite aura un onglet « Anomalie en arrière »:

Traceback dans UI

Les exceptions capturées mais non relancées ne seront pas enregistrées, par exemple:

with logfire.span('This is a span'):
    try:
        raise ValueError('This is an acceptable error not worth recording')
    except ValueError:
        pass

Si vous souhaitez documenter une exception gérée, utilisez span.record_exception Méthodes:

with logfire.span('This is a span') as span:
    try:
        raise ValueError('Catch this error, but record it')
    except ValueError as e:
        span.record_exception(e)

Alternativement, si vous souhaitez simplement enregistrer des exceptions sans créer de portée pour la situation normale, vous pouvez utiliser logfire.exception:

try:
    raise ValueError('This is an error')
except ValueError:
    logfire.exception('Something went wrong')

logfire.exception(...) est équivalent à logfire.error(..., _exc_info=True)。 Vous pouvez également utiliser _exc_info avec d'autres méthodes de journalisation si vous souhaitez enregistrer un niveau de retour en arrière non erroné dans le journal._exc_info peut être défini sur l'objet exceptionnel si ce n'est pas celui sur lequel vous travaillez.

Portée fonctionnelle pratique avec @logfire.instrument

Souvent, vous voulez envelopper la fonction entière dans une portée. au lieu de cela:

def my_function(x, y):
    with logfire.span('my_function', x=x, y=y):
        ...

Vous pouvez utiliser le décorateur @logfire.instrument :

Par défaut, cela ajoute les arguments de la fonction en tant que propriété à span. Pour désactiver cette fonctionnalité (par exemple, si les arguments sont de gros objets qui ne valent pas la peine d'être collectés), utilisez instrument(extract_args=False)。

Le nom de l'étendue par défaut est similaire à Calling module_name.my_function. Vous pouvez passer le nom de l'étendue alternative comme premier argument à instrument, qui peut même servir de modèle pour le formatage des arguments, par exemple:

@logfire.instrument('Applying my_function to {x=} and {y=}')
def my_function(x, y):
    ...

my_function(3, 4)
# Logs: Applying my_function to x=3 and y=4

Attention

  • @logfire.instrument » Le décorateur doit être appliqué en premier, c'est-à-dire sous tout autre décorateur.
  • Le code source de la fonction doit être accessible.

Niveau de log

Les différents niveaux de logs sont créés comme suit:

  • logfire.trace
  • logfire.debug
  • logfire.info
  • logfire.notice(日志火灾通知)
  • logfire.warn
  • logfire.error
  • logfire.fatal

Les journaux 跟踪 et 调试 sont masqués par défaut. vous pouvez modifier ce paramètre en cliquant sur le menu déroulant « Niveau par défaut » dans la vue en direct:

Niveaux par défaut dropdown

Vous pouvez également utiliser logfire.configure pour définir le niveau minimum à utiliser pour la journalisation de la console, par exemple:

import logfire

logfire.configure(console=logfire.ConsoleOptions(min_log_level='debug'))

Pour enregistrer un message avec un niveau variable, vous pouvez utiliser logfire.log, par exemple logfire.log('info', 'This is an info log') est équivalent à logfire.info('This is an info log')。.

Par défaut, l'étendue est le niveau 信息. Vous pouvez modifier ce paramètre avec le paramètre _level, par exemple 使用 logfire.span('This is a debug span', _level='debug'):. Vous pouvez également modifier le niveau avec span.set_level après le début de l'étendue, mais avant qu 'elle ne soit terminée. LogfireSpan.set_level], par exemple:

with logfire.span('Doing a thing') as span:
    success = do_thing()
    if not success:
        span.set_level('error')

Dans la vue « Live », les spansont colorés en fonction de leur niveau le plus élevé et de leurs descendants.

import logfire

logfire.configure()

with logfire.span('Outer span'):
    with logfire.span('Inner span'):
        logfire.info('This is an info message')
        logfire.error('This is an error message')

Il sera affiché comme ceci:

Spans coloré par niveau

Ici, le niveau des portées elles-mêmes est toujours défini sur les informations 默认, mais elles sont peintes en rouge au lieu de bleu, car elles contiennent des journaux d'erreurs.

Si une étendue se termine par une exception non gérée, le niveau de journalisation de l'étendue sera également défini sur 错误. avec span.record_exception Cela ne se produit pas lorsque la méthode LogfireSpan.record_exception] est utilisée.

Dans une base de données, les niveaux de journal sont stockés sous forme de nombres dans une colonne 级别. Ces valeurs sont basées sur OpenTelemetry, par exemple info est 9. Vous pouvez utiliser la fonction level_num SQL pour convertir les noms de niveau en nombres, par exemple 级别 > level_num('info') recherchera tous les enregistrements « anormaux ». Vous pouvez également utiliser la fonction level_name SQL pour convertir les nombres en noms, par exemple SELECT level_name(level)、... pour afficher les niveaux lisibles par l'homme dans la vue Explorer. Notez que la colonne level est indexée, donc filtrer level = level_num('error') est efficace, mais filtrer level_name(level) = 'error' ne l'est pas.


本文总阅读量次