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 usar println 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 AbstractStrings pasados como mensajes se asumen en formato markdown. Otros tipos se mostrarán utilizando print(io, obj) o string(obj) para salida basada en texto y posiblemente show(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 y lí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:

  1. 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.
  2. 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).
  3. 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 evento id 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.LoggingModule

Utilidades 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.

source

Creating events

Logging.@logmsgMacro
@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 de maxlog veces.
  • exception=ex debe usarse para transportar una excepción con un mensaje de log, a menudo utilizado con @error. Un backtrace asociado bt puede adjuntarse utilizando la tupla exception=(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
source
Logging.LogLevelType
LogLevel(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
source

Processing events with AbstractLogger

El procesamiento de eventos se controla mediante la sobrescritura de funciones asociadas con AbstractLogger:

Methods to implementBrief description
Logging.handle_messageHandle a log event
Logging.shouldlogEarly filtering of events
Logging.min_enabled_levelLower bound for log level of accepted events
Optional methodsDefault definitionBrief description
Logging.catch_exceptionstrueCatch exceptions during event evaluation
Logging.AbstractLoggerType

Un 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.

source
Logging.handle_messageFunction
handle_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.

source
Logging.shouldlogFunction
shouldlog(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.

source
Logging.min_enabled_levelFunction
min_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.

source
Logging.catch_exceptionsFunction
catch_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.

source
Logging.disable_loggingFunction
disable_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
source

Using Loggers

Instalación e inspección del registrador:

Logging.global_loggerFunction
global_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.

source
Logging.with_loggerFunction
with_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
source
Logging.current_loggerFunction
current_logger()

Devuelve el registrador para la tarea actual, o el registrador global si no hay ninguno adjunto a la tarea.

source

Registros que se suministran con el sistema:

Logging.NullLoggerType
NullLogger()

Registrador que desactiva todos los mensajes y no produce salida - el equivalente del registrador a /dev/null.

source
Base.CoreLogging.ConsoleLoggerType
ConsoleLogger([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 de IOContext 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).
source
Logging.SimpleLoggerType
SimpleLogger([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.

source