Logging
Le module Logging
fournit un moyen d'enregistrer l'historique et le progrès d'un calcul sous forme de journal d'événements. Les événements sont créés en insérant une instruction de journalisation dans le code source, par exemple :
@warn "Abandon printf debugging, all ye who enter here!"
┌ Warning: Abandon printf debugging, all ye who enter here!
└ @ Main REPL[1]:1
Le système offre plusieurs avantages par rapport à l'insertion d'appels à println()
dans votre code source. Tout d'abord, il vous permet de contrôler la visibilité et la présentation des messages sans modifier le code source. Par exemple, contrairement à l'@warn
ci-dessus
@debug "The sum of some values $(sum(rand(100)))"
produira aucune sortie par défaut. De plus, il est très peu coûteux de laisser des instructions de débogage comme celle-ci dans le code source car le système évite d'évaluer le message s'il sera ensuite ignoré. Dans ce cas, sum(rand(100))
et le traitement de chaîne associé ne seront jamais exécutés à moins que la journalisation de débogage ne soit activée.
Deuxièmement, les outils de journalisation vous permettent d'attacher des données arbitraires à chaque événement sous forme d'un ensemble de paires clé-valeur. Cela vous permet de capturer des variables locales et d'autres états du programme pour une analyse ultérieure. Par exemple, pour attacher la variable de tableau local A
et la somme d'un vecteur v
sous la clé s
, vous pouvez utiliser
A = ones(Int, 4, 4)
v = ones(100)
@info "Some variables" A s=sum(v)
# output
┌ Info: Some variables
│ A =
│ 4×4 Matrix{Int64}:
│ 1 1 1 1
│ 1 1 1 1
│ 1 1 1 1
│ 1 1 1 1
└ s = 100.0
Tous les macros de journalisation @debug
, @info
, @warn
et @error
partagent des caractéristiques communes qui sont décrites en détail dans la documentation du macro plus général @logmsg
.
Log event structure
Chaque événement génère plusieurs éléments de données, certains fournis par l'utilisateur et d'autres extraits automatiquement. Examinons d'abord les données définies par l'utilisateur :
Le niveau de journalisation est une catégorie large pour le message qui est utilisée pour un filtrage précoce. Il existe plusieurs niveaux standard de type
LogLevel
; des niveaux définis par l'utilisateur sont également possibles. Chacun est distinct dans son objectif :Logging.Debug
(niveau de journal -1000) est une information destinée au développeur du programme. Ces événements sont désactivés par défaut.Logging.Info
(niveau de journal 0) est destiné à fournir des informations générales à l'utilisateur. Pensez-y comme une alternative à l'utilisation deprintln
directement.Logging.Warn
(niveau de journal 1000) signifie que quelque chose ne va pas et qu'une action est probablement nécessaire, mais que pour l'instant le programme fonctionne toujours.Logging.Error
(niveau de journal 2000) signifie que quelque chose ne va pas et qu'il est peu probable que cela soit récupéré, du moins par cette partie du code. Souvent, ce niveau de journal est inutile car le lancement d'une exception peut transmettre toutes les informations requises.
Le message est un objet décrivant l'événement. Par convention, les
AbstractString
passés en tant que messages sont supposés être au format markdown. D'autres types seront affichés en utilisantprint(io, obj)
oustring(obj)
pour une sortie textuelle et éventuellementshow(io,mime,obj)
pour d'autres affichages multimédias utilisés dans le journal installé.Les paires clé-valeur optionnelles permettent d'attacher des données arbitraires à chaque événement. Certaines clés ont une signification conventionnelle qui peut affecter la façon dont un événement est interprété (voir
@logmsg
).
Le système génère également des informations standard pour chaque événement :
- Le
module
dans lequel la macro de journalisation a été développée. - Le
fichier
et laligne
où la macro de journalisation se produit dans le code source. - Un identifiant
id
qui est un identifiant unique et fixe pour l'instruction de code source où la macro de journalisation apparaît. Cet identifiant est conçu pour être assez stable même si le code source du fichier change, tant que l'instruction de journalisation elle-même reste la même. - Un
groupe
pour l'événement, qui est défini par défaut sur le nom de base du fichier, sans extension. Cela peut être utilisé pour regrouper les messages en catégories plus fines que le niveau de journal (par exemple, tous les avertissements de dépréciation ont le groupe:depwarn
), ou en regroupements logiques à travers ou au sein des modules.
Remarquez que certaines informations utiles, telles que l'heure de l'événement, ne sont pas incluses par défaut. Cela est dû au fait que ces informations peuvent être coûteuses à extraire et sont également dynamiquement disponibles pour le logger actuel. Il est simple de définir un custom logger pour compléter les données de l'événement avec l'heure, la trace de retour, les valeurs des variables globales et d'autres informations utiles selon les besoins.
Processing log events
Comme vous pouvez le voir dans les exemples, les instructions de journalisation ne mentionnent pas où vont les événements de journal ou comment ils sont traités. C'est une caractéristique de conception clé qui rend le système composable et naturel pour une utilisation concurrente. Cela se fait en séparant deux préoccupations différentes :
- Créer des événements de journal est la préoccupation de l'auteur du module qui doit décider où les événements sont déclenchés et quelles informations inclure.
- Traitement des événements de journal — c'est-à-dire l'affichage, le filtrage, l'agrégation et l'enregistrement — est la préoccupation de l'auteur de l'application qui doit rassembler plusieurs modules dans une application coopérative.
Loggers
Le traitement des événements est effectué par un logger, qui est le premier morceau de code configurable par l'utilisateur à voir l'événement. Tous les loggers doivent être des sous-types de AbstractLogger
.
Lorsque qu'un événement est déclenché, le logger approprié est trouvé en recherchant un logger local à la tâche avec le logger global comme solution de repli. L'idée ici est que le code de l'application sait comment les événements de journalisation doivent être traités et se trouve quelque part en haut de la pile d'appels. Nous devrions donc remonter la pile d'appels pour découvrir le logger — c'est-à-dire que le logger devrait être dynamiquement scoping. (C'est un point de contraste avec les frameworks de journalisation où le logger est lexicalement scoping ; fourni explicitement par l'auteur du module ou en tant que simple variable globale. Dans un tel système, il est difficile de contrôler la journalisation tout en composant des fonctionnalités à partir de plusieurs modules.)
Le journal global peut être configuré avec global_logger
, et les journaux locaux de tâche sont contrôlés à l'aide de with_logger
. Les tâches nouvellement créées héritent du journal de la tâche parente.
Il existe trois types de journaux fournis par la bibliothèque. ConsoleLogger
est le journal par défaut que vous voyez lorsque vous démarrez le REPL. Il affiche les événements dans un format texte lisible et essaie de donner un contrôle simple mais convivial sur le formatage et le filtrage. NullLogger
est un moyen pratique de supprimer tous les messages si nécessaire ; c'est l'équivalent de journalisation du flux devnull
. SimpleLogger
est un journal de formatage texte très simpliste, principalement utile pour déboguer le système de journalisation lui-même.
Les enregistreurs personnalisés devraient être fournis avec des surcharges pour les fonctions décrites dans le reference section.
Early filtering and message handling
Lorsqu'un événement se produit, quelques étapes de filtrage précoce ont lieu pour éviter de générer des messages qui seront rejetés :
- Le niveau de journalisation des messages est vérifié par rapport à un niveau minimum global (défini via
disable_logging
). C'est un paramètre global rudimentaire mais extrêmement économique. - L'état actuel du logger est consulté et le niveau de message est vérifié par rapport au niveau minimum mis en cache du logger, tel que trouvé en appelant
Logging.min_enabled_level
. Ce comportement peut être remplacé via des variables d'environnement (plus d'informations à ce sujet plus tard). - La fonction
Logging.shouldlog
est appelée avec le logger actuel, prenant quelques informations minimales (niveau, module, groupe, id) qui peuvent être calculées statiquement. Plus utilement,shouldlog
reçoit unid
d'événement qui peut être utilisé pour rejeter les événements tôt en fonction d'un prédicat mis en cache.
Si tous ces contrôles passent, le message et les paires clé-valeur sont évalués dans leur intégralité et transmis au logger actuel via la fonction Logging.handle_message
. handle_message()
peut effectuer un filtrage supplémentaire si nécessaire et afficher l'événement à l'écran, l'enregistrer dans un fichier, etc.
Les exceptions qui se produisent lors de la génération de l'événement de journal sont capturées et enregistrées par défaut. Cela empêche les événements individuels défectueux de faire planter l'application, ce qui est utile lors de l'activation d'événements de débogage peu utilisés dans un système de production. Ce comportement peut être personnalisé par type de logger en étendant Logging.catch_exceptions
.
Testing log events
Log events are a side effect of running normal code, but you might find yourself wanting to test particular informational messages and warnings. The Test
module provides a @test_logs
macro that can be used to pattern match against the log event stream.
Environment variables
Le filtrage des messages peut être influencé par la variable d'environnement JULIA_DEBUG
, et sert de moyen simple pour activer la journalisation de débogage pour un fichier ou un module. Charger julia avec JULIA_DEBUG=loading
activera les messages de journalisation @debug
dans loading.jl
. Par exemple, dans les shells Linux :
$ JULIA_DEBUG=loading julia -e 'using OhMyREPL'
┌ Debug: Rejecting cache file /home/user/.julia/compiled/v0.7/OhMyREPL.ji due to it containing an incompatible cache header
└ @ Base loading.jl:1328
[ Info: Recompiling stale cache file /home/user/.julia/compiled/v0.7/OhMyREPL.ji for module OhMyREPL
┌ Debug: Rejecting cache file /home/user/.julia/compiled/v0.7/Tokenize.ji due to it containing an incompatible cache header
└ @ Base loading.jl:1328
...
Sur Windows, la même chose peut être réalisée dans CMD
en exécutant d'abord set JULIA_DEBUG="loading"
et dans Powershell
via $env:JULIA_DEBUG="loading"
.
De la même manière, la variable d'environnement peut être utilisée pour activer la journalisation de débogage des modules, tels que Pkg
, ou des racines de module (voir Base.moduleroot
). Pour activer toute la journalisation de débogage, utilisez la valeur spéciale all
.
Pour activer la journalisation de débogage depuis le REPL, définissez ENV["JULIA_DEBUG"]
sur le nom du module d'intérêt. Les fonctions définies dans le REPL appartiennent au module Main
; la journalisation pour celles-ci peut être activée comme ceci :
julia> foo() = @debug "foo"
foo (generic function with 1 method)
julia> foo()
julia> ENV["JULIA_DEBUG"] = Main
Main
julia> foo()
┌ Debug: foo
└ @ Main REPL[1]:1
Utilisez un séparateur de virgule pour activer le débogage pour plusieurs modules : JULIA_DEBUG=loading,Main
.
Examples
Example: Writing log events to a file
Parfois, il peut être utile d'écrire des événements de journal dans un fichier. Voici un exemple de la façon d'utiliser un journaliseur local à la tâche et un journaliseur global pour écrire des informations dans un fichier texte :
# Load the logging module
julia> using Logging
# Open a textfile for writing
julia> io = open("log.txt", "w+")
IOStream(<file log.txt>)
# Create a simple logger
julia> logger = SimpleLogger(io)
SimpleLogger(IOStream(<file log.txt>), Info, Dict{Any,Int64}())
# Log a task-specific message
julia> with_logger(logger) do
@info("a context specific log message")
end
# Write all buffered messages to the file
julia> flush(io)
# Set the global logger to logger
julia> global_logger(logger)
SimpleLogger(IOStream(<file log.txt>), Info, Dict{Any,Int64}())
# This message will now also be written to the file
julia> @info("a global log message")
# Close the file
julia> close(io)
Example: Enable debug-level messages
Voici un exemple de création d'un ConsoleLogger
qui laisse passer tous les messages avec un niveau de journalisation supérieur ou égal à Logging.Debug
.
julia> using Logging
# Create a ConsoleLogger that prints any log messages with level >= Debug to stderr
julia> debuglogger = ConsoleLogger(stderr, Logging.Debug)
# Enable debuglogger for a task
julia> with_logger(debuglogger) do
@debug "a context specific log message"
end
# Set the global logger
julia> global_logger(debuglogger)
Reference
Logging module
Logging.Logging
— ModuleUtilitaires pour capturer, filtrer et présenter des flux d'événements de journal. Normalement, vous n'avez pas besoin d'importer Logging
pour créer des événements de journal ; pour cela, les macros de journalisation standard telles que @info
sont déjà exportées par Base
et disponibles par défaut.
Creating events
Logging.@logmsg
— Macro@debug message [clé=valeur | valeur ...]
@info message [clé=valeur | valeur ...]
@warn message [clé=valeur | valeur ...]
@error message [clé=valeur | valeur ...]
@logmsg niveau message [clé=valeur | valeur ...]
Créez un enregistrement de journal avec un `message` informatif. Pour plus de commodité, quatre macros de journalisation `@debug`, `@info`, `@warn` et `@error` sont définies, qui enregistrent aux niveaux de gravité standard `Debug`, `Info`, `Warn` et `Error`. `@logmsg` permet de définir `niveau` de manière programmatique à tout `LogLevel` ou types de niveaux de journal personnalisés.
`message` doit être une expression qui évalue à une chaîne qui est une description lisible par l'homme de l'événement de journal. Par convention, cette chaîne sera formatée en tant que markdown lorsqu'elle est présentée.
La liste optionnelle de paires `clé=valeur` prend en charge des métadonnées définies par l'utilisateur qui seront transmises au backend de journalisation dans le cadre de l'enregistrement de journal. Si seule une expression `valeur` est fournie, une clé représentant l'expression sera générée en utilisant [`Symbol`](@ref). Par exemple, `x` devient `x=x`, et `foo(10)` devient `Symbol("foo(10)")=foo(10)`. Pour splatter une liste de paires clé-valeur, utilisez la syntaxe de splatting normale, `@info "blah" kws...`.
Il existe certaines clés qui permettent de remplacer les données de journal générées automatiquement :
* `_module=mod` peut être utilisé pour spécifier un module d'origine différent de l'emplacement source du message.
* `_group=symbol` peut être utilisé pour remplacer le groupe de messages (cela est normalement dérivé du nom de base du fichier source).
* `_id=symbol` peut être utilisé pour remplacer l'identifiant de message unique généré automatiquement. Cela est utile si vous devez associer très étroitement des messages générés sur différentes lignes de code source.
* `_file=string` et `_line=integer` peuvent être utilisés pour remplacer l'emplacement source apparent d'un message de journal.
Il y a aussi quelques paires clé-valeur qui ont une signification conventionnelle :
* `maxlog=integer` doit être utilisé comme un indice pour le backend que le message ne doit pas être affiché plus de `maxlog` fois.
* `exception=ex` doit être utilisé pour transporter une exception avec un message de journal, souvent utilisé avec `@error`. Un backtrace associé `bt` peut être attaché en utilisant le tuple `exception=(ex,bt)`.
# Exemples
julia @debug "Informations de débogage détaillées. Invisible par défaut" @info "Un message informatif" @warn "Quelque chose était étrange. Vous devriez faire attention" @error "Une erreur non fatale s'est produite"
x = 10 @info "Certaines variables attachées au message" x a=42.0
@debug begin sA = sum(A) "sum(A) = sA est une opération coûteuse, évaluée uniquement lorsque shouldlog
renvoie vrai" end
for i=1:10000 @info "Avec le backend par défaut, vous ne verrez que (i = i) dix fois" maxlog=10 @debug "Algorithm1" i progress=i/10000 end ```
Logging.LogLevel
— TypeLogLevel(niveau)
Gravité/verbosité d'un enregistrement de journal.
Le niveau de journal fournit une clé contre laquelle les enregistrements de journal potentiels peuvent être filtrés, avant que tout autre travail ne soit effectué pour construire la structure de données de l'enregistrement de journal lui-même.
Exemples
julia> Logging.LogLevel(0) == Logging.Info
true
Logging.Debug
— ConstantDébogage
Alias pour LogLevel(-1000)
.
Logging.Info
— ConstantInfo
Alias pour LogLevel(0)
.
Logging.Warn
— ConstantAvertir
Alias pour LogLevel(1000)
.
Logging.Error
— ConstantErreur
Alias pour LogLevel(2000)
.
Logging.BelowMinLevel
— ConstantBelowMinLevel
Alias pour LogLevel(-1_000_001)
.
Logging.AboveMaxLevel
— ConstantAboveMaxLevel
Alias pour LogLevel(1_000_001)
.
Processing events with AbstractLogger
Le traitement des événements est contrôlé par la substitution des fonctions associées à AbstractLogger
:
Methods to implement | Brief description | |
---|---|---|
Logging.handle_message | Handle a log event | |
Logging.shouldlog | Early filtering of events | |
Logging.min_enabled_level | Lower bound for log level of accepted events | |
Optional methods | Default definition | Brief description |
Logging.catch_exceptions | true | Catch exceptions during event evaluation |
Logging.AbstractLogger
— TypeUn logger contrôle comment les enregistrements de log sont filtrés et dispatchés. Lorsqu'un enregistrement de log est généré, le logger est le premier morceau de code configurable par l'utilisateur qui inspecte l'enregistrement et décide quoi en faire.
Logging.handle_message
— Functionhandle_message(logger, level, message, _module, group, id, file, line; key1=val1, ...)
Enregistre un message dans logger
au niveau level
. L'emplacement logique où le message a été généré est donné par le module _module
et le groupe ; l'emplacement source par file
et line
. id
est une valeur unique arbitraire (typiquement un Symbol
) à utiliser comme clé pour identifier l'instruction de journalisation lors du filtrage.
Logging.shouldlog
— Functionshouldlog(logger, level, _module, group, id)
Retourne true
lorsque logger
accepte un message au niveau level
, généré pour _module
, group
et avec un identifiant de journal unique id
.
Logging.min_enabled_level
— Functionmin_enabled_level(logger)
Retourne le niveau minimum activé pour logger
pour un filtrage précoce. C'est-à-dire, le niveau de journalisation en dessous ou égal auquel tous les messages sont filtrés.
Logging.catch_exceptions
— Functioncatch_exceptions(logger)
Retourne true
si le logger doit attraper les exceptions qui se produisent lors de la construction de l'enregistrement de log. Par défaut, les messages sont attrapés.
Par défaut, toutes les exceptions sont attrapées pour empêcher la génération de messages de log de faire planter le programme. Cela permet aux utilisateurs de basculer en toute confiance des fonctionnalités peu utilisées - comme le logging de débogage - dans un système de production.
Si vous souhaitez utiliser le logging comme une piste de vérification, vous devez désactiver cela pour votre type de logger.
Logging.disable_logging
— Functiondisable_logging(level)
Désactive tous les messages de journalisation à des niveaux de journalisation égaux ou inférieurs à level
. Il s'agit d'un paramètre global, destiné à rendre la journalisation de débogage extrêmement peu coûteuse lorsqu'elle est désactivée.
Exemples
Logging.disable_logging(Logging.Info) # Désactiver le débogage et les informations
Using Loggers
Installation et inspection du logger :
Logging.global_logger
— Functionglobal_logger()
Retourne le journaliseur global, utilisé pour recevoir des messages lorsqu'aucun journaliseur spécifique n'existe pour la tâche actuelle.
global_logger(logger)
Définit le journaliseur global sur logger
, et retourne le précédent journaliseur global.
Logging.with_logger
— Functionwith_logger(function, logger)
Exécutez function
, en dirigeant tous les messages de journal vers logger
.
Exemples
function test(x)
@info "x = $x"
end
with_logger(logger) do
test(1)
test([1,2])
end
Logging.current_logger
— Functioncurrent_logger()
Renvoie le logger pour la tâche actuelle, ou le logger global s'il n'y en a pas de rattaché à la tâche.
Loggers fournis avec le système :
Logging.NullLogger
— TypeNullLogger()
Logger qui désactive tous les messages et ne produit aucune sortie - l'équivalent du logger de /dev/null.
Base.CoreLogging.ConsoleLogger
— TypeConsoleLogger([stream,] min_level=Info; meta_formatter=default_metafmt,
show_limited=true, right_justify=0)
Logger avec un formatage optimisé pour la lisibilité dans une console texte, par exemple pour un travail interactif avec le REPL Julia.
Les niveaux de journalisation inférieurs à min_level
sont filtrés.
Le formatage des messages peut être contrôlé en définissant des arguments de mot-clé :
meta_formatter
est une fonction qui prend les métadonnées de l'événement de journalisation(level, _module, group, id, file, line)
et retourne une couleur (comme serait passé à printstyled), un préfixe et un suffixe pour le message de journalisation. La valeur par défaut est de préfixer avec le niveau de journalisation et un suffixe contenant le module, le fichier et la ligne.show_limited
limite l'impression des grandes structures de données à quelque chose qui peut tenir sur l'écran en définissant la clé:limit
IOContext
lors du formatage.right_justify
est la colonne entière à laquelle les métadonnées de journalisation sont justifiées à droite. La valeur par défaut est zéro (les métadonnées vont sur leur propre ligne).
Logging.SimpleLogger
— TypeSimpleLogger([stream,] min_level=Info)
Logger simpliste pour enregistrer tous les messages avec un niveau supérieur ou égal à min_level
dans stream
. Si le flux est fermé, les messages avec un niveau de journalisation supérieur ou égal à Warn
seront enregistrés dans stderr
et ceux en dessous dans stdout
.