Logging
El módulo Logging
proporciona una forma de registrar la historia y el progreso de un cálculo como un registro de eventos. Los eventos se crean insertando una declaración de registro en el código fuente, por ejemplo:
@warn "Abandon printf debugging, all ye who enter here!"
┌ Warning: Abandon printf debugging, all ye who enter here!
└ @ Main REPL[1]:1
El sistema ofrece varias ventajas sobre el uso de println()
en tu código fuente. Primero, te permite controlar la visibilidad y presentación de los mensajes sin editar el código fuente. Por ejemplo, en contraste con el @warn
anterior.
@debug "The sum of some values $(sum(rand(100)))"
no producirá ninguna salida por defecto. Además, es muy barato dejar declaraciones de depuración como esta en el código fuente porque el sistema evita evaluar el mensaje si más tarde se ignoraría. En este caso, sum(rand(100))
y el procesamiento de cadenas asociado nunca se ejecutarán a menos que se habilite el registro de depuración.
En segundo lugar, las herramientas de registro te permiten adjuntar datos arbitrarios a cada evento como un conjunto de pares clave-valor. Esto te permite capturar variables locales y otro estado del programa para un análisis posterior. Por ejemplo, para adjuntar la variable de arreglo local A
y la suma de un vector v
como la clave s
, puedes usar
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
Todos los macros de registro @debug
, @info
, @warn
y @error
comparten características comunes que se describen en detalle en la documentación del macro más general @logmsg
.
Log event structure
Cada evento genera varias piezas de datos, algunas proporcionadas por el usuario y otras extraídas automáticamente. Examinemos primero los datos definidos por el usuario:
El nivel de registro es una categoría amplia para el mensaje que se utiliza para el filtrado temprano. Hay varios niveles estándar de tipo
LogLevel
; también son posibles niveles definidos por el usuario. Cada uno es distinto en propósito:Logging.Debug
(nivel de registro -1000) es información destinada al desarrollador del programa. Estos eventos están deshabilitados por defecto.Logging.Info
(nivel de registro 0) es para información general al usuario. Piénsalo como una alternativa a usarprintln
directamente.Logging.Warn
(nivel de registro 1000) significa que algo está mal y es probable que se requiera acción, pero que por ahora el programa sigue funcionando.Logging.Error
(nivel de registro 2000) significa que algo está mal y es poco probable que se recupere, al menos por esta parte del código. A menudo, este nivel de registro no es necesario, ya que lanzar una excepción puede transmitir toda la información requerida.
El mensaje es un objeto que describe el evento. Por convención, los
AbstractString
s pasados como mensajes se asumen en formato markdown. Otros tipos se mostrarán utilizandoprint(io, obj)
ostring(obj)
para salida basada en texto y posiblementeshow(io,mime,obj)
para otras visualizaciones multimedia utilizadas en el registrador instalado.Los pares clave-valor opcionales permiten adjuntar datos arbitrarios a cada evento. Algunas claves tienen un significado convencional que puede afectar la forma en que se interpreta un evento (ver
@logmsg
).
El sistema también genera información estándar para cada evento:
- El
módulo
en el que se expandió el macro de registro. - El
archivo
ylínea
donde ocurre el macro de registro en el código fuente. - Un mensaje
id
que es un identificador único y fijo para la declaración de código fuente donde aparece el macro de registro. Este identificador está diseñado para ser bastante estable incluso si el código fuente del archivo cambia, siempre que la declaración de registro en sí misma permanezca igual. - Un
grupo
para el evento, que se establece en el nombre base del archivo por defecto, sin extensión. Esto se puede utilizar para agrupar mensajes en categorías más finas que el nivel de registro (por ejemplo, todas las advertencias de deprecación tienen el grupo:depwarn
), o en agrupaciones lógicas a través de o dentro de módulos.
Tenga en cuenta que información útil como la hora del evento no se incluye por defecto. Esto se debe a que dicha información puede ser costosa de extraer y también está disponible dinámicamente para el registrador actual. Es simple definir un custom logger para aumentar los datos del evento con la hora, la traza de la pila, los valores de las variables globales y otra información útil según sea necesario.
Processing log events
Como puedes ver en los ejemplos, las declaraciones de registro no mencionan dónde van los eventos de registro ni cómo se procesan. Esta es una característica clave del diseño que hace que el sistema sea componible y natural para su uso concurrente. Lo hace separando dos preocupaciones diferentes:
- Crear eventos de registro es la preocupación del autor del módulo que necesita decidir dónde se activan los eventos y qué información incluir.
- Procesamiento de eventos de registro — es decir, visualización, filtrado, agregación y grabación — es la preocupación del autor de la aplicación que necesita reunir múltiples módulos en una aplicación cooperativa.
Loggers
El procesamiento de eventos es realizado por un logger, que es la primera pieza de código configurable por el usuario que ve el evento. Todos los loggers deben ser subtipos de AbstractLogger
.
Cuando se activa un evento, se encuentra el registrador apropiado buscando un registrador local de tarea con el registrador global como respaldo. La idea aquí es que el código de la aplicación sabe cómo se deben procesar los eventos de registro y existe en algún lugar en la parte superior de la pila de llamadas. Por lo tanto, debemos buscar hacia arriba a través de la pila de llamadas para descubrir el registrador; es decir, el registrador debe tener un alcance dinámico. (Este es un punto de contraste con los marcos de registro donde el registrador tiene un alcance léxico; proporcionado explícitamente por el autor del módulo o como una simple variable global. En tal sistema, es incómodo controlar el registro mientras se compone funcionalidad de múltiples módulos.)
El registrador global se puede configurar con global_logger
, y los registradores locales de tareas se controlan utilizando with_logger
. Las tareas recién creadas heredan el registrador de la tarea padre.
Hay tres tipos de registradores proporcionados por la biblioteca. ConsoleLogger
es el registrador predeterminado que ves al iniciar el REPL. Muestra eventos en un formato de texto legible y trata de ofrecer un control simple pero amigable sobre el formato y el filtrado. NullLogger
es una forma conveniente de descartar todos los mensajes cuando sea necesario; es el equivalente de registro del flujo devnull
. SimpleLogger
es un registrador de formato de texto muy simplista, principalmente útil para depurar el sistema de registro en sí.
Los registradores personalizados deben venir con sobrecargas para las funciones descritas en el reference section.
Early filtering and message handling
Cuando ocurre un evento, se realizan algunos pasos de filtrado temprano para evitar generar mensajes que serán descartados:
- El nivel de registro de mensajes se verifica contra un nivel mínimo global (establecido a través de
disable_logging
). Este es un ajuste global rudimentario pero extremadamente económico. - El estado actual del registrador se consulta y el nivel del mensaje se verifica en comparación con el nivel mínimo en caché del registrador, como se encuentra al llamar a
Logging.min_enabled_level
. Este comportamiento se puede anular a través de variables de entorno (más sobre esto más adelante). - La función
Logging.shouldlog
se llama con el registrador actual, tomando alguna información mínima (nivel, módulo, grupo, id) que puede ser calculada estáticamente. Más útilmente,shouldlog
recibe un eventoid
que puede ser utilizado para descartar eventos temprano basado en un predicado en caché.
Si todas estas verificaciones pasan, el mensaje y los pares clave-valor se evalúan en su totalidad y se pasan al registrador actual a través de la función Logging.handle_message
. handle_message()
puede realizar filtrados adicionales según sea necesario y mostrar el evento en la pantalla, guardarlo en un archivo, etc.
Las excepciones que ocurren al generar el evento de registro se capturan y registran por defecto. Esto evita que eventos individuales rotos hagan que la aplicación se bloquee, lo cual es útil al habilitar eventos de depuración poco utilizados en un sistema de producción. Este comportamiento se puede personalizar por tipo de registrador extendiendo Logging.catch_exceptions
.
Testing log events
Los eventos de registro son un efecto secundario de ejecutar código normal, pero es posible que desees probar mensajes informativos y advertencias particulares. El módulo Test
proporciona un macro @test_logs
que se puede utilizar para hacer coincidencias de patrones contra el flujo de eventos de registro.
Environment variables
El filtrado de mensajes puede ser influenciado a través de la variable de entorno JULIA_DEBUG
, y sirve como una forma sencilla de habilitar el registro de depuración para un archivo o módulo. Cargar julia con JULIA_DEBUG=loading
activará los mensajes de registro @debug
en loading.jl
. Por ejemplo, en las terminales de 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
...
En Windows, lo mismo se puede lograr en CMD
ejecutando primero set JULIA_DEBUG="loading"
y en Powershell
a través de $env:JULIA_DEBUG="loading"
.
De manera similar, la variable de entorno se puede utilizar para habilitar el registro de depuración de módulos, como Pkg
, o raíces de módulos (ver Base.moduleroot
). Para habilitar todos los registros de depuración, utiliza el valor especial all
.
Para activar el registro de depuración desde el REPL, establece ENV["JULIA_DEBUG"]
al nombre del módulo de interés. Las funciones definidas en el REPL pertenecen al módulo Main
; el registro para ellas se puede habilitar de esta manera:
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
Utiliza un separador de comas para habilitar la depuración de múltiples módulos: JULIA_DEBUG=loading,Main
.
Examples
Example: Writing log events to a file
A veces puede ser útil escribir eventos de registro en un archivo. Aquí hay un ejemplo de cómo usar un registrador local de tarea y un registrador global para escribir información en un archivo de texto:
# 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
Aquí hay un ejemplo de crear un ConsoleLogger
que permite pasar cualquier mensaje con un nivel de registro mayor o igual a 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
— ModuleUtilidades para capturar, filtrar y presentar flujos de eventos de registro. Normalmente no necesitas importar Logging
para crear eventos de registro; para esto, los macros de registro estándar como @info
ya están exportados por Base
y disponibles por defecto.
Creating events
Logging.@logmsg
— Macro@debug mensaje [clave=valor | valor ...]
@info mensaje [clave=valor | valor ...]
@warn mensaje [clave=valor | valor ...]
@error mensaje [clave=valor | valor ...]
@logmsg nivel mensaje [clave=valor | valor ...]
Crea un registro de log con un mensaje
informativo. Para conveniencia, se definen cuatro macros de logging @debug
, @info
, @warn
y @error
que registran en los niveles de severidad estándar Debug
, Info
, Warn
y Error
. @logmsg
permite que nivel
se establezca programáticamente a cualquier LogLevel
o tipos de niveles de log personalizados.
mensaje
debe ser una expresión que evalúe a una cadena que sea una descripción legible por humanos del evento de log. Por convención, esta cadena se formateará como markdown cuando se presente.
La lista opcional de pares clave=valor
admite metadatos arbitrarios definidos por el usuario que se pasarán al backend de logging como parte del registro de log. Si solo se proporciona una expresión valor
, se generará una clave que representa la expresión utilizando Symbol
. Por ejemplo, x
se convierte en x=x
, y foo(10)
se convierte en Symbol("foo(10)")=foo(10)
. Para descomponer una lista de pares clave-valor, utiliza la sintaxis normal de descomposición, @info "blah" kws...
.
Hay algunas claves que permiten anular datos de log generados automáticamente:
_module=mod
se puede usar para especificar un módulo de origen diferente de la ubicación de origen del mensaje._group=symbol
se puede usar para anular el grupo de mensajes (esto normalmente se deriva del nombre base del archivo de origen)._id=symbol
se puede usar para anular el identificador único de mensaje generado automáticamente. Esto es útil si necesitas asociar muy de cerca mensajes generados en diferentes líneas de origen._file=string
y_line=integer
se pueden usar para anular la aparente ubicación de origen de un mensaje de log.
También hay algunos pares clave-valor que tienen un significado convencional:
maxlog=integer
debe usarse como una pista para el backend de que el mensaje no debe mostrarse más demaxlog
veces.exception=ex
debe usarse para transportar una excepción con un mensaje de log, a menudo utilizado con@error
. Un backtrace asociadobt
puede adjuntarse utilizando la tuplaexception=(ex,bt)
.
Ejemplos
@debug "Información de depuración detallada. Invisible por defecto"
@info "Un mensaje informativo"
@warn "Algo fue extraño. Deberías prestar atención"
@error "Ocurrió un error no fatal"
x = 10
@info "Algunas variables adjuntas al mensaje" x a=42.0
@debug begin
sA = sum(A)
"sum(A) = $sA es una operación costosa, evaluada solo cuando `shouldlog` devuelve verdadero"
end
for i=1:10000
@info "Con el backend por defecto, solo verás (i = $i) diez veces" maxlog=10
@debug "Algoritmo1" i progress=i/10000
end
Logging.LogLevel
— TypeLogLevel(nivel)
Severidad/verbosidad de un registro de log.
El nivel de log proporciona una clave contra la cual se pueden filtrar los registros de log potenciales, antes de que se realice cualquier otro trabajo para construir la estructura de datos del registro de log en sí.
Ejemplos
julia> Logging.LogLevel(0) == Logging.Info
true
Logging.Debug
— ConstantDepurar
Alias para LogLevel(-1000)
.
Logging.Info
— ConstantInfo
Alias para LogLevel(0)
.
Logging.Warn
— ConstantAdvertencia
Alias para LogLevel(1000)
.
Logging.Error
— ConstantError
Alias para LogLevel(2000)
.
Logging.BelowMinLevel
— ConstantBelowMinLevel
Alias para LogLevel(-1_000_001)
.
Logging.AboveMaxLevel
— ConstantAboveMaxLevel
Alias para LogLevel(1_000_001)
.
Processing events with AbstractLogger
El procesamiento de eventos se controla mediante la sobrescritura de funciones asociadas con 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 registrador controla cómo se filtran y despachan los registros de log. Cuando se genera un registro de log, el registrador es la primera pieza de código configurable por el usuario que inspecciona el registro y decide qué hacer con él.
Logging.handle_message
— Functionhandle_message(logger, level, message, _module, group, id, file, line; key1=val1, ...)
Registra un mensaje en logger
en el nivel level
. La ubicación lógica en la que se generó el mensaje está dada por el módulo _module
y el grupo; la ubicación de origen por file
y line
. id
es un valor único arbitrario (típicamente un Symbol
) que se utilizará como clave para identificar la declaración de registro al filtrar.
Logging.shouldlog
— Functionshouldlog(logger, level, _module, group, id)
Devuelve true
cuando logger
acepta un mensaje en level
, generado para _module
, group
y con un identificador de registro único id
.
Logging.min_enabled_level
— Functionmin_enabled_level(logger)
Devuelve el nivel mínimo habilitado para logger
para el filtrado temprano. Es decir, el nivel de registro por debajo o igual al cual se filtran todos los mensajes.
Logging.catch_exceptions
— Functioncatch_exceptions(logger)
Devuelve true
si el registrador debe capturar excepciones que ocurren durante la construcción del registro de log. Por defecto, los mensajes son capturados.
Por defecto, todas las excepciones son capturadas para evitar que la generación de mensajes de log haga que el programa se bloquee. Esto permite a los usuarios activar con confianza funcionalidades poco utilizadas, como el registro de depuración, en un sistema de producción.
Si deseas utilizar el registro como un rastro de auditoría, deberías desactivar esto para tu tipo de registrador.
Logging.disable_logging
— Functiondisable_logging(level)
Desactiva todos los mensajes de registro en niveles de registro iguales o inferiores a level
. Esta es una configuración global, destinada a hacer que el registro de depuración sea extremadamente barato cuando está desactivado.
Ejemplos
Logging.disable_logging(Logging.Info) # Desactivar depuración e información
Using Loggers
Instalación e inspección del registrador:
Logging.global_logger
— Functionglobal_logger()
Devuelve el registrador global, utilizado para recibir mensajes cuando no existe un registrador específico para la tarea actual.
global_logger(logger)
Establece el registrador global en logger
y devuelve el registrador global anterior.
Logging.with_logger
— Functionwith_logger(function, logger)
Ejecuta function
, dirigiendo todos los mensajes de registro a logger
.
Ejemplos
function test(x)
@info "x = $x"
end
with_logger(logger) do
test(1)
test([1,2])
end
Logging.current_logger
— Functioncurrent_logger()
Devuelve el registrador para la tarea actual, o el registrador global si no hay ninguno adjunto a la tarea.
Registros que se suministran con el sistema:
Logging.NullLogger
— TypeNullLogger()
Registrador que desactiva todos los mensajes y no produce salida - el equivalente del registrador a /dev/null.
Base.CoreLogging.ConsoleLogger
— TypeConsoleLogger([stream,] min_level=Info; meta_formatter=default_metafmt,
show_limited=true, right_justify=0)
Registrador con formato optimizado para la legibilidad en una consola de texto, por ejemplo, trabajo interactivo con el REPL de Julia.
Los niveles de registro inferiores a min_level
son filtrados.
El formato del mensaje se puede controlar configurando argumentos de palabra clave:
meta_formatter
es una función que toma los metadatos del evento de registro(nivel, _módulo, grupo, id, archivo, línea)
y devuelve un color (como se pasaría a printstyled), un prefijo y un sufijo para el mensaje de registro. El valor predeterminado es prefijar con el nivel de registro y un sufijo que contenga el módulo, el archivo y la ubicación de la línea.show_limited
limita la impresión de estructuras de datos grandes a algo que puede caber en la pantalla configurando la clave:limit
deIOContext
durante el formato.right_justify
es la columna entera en la que los metadatos del registro están justificados a la derecha. El valor predeterminado es cero (los metadatos van en su propia línea).
Logging.SimpleLogger
— TypeSimpleLogger([stream,] min_level=Info)
Registrador simplista para registrar todos los mensajes con un nivel mayor o igual a min_level
en stream
. Si el stream está cerrado, entonces los mensajes con un nivel de registro mayor o igual a Warn
se registrarán en stderr
y los inferiores en stdout
.