racket/pkgs/racket-doc/scribblings/reference/logging.scrbl
Matthew Flatt 710320e3dc "Mac OS X" -> "Mac OS"
Although "macOS" is the correct name for Apple's current desktop OS,
we've decided to go with "Mac OS" to cover all of Apple's Unix-like
desktop OS versions. The label "Mac OS" is more readable, clear in
context (i.e., unlikely to be confused with the Mac OSes that
proceeded Mac OS X), and as likely to match Apple's future OS names
as anything.
2016-12-23 12:18:36 -07:00

409 lines
16 KiB
Racket

#lang scribble/doc
@(require "mz.rkt"
scribble/bnf
(for-label racket/cmdline))
@title[#:tag "logging"]{Logging}
A @deftech{logger} accepts events that contain information to be
logged for interested parties. A @deftech{log receiver} represents an
interested party that receives logged events asynchronously. Each
event has a topic and level of detail, and a @tech{log receiver} subscribes to
logging events at a certain level of detail (and higher) for a specific topic or for all topics. The
levels, in increasing order of detail, are @racket['fatal],
@racket['error], @racket['warning], @racket['info], and
@racket['debug].
To help organize logged events, a @tech{logger} can have a default topic and/or
a parent logger. Every event reported to a logger is also propagated to
its parent (if any), while the event message is prefixed with the logger's topic (if
any) if the message doesn't already have a topic. Furthermore, events that are propagated
from a logger to its parent can be filtered by level and topic.
On start-up, Racket creates an initial logger that is used to
record events from the core run-time system. For example, an
@racket['debug] event is reported for each garbage collection (see
@secref["gc-model"]). For this initial logger, two log receivers are
also created: one that writes events to the process's original error
output port, and one that writes events to the system log. The level
of written events in each case is system-specific, and the default can
be changed through command-line flags (see @secref["mz-cmdline"]) or
through environment variables:
@itemize[
@item{If the @indexed-envvar{PLTSTDERR} environment variable is
defined and is not overridden by a command-line flag, it
determines the level of the @tech{log receiver} that propagates
events to the original error port.
The environment variable's value can be a @nonterm{level}:
@litchar{none}, @litchar{fatal}, @litchar{error},
@litchar{warning}, @litchar{info}, or @litchar{debug}; all
events the corresponding level of higher are printed. After an
initial @nonterm{level}, the value can contain
whitespace-separated specifications of the form
@nonterm{level}@litchar["@"]@nonterm{topic}, which prints
events whose topics match @nonterm{topic} only at the given
@nonterm{level} or higher (where a @nonterm{topic} contains any
character other than whitespace or @litchar["@"]). Leading and
trailing whitespace is ignored. For example, the value
@racket["error debug@GC"] prints all events at the
@racket['error] level and higher, but prints events for the
topic @racket['GC] at the @racket['debug] level and higher
(which includes all levels).
The default is @racket["error"].}
@item{If the @indexed-envvar{PLTSYSLOG} environment variable is
defined and is not overridden by a command-line flag, it
determines the level of the @tech{log receiver} that propagates
events to the system log. The possible values are the
same as for @envvar{PLTSTDERR}.
The default is @racket["none"] for Unix or @racket["error"] for
Windows and Mac OS.}
]
The @racket[current-logger] @tech{parameter} determines the
@deftech{current logger} that is used by forms such as
@racket[log-warning]. On start-up, the initial value of this parameter
is the initial logger. The run-time system sometimes uses the current
logger to report events. For example, the bytecode compiler sometimes
reports @racket['warning] events when it detects an expression that
would produce a run-time error if evaluated.
@history[#:changed "6.6.0.2" @elem{Prior to version 6.6.0.2, parsing
of @envvar{PLTSTDERR} and @envvar{PLTSYSLOG} was very strict.
Leading and trailing whitespace was forbidden, and anything other
than exactly one space character separating two specifications was
rejected.}]
@; ----------------------------------------
@section{Creating Loggers}
@defproc[(logger? [v any/c]) boolean?]{
Returns @racket[#t] if @racket[v] is a @tech{logger}, @racket[#f]
otherwise.}
@defproc[(make-logger [topic (or/c symbol? #f) #f]
[parent (or/c logger? #f) #f]
[propagate-level log-level/c 'debug]
[propagate-topic (or/c #f symbol?) #f]
... ...)
logger?]{
Creates a new @tech{logger} with an optional topic and parent.
The optional @racket[propagate-level] and @racket[propagate-topic]
arguments constrain the events that are propagated from the new logger
to @racket[parent] (when @racket[parent] is not @racket[#f]) in the
same way that events are described for a log receiver in
@racket[make-log-receiver]. By default, all events are propagated to
@racket[parent].
@history[#:changed "6.1.1.3" @elem{Removed an optional argument to
specify a notification callback,
and added @racket[propagate-level] and
@racket[propagate-topic] constraints for
events to propagate.}]}
@defproc[(logger-name [logger logger?]) (or/c symbol? #f)]{
Reports @racket[logger]'s default topic, if any.}
@defparam[current-logger logger logger?]{
A @tech{parameter} that determines the @tech{current logger}.}
@defform[(define-logger id)]{
Defines @racketkeywordfont{log-}@racket[id]@racketkeywordfont{-fatal},
@racketkeywordfont{log-}@racket[id]@racketkeywordfont{-error},
@racketkeywordfont{log-}@racket[id]@racketkeywordfont{-warning},
@racketkeywordfont{log-}@racket[id]@racketkeywordfont{-info}, and
@racketkeywordfont{log-}@racket[id]@racketkeywordfont{-debug} as forms
like @racket[log-fatal], @racket[log-error],@racket[log-warning],
@racket[log-info], and @racket[log-debug]. The @racket[define-logger]
form also defines @racket[id]@racketidfont{-logger}, which is a logger with
default topic @racket['@#,racket[id]] that is a child of @racket[(current-logger)];
the @racketkeywordfont{log-}@racket[id]@racketkeywordfont{-fatal},
@|etc| forms use this new logger. The new logger is
created when @racket[define-logger] is evaluated.}
@; ----------------------------------------
@section{Logging Events}
@defproc[(log-message [logger logger?]
[level log-level/c]
[topic (or/c symbol? #f) (logger-name logger)]
[message string?]
[data any/c]
[prefix-message? any/c #t])
void?]{
Reports an event to @racket[logger], which in turn distributes the
information to any @tech{log receivers} attached to @racket[logger] or
its ancestors that are interested in events at @racket[level] or
higher.
@tech{Log receivers} can filter events based on @racket[topic]. In
addition, if @racket[topic] and @racket[prefix-message?] are not
@racket[#f], then @racket[message] is prefixed with the topic followed
by @racket[": "] before it is sent to receivers.
@history[#:changed "6.0.1.10" @elem{Added the @racket[prefix-message?] argument.}]}
@defproc[(log-level? [logger logger?]
[level log-level/c]
[topic (or/c symbol? #f) #f])
boolean?]{
Reports whether any @tech{log receiver} attached to @racket[logger] or
one of its ancestors is interested in @racket[level] events (or
potentially lower) for @racket[topic]. If @racket[topic] is @racket[#f],
the result indicates whether a @tech{log receiver} is interested in
events at @racket[level] for any topic.
Use this function to avoid work generating an
event for @racket[log-message] if no receiver is interested in the
information; this shortcut is built into @racket[log-fatal],
@racket[log-error], @racket[log-warning], @racket[log-info],
@racket[log-debug], and forms bound by @racket[define-logger],
however, so it should not be used with those forms.
The result of this function can change if a garbage collection
determines that a log receiver is no longer accessible (and therefore
that any event information it receives will never become accessible).
@history[#:changed "6.1.1.3" @elem{Added the @racket[topic] argument.}]}
@defproc[(log-max-level [logger logger?]
[topic (or/c symbol? #f) #f])
(or/c log-level/c #f)]{
Similar to @racket[log-level?], but reports the maximum-detail level of logging for
which @racket[log-level?] on @racket[logger] and @racket[topic] returns @racket[#t]. The
result is @racket[#f] if @racket[log-level?] with @racket[logger] and @racket[topic]
currently returns @racket[#f] for all levels.
@history[#:changed "6.1.1.3" @elem{Added the @racket[topic] argument.}]}
@defproc[(log-all-levels [logger logger?])
(list/c (or/c #f log-level/c)
(or/c #f symbol?)
... ...)]{
Summarizes the possible results of @racket[log-max-level] on all
possible @tech{interned} symbols. The result list contains a sequence
of symbols and @racket[#f], where the first, third, etc., list element
corresponds to a level, and the second, fourth, etc., list element
indicates a corresponding topic. The level is the result that
@racket[log-max-level] would produce for the topic, where the level for
the @racket[#f] topic (which is always present in the result list)
indicates the result for any @tech{interned}-symbol topic that does not
appear in the list.
The result is suitable as a sequence of arguments to
@racket[make-log-receiver] (after a @tech{logger} argument) to create
a new receiver for events that currently have receivers in @racket[logger].
@history[#:added "6.1.1.4"]}
@defproc[(log-level-evt [logger logger?]) evt?]{
Creates a @tech{synchronizable event} that is @tech{ready for
synchronization} when the result of @racket[log-level?],
@racket[log-max-level], or @racket[log-all-levels] can be different
than before @racket[log-level-evt] was called. The event's
@tech{synchronization result} is the event itself.
The condition reported by the event is a conservative approximation:
the event can become @tech{ready for synchronization} even if the
results of @racket[log-level?], @racket[log-max-level], and
@racket[log-all-levels] are unchanged. Nevertheless, the expectation
is that events produced by @racket[log-level-evt] become ready infrequently,
because they are triggered by the creation of a log receiver.
@history[#:added "6.1.1.4"]}
@deftogether[(
@defform*[[(log-fatal string-expr)
(log-fatal format-string-expr v ...)]]
@defform*[[(log-error string-expr)
(log-error format-string-expr v ...)]]
@defform*[[(log-warning string-expr)
(log-warning format-string-expr v ...)]]
@defform*[[(log-info string-expr)
(log-info format-string-expr v ...)]]
@defform*[[(log-debug string-expr)
(log-debug format-string-expr v ...)]]
)]{
Log an event with the @tech{current logger}, evaluating
@racket[string-expr] or @racket[(format format-string-expr v ...)]
only if the logger has receivers that are interested in the event. In
addition, the current continuation's @tech{continuation marks} are
sent to the logger with the message string.
These form are convenient for using the current logger, but libraries
should generally use a logger for a specific topic---typically through
similar convenience forms generated by @racket[define-logger].
For each @racketkeywordfont{log-}@racket[_level],
@racketblock[
(@#,racketkeywordfont{log-}_level string-expr)
]
is equivalent to
@racketblock[
(let ([l (current-logger)])
(when (log-level? l '@#,racket[_level])
(log-message l '@#,racket[_level] string-expr
(current-continuation-marks))))
]
while
@racketblock[
(@#,racketkeywordfont{log-}_level format-string-expr v ...)
]
is equivalent to
@racketblock[
(@#,racketkeywordfont{log-}_level (format format-string-expr v ...))
]}
@; ----------------------------------------
@section[#:tag "receiving-logged-events"]{Receiving Logged Events}
@defproc[(log-receiver? [v any/c]) boolean?]{
Returns @racket[#t] if @racket[v] is a @tech{log receiver}, @racket[#f]
otherwise.}
@defproc[(make-log-receiver [logger logger?]
[level log-level/c]
[topic (or/c #f symbol?) #f]
... ...)
log-receiver?]{
Creates a @tech{log receiver} to receive events of detail
@racket[level] and lower as reported to @racket[logger] and its
descendants, as long as either @racket[topic] is @racket[#f] or the
event's topic matches @racket[topic].
A @tech{log receiver} is a @tech{synchronizable event}. It becomes
@tech{ready for synchronization} when a logging event is
received, so use @racket[sync] to receive a logged event. The
@tech{log receiver}'s @tech{synchronization result} is an immutable vector containing
four values: the level of the event as a symbol, an immutable string
for the event message, an arbitrary value that was supplied as the
last argument to @racket[log-message] when the event was logged, and a
symbol or @racket[#f] for the event topic.
Multiple pairs of @racket[level] and @racket[topic] can be provided to
indicate different specific @racket[level]s for different
@racket[topic]s (where @racket[topic] defaults to @racket[#f] only for
the last given @racket[level]). A @racket[level] for a @racket[#f]
@racket[topic] applies only to events whose topic does not match any other
provided @racket[topic]. If the same @racket[topic] is provided multiple
times, the @racket[level] provided with the last instance in the
argument list takes precedence.}
@; ----------------------------------------
@section{Additional Logging Functions}
@note-lib[racket/logging]
@(require (for-label racket/logging))
@(define log-eval (make-base-eval))
@examples[#:hidden #:eval log-eval
(require racket/logging)]
@defproc[(log-level/c [v any/c])
boolean?]{
Returns @racket[#t] if @racket[v] is a valid logging level (@racket['none],
@racket['fatal], @racket['error], @racket['warning], @racket['info], or
@racket['debug]), @racket[#f] otherwise.
@history[#:added "6.3"]{}
}
@defproc[(with-intercepted-logging
[interceptor (-> (vector/c
log-level/c
string?
any/c
(or/c symbol? #f))
any)]
[proc (-> any)]
[#:logger logger logger? #f]
[level log-level/c]
[topic (or/c #f symbol?) #f]
... ...)
any]{
Runs @racket[proc], calling @racket[interceptor] on any log event that the
execution of @racket[proc] emits to @racket[current-logger] at the specified
levels and topics.
If @racket[#:logger] is specified, intercepts events sent to that logger,
otherwise uses a new child logger of the current logger.
Returns whatever @racket[proc] returns.
@examples[
#:eval log-eval
(let ([warning-counter 0])
(with-intercepted-logging
(lambda (l)
(when (eq? (vector-ref l 0) ; actual level
'warning)
(set! warning-counter (add1 warning-counter))))
(lambda ()
(log-warning "Warning!")
(log-warning "Warning again!")
(+ 2 2))
'warning)
warning-counter)]
@history[#:added "6.3" #:changed "6.7.0.1" @elem{Added @racket[#:logger] argument.}]{}}
@defproc[(with-logging-to-port
[port output-port?] [proc (-> any)]
[#:logger logger logger? #f]
[level log-level/c]
[topic (or/c #f symbol?) #f]
... ...)
any]{
Runs @racket[proc], outputting any logging that the execution of @racket[proc]
emits to @racket[current-logger] at the specified levels and topics.
If @racket[#:logger] is specified, intercepts events sent to that logger,
otherwise uses a new child logger of the current logger.
Returns whatever @racket[proc] returns.
@examples[
#:eval log-eval
(let ([my-log (open-output-string)])
(with-logging-to-port my-log
(lambda ()
(log-warning "Warning World!")
(+ 2 2))
'warning)
(get-output-string my-log))]
@history[#:added "6.3" #:changed "6.7.0.1" @elem{Added @racket[#:logger] argument.}]{}}