Skip to content

Logging

Alexander Diemand edited this page Nov 1, 2019 · 3 revisions

Logging is the process of capturing observables and processing them, including eventual output to the console or in a file. Log files are managed by Log rotation that can be configured how many files to keep and how large they can become.
We distinguish between two levels of logging: first, the capturing of observables using the contra-tracer package, second the processing and routing of LogObject via the switchboard in package iohk-monitoring.

package contra-tracer

The simple package contra-tracer does not depend on further packages and thus is lightweight in integration into libraries. It defines the central type Tracer m a as a contravariant functor that can be passed to functions that need tracing of observables.

The more complex package is iohk-monitoring which does the processing and routing of traced objects. Its central type is Trace m a which is an alias for Tracer m (LogObject a) where the a stands for the type of textual messages, either Text or String. This is a tracer that passes around instances of LogObject that contains everything a log message requires: timestamp, severity level, named context, traced content.

1.) timestamp is taken when the LogObject is created
2.) severity level is on of Debug, Info, Notice, Warning, Error, .. (see Severity)
3.) named context defines where in the call graph this message has originated. We can enter a named context with the function appendName.
4.) the traced content is the payload the message carries. It is usually created from tracer transformers Tracer m (LogObject b) -> Tracer m a

tracer transformers

To convert from a traced object of type a to a LogObject, either an instance of ToJSON is available or an instance of ToObject a is required. These instances can be implemented as orphans in a quasi declarative style.
Furthermore, instances for DefineSeverity and DefinePrivacyAnnotation are required.

example for a data structure Pet:

data Pet = Pet { name :: Text, age :: Int}
           deriving (Show)

the ToObject instance returns a JSON Object of different complexity, depending on the chosen verbosity:

instance ToObject Pet where
    toObject MinimalVerbosity _ = emptyObject -- do not log
    toObject NormalVerbosity (Pet _ _) =
        mkObject [ "kind" .= String "Pet"]
    toObject MaximalVerbosity (Pet n a) =
        mkObject [ "kind" .= String "Pet"
                 , "name" .= toJSON n
                 , "age" .= toJSON a ]

the rendering of the representation of the data structure depends on a chosen formatting. The trStructured transformer will call the toObject function (see above):

instance Transformable Text IO Pet where
    -- transform to JSON Object
    trTransformer StructuredLogging verb tr = trStructured verb tr
    -- transform to textual representation using |show|
    trTransformer TextualRepresentation _v tr = Tracer $ \pet -> do
        meta <- mkLOMeta Info Public
        traceWith tr $ LogObject "pet" meta $ (LogMessage . pack . show) pet
    trTransformer _ _verb _tr = nullTracer -- do not log

annotations (severity level, privacy) are defined with the following instances:

-- default privacy annotation: Public
instance DefinePrivacyAnnotation Pet
-- default severity: Debug
instance DefineSeverity Pet

switchboard

The switchboard takes care of routing every traced object to its configured backend. It does so by looking up this information in the configuration for the context name of the LogObject.

backends

Several backends have been implemented, and user-defined ones can easily added.
(see list in https://github.com/input-output-hk/iohk-monitoring-framework/tree/master/iohk-monitoring/src/Cardano/BM/Backend)