While logging is
necessary it has a significantly negative impact on performance and quickly
loses its usefulness if it's not kept reasonably terse. The logging facilities
provides five different levels of logging. Below are the different levels and
when and how they should be used.
·
ERROR: This level of logging should be used when
something fatal has happened, i.e. something that will have user-visible
consequences and won't be recoverable without explicitly deleting some data,
uninstalling applications, wiping the data partitions or reflashing the entire
phone (or worse). This level is always logged. Issues that justify some logging
at the ERROR level are typically good candidates to be reported to a
statistics-gathering server.
·
WARNING: This level of logging should used when
something serious and unexpected happened, i.e. something that will have
user-visible consequences but is likely to be recoverable without data loss by
performing some explicit action, ranging from waiting or restarting an app all
the way to re-downloading a new version of an application or rebooting the
device. This level is always logged. Issues that justify some logging at the
WARNING level might also be considered for reporting to a statistics-gathering
server.
·
INFORMATIVE: This level of logging should used
be to note that something interesting to most people happened, i.e. when a
situation is detected that is likely to have widespread impact, though isn't
necessarily an error. Such a condition should only be logged by a module that
reasonably believes that it is the most authoritative in that domain (to avoid
duplicate logging by non-authoritative components). This level is always
logged.
·
DEBUG: This level of logging should be used to
further note what is happening on the device that could be relevant to
investigate and debug unexpected behaviors. You should log only what is needed
to gather enough information about what is going on about your component. If
your debug logs are dominating the log then you probably should be using
verbose logging.
This level will be logged, even on release builds, and is
required to be surrounded by an if (LOCAL_LOG) or if (LOCAL_LOGD) block, where
LOCAL_LOG[D] is defined in your class or subcomponent, so that there can exist
a possibility to disable all such logging. There must therefore be no active
logic in an if (LOCAL_LOG) block. All the string building for the log also
needs to be placed inside the if (LOCAL_LOG) block. The logging call should not
be re-factored out into a method call if it is going to cause the string
building to take place outside of the if (LOCAL_LOG) block.
There is some code that still says if (localLOGV). This is
considered acceptable as well, although the name is nonstandard.
·
VERBOSE: This level of logging should be used
for everything else. This level will only be logged on debug builds and should
be surrounded by an if (LOCAL_LOGV) block (or equivalent) so that it can be
compiled out by default. Any string building will be stripped out of release
builds and needs to appear inside the if (LOCAL_LOGV) block.
Notes:
·
Within a given module, other than at the VERBOSE
level, an error should only be reported once if possible: within a single chain
of function calls within a module, only the innermost function should return
the error, and callers in the same module should only add some logging if that
significantly helps to isolate the issue.
·
In a chain of modules, other than at the VERBOSE
level, when a lower-level module detects invalid data coming from a
higher-level module, the lower-level module should only log this situation to
the DEBUG log, and only if logging provides information that is not otherwise
available to the caller. Specifically, there is no need to log situations where
an exception is thrown (the exception should contain all the relevant
information), or where the only information being logged is contained in an
error code. This is especially important in the interaction between the
framework and applications, and conditions caused by third-party applications
that are properly handled by the framework should not trigger logging higher
than the DEBUG level. The only situations that should trigger logging at the
INFORMATIVE level or higher is when a module or application detects an error at
its own level or coming from a lower level.
·
When a condition that would normally justify
some logging is likely to occur many times, it can be a good idea to implement
some rate-limiting mechanism to prevent overflowing the logs with many
duplicate copies of the same (or very similar) information.
·
Losses of network connectivity are considered
common and fully expected and should not be logged gratuitously. A loss of
network connectivity that has consequences within an app should be logged at
the DEBUG or VERBOSE level (depending on whether the consequences are serious
enough and unexpected enough to be logged in a release build).
·
A full filesystem on a filesystem that is
acceessible to or on behalf of third-party applications should not be logged at
a level higher than INFORMATIVE.
·
Invalid data coming from any untrusted source
(including any file on shared storage, or data coming through just about any
network connections) is considered expected and should not trigger any logging
at a level higher then DEBUG when it's detected to be invalid (and even then
logging should be as limited as possible).
·
Keep in mind that the + operator, when used on
Strings, implicitly creates a StringBuilder with the default buffer size (16
characters) and potentially quite a few other temporary String objects, i.e.
that explicitly creating StringBuilders isn't more expensive than relying on
the default '+' operator (and can be a lot more efficient in fact). Also keep
in mind that code that calls Log.v() is compiled and executed on release
builds, including building the strings, even if the logs aren't being read.
·
Any logging that is meant to be read by other
people and to be available in release builds should be terse without being
cryptic, and should be reasonably understandable. This includes all logging up
to the DEBUG level.
·
When possible, logging should be kept on a
single line if it makes sense. Line lengths up to 80 or 100 characters are
perfectly acceptable, while lengths longer than about 130 or 160 characters
(including the length of the tag) should be avoided if possible.
·
Logging that reports successes should never be
used at levels higher than VERBOSE.
·
Temporary logging that is used to diagnose an
issue that's hard to reproduce should be kept at the DEBUG or VERBOSE level,
and should be enclosed by if blocks that allow to disable it entirely at
compile-time.
·
Be careful about security leaks through the log.
Private information should be avoided. Information about protected content must
definitely be avoided. This is especially important when writing framework code
as it's not easy to know in advance what will and will not be private information
or protected content.
System.out.println() (or printf() for native code) should
never be used. System.out and System.err get redirected to /dev/null, so your
print statements will have no visible effects. However, all the string building
that happens for these calls still gets executed.
The golden rule of logging is that your logs may
not unnecessarily push other logs out of the buffer, just as others may not
push out yours.
No comments:
Post a Comment