-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Batteries-included Structured Logging library -- -- Please see README.md @package Blammo @version 1.0.2.2 module Blammo.Logging.Colors data Colors Colors :: (Text -> Text) -> (Text -> Text) -> (Text -> Text) -> (Text -> Text) -> (Text -> Text) -> (Text -> Text) -> (Text -> Text) -> (Text -> Text) -> (Text -> Text) -> (Text -> Text) -> Colors [gray] :: Colors -> Text -> Text [black] :: Colors -> Text -> Text [cyan] :: Colors -> Text -> Text [magenta] :: Colors -> Text -> Text [blue] :: Colors -> Text -> Text [yellow] :: Colors -> Text -> Text [green] :: Colors -> Text -> Text [red] :: Colors -> Text -> Text [bold] :: Colors -> Text -> Text [dim] :: Colors -> Text -> Text getColors :: Bool -> Colors -- | Backwards-compatible extension to a simple LogLevel -- parser/filter -- -- Assume you are using this library/module to parse a LOG_LEVEL -- environment variable, which is used to filter your logs. -- -- Running, -- --
-- LOG_LEVEL=warn ./my-program ---- -- Will do what you expect: filter all logging to only those messages -- at-or-above warn level. -- -- While, -- --
-- LOG_LEVEL=debug ./my-program ---- -- Will enable debug logging throughout. -- -- This is all un-surprising and this module does not change behavior in -- this case whatsoever. But let's say that is entirely too noisy. -- Because you're using Amazonka and persistent, and have correctly -- integrated your main logging with them, you are now getting -- tons of spam from their very-chatty debug logs, and its -- drowning out the application debug logs you were hoping to see. -- -- Well, now can do this: -- --
-- LOG_LEVEL="debug,Amazonka:info,SQL:warn" ./my-program ---- -- And suddenly your application's debug logs are standing out again, -- because everything from the Amazonka source is filtered to info and -- the SQL source is filtered to warn. -- -- The format parsed by readLogLevels is: -- --
-- [source:level, ...,]level[, source:level, ...] ---- -- Where level defines the minimum level for anything not -- overridden by source. If you go on to add any -- source:level pairs, that will change the minimum level -- for messages from that source. module Blammo.Logging.LogSettings.LogLevels data LogLevels data LogLevel LevelDebug :: LogLevel LevelInfo :: LogLevel LevelWarn :: LogLevel LevelError :: LogLevel LevelOther :: Text -> LogLevel newLogLevels :: LogLevel -> [(LogSource, LogLevel)] -> LogLevels readLogLevels :: String -> Either String LogLevels shouldLogLevel :: LogLevels -> LogSource -> LogLevel -> Bool defaultLogLevels :: LogLevels instance GHC.Show.Show Blammo.Logging.LogSettings.LogLevels.LogLevels instance GHC.Classes.Eq Blammo.Logging.LogSettings.LogLevels.LogLevels module Blammo.Logging.LogSettings data LogSettings data LogLevels data LogDestination LogDestinationStdout :: LogDestination LogDestinationStderr :: LogDestination LogDestinationFile :: FilePath -> LogDestination data LogFormat LogFormatJSON :: LogFormat LogFormatTerminal :: LogFormat data LogColor LogColorAuto :: LogColor LogColorAlways :: LogColor LogColorNever :: LogColor readLogLevels :: String -> Either String LogLevels readLogDestination :: String -> Either String LogDestination readLogFormat :: String -> Either String LogFormat readLogColor :: String -> Either String LogColor defaultLogSettings :: LogSettings setLogSettingsLevels :: LogLevels -> LogSettings -> LogSettings setLogSettingsDestination :: LogDestination -> LogSettings -> LogSettings setLogSettingsFormat :: LogFormat -> LogSettings -> LogSettings setLogSettingsColor :: LogColor -> LogSettings -> LogSettings getLogSettingsLevels :: LogSettings -> LogLevels getLogSettingsDestination :: LogSettings -> LogDestination getLogSettingsFormat :: LogSettings -> LogFormat getLogSettingsColor :: LogSettings -> LogColor shouldLogLevel :: LogSettings -> LogSource -> LogLevel -> Bool shouldColorAuto :: Applicative m => LogSettings -> m Bool -> m Bool shouldColorHandle :: MonadIO m => LogSettings -> Handle -> m Bool -- | Produce a LogSettings by reading environment variables -- --
-- import Blammo.Logging -- import qualified Logging.LogSettings.Env as Env -- -- main :: IO () -- main = do -- logger <- newLogger =<< Env.parse -- runLoggerLoggingT logger $ -- ... --module Blammo.Logging.LogSettings.Env parse :: IO LogSettings parser :: Parser Error LogSettings module Blammo.Logging.Test data LoggedMessages -- | This type is the Haskell representation of each JSON log message -- produced by this library. -- -- While we never interact with this type directly when logging messages -- with monad-logger-aeson, we may wish to use this type if we -- are parsing/processing log files generated by this library. data LoggedMessage LoggedMessage :: UTCTime -> LogLevel -> Maybe Loc -> Maybe LogSource -> KeyMap Value -> Text -> KeyMap Value -> LoggedMessage [loggedMessageTimestamp] :: LoggedMessage -> UTCTime [loggedMessageLevel] :: LoggedMessage -> LogLevel [loggedMessageLoc] :: LoggedMessage -> Maybe Loc [loggedMessageLogSource] :: LoggedMessage -> Maybe LogSource [loggedMessageThreadContext] :: LoggedMessage -> KeyMap Value [loggedMessageText] :: LoggedMessage -> Text [loggedMessageMeta] :: LoggedMessage -> KeyMap Value newLoggedMessages :: MonadIO m => m LoggedMessages appendLogStr :: MonadIO m => LoggedMessages -> LogStr -> m () getLoggedMessages :: MonadIO m => LoggedMessages -> m [Either String LoggedMessage] module Data.Aeson.Compat data Key fromText :: Text -> Key toText :: Key -> Text -- | A map from JSON key type Key to v. data KeyMap v -- | Construct an empty map. empty :: KeyMap v -- | Is the map empty? null :: KeyMap v -> Bool -- | Construct a map with a single element. singleton :: Key -> v -> KeyMap v -- | Construct a map with the supplied mappings. If the list contains -- duplicate mappings, the later mappings take precedence. fromList :: [(Key, v)] -> KeyMap v -- | Return a list of this map's keys and elements. -- -- The order is not stable. Use toAscList for stable ordering. toList :: KeyMap v -> [(Key, v)] -- | Colorful logging for humans -- -- Lines are formatted as -- --
-- {timestamp} [{level}] {message} {details}
--
--
-- level is padded to 9 characters and message is
-- padded to 31. This means things will align as long as values are
-- shorter than that. Longer values will overflow (not be truncated).
--
-- This format was designed to match Python's structlog package in
-- its default configuration.
module Blammo.Logging.Terminal
reformatTerminal :: Bool -> LogLevel -> ByteString -> ByteString
module Blammo.Logging.Logger
data Logger
class HasLogger env
loggerL :: HasLogger env => Lens' env Logger
newLogger :: MonadIO m => LogSettings -> m Logger
getLoggerReformat :: Logger -> LogLevel -> ByteString -> ByteString
getLoggerShouldLog :: Logger -> LogSource -> LogLevel -> Bool
pushLogStrLn :: MonadIO m => Logger -> LogStr -> m ()
flushLogStr :: MonadIO m => Logger -> m ()
-- | Create a Logger that will capture log messages instead of
-- logging them
--
-- See Blammo.Logging.LoggedMessages for more details.
newTestLogger :: MonadIO m => LogSettings -> m Logger
-- | This type is the Haskell representation of each JSON log message
-- produced by this library.
--
-- While we never interact with this type directly when logging messages
-- with monad-logger-aeson, we may wish to use this type if we
-- are parsing/processing log files generated by this library.
data LoggedMessage
LoggedMessage :: UTCTime -> LogLevel -> Maybe Loc -> Maybe LogSource -> KeyMap Value -> Text -> KeyMap Value -> LoggedMessage
[loggedMessageTimestamp] :: LoggedMessage -> UTCTime
[loggedMessageLevel] :: LoggedMessage -> LogLevel
[loggedMessageLoc] :: LoggedMessage -> Maybe Loc
[loggedMessageLogSource] :: LoggedMessage -> Maybe LogSource
[loggedMessageThreadContext] :: LoggedMessage -> KeyMap Value
[loggedMessageText] :: LoggedMessage -> Text
[loggedMessageMeta] :: LoggedMessage -> KeyMap Value
-- | Return the logged messages if newTestLogger was used
--
-- If not, the empty list is returned.
getLoggedMessages :: (MonadIO m, MonadReader env m, HasLogger env) => m [Either String LoggedMessage]
-- | getLoggedMessages but ignore any messages that fail to parse
getLoggedMessagesLenient :: (MonadIO m, MonadReader env m, HasLogger env) => m [LoggedMessage]
-- | getLoggedMessages but throwString if any messages failed
-- to parse
getLoggedMessagesUnsafe :: (HasCallStack, MonadIO m, MonadReader env m, HasLogger env) => m [LoggedMessage]
-- | Deprecated: Internal function, will be removed in a future
-- version
getLoggerLoggerSet :: Logger -> LoggerSet
instance Blammo.Logging.Logger.HasLogger Blammo.Logging.Logger.Logger
module Blammo.Logging
data LogSettings
data LogLevel
LevelDebug :: LogLevel
LevelInfo :: LogLevel
LevelWarn :: LogLevel
LevelError :: LogLevel
LevelOther :: Text -> LogLevel
data LogDestination
LogDestinationStdout :: LogDestination
LogDestinationStderr :: LogDestination
LogDestinationFile :: FilePath -> LogDestination
data LogFormat
LogFormatJSON :: LogFormat
LogFormatTerminal :: LogFormat
data LogColor
LogColorAuto :: LogColor
LogColorAlways :: LogColor
LogColorNever :: LogColor
defaultLogSettings :: LogSettings
setLogSettingsLevels :: LogLevels -> LogSettings -> LogSettings
setLogSettingsDestination :: LogDestination -> LogSettings -> LogSettings
setLogSettingsFormat :: LogFormat -> LogSettings -> LogSettings
setLogSettingsColor :: LogColor -> LogSettings -> LogSettings
data Logger
class HasLogger env
loggerL :: HasLogger env => Lens' env Logger
newLogger :: MonadIO m => LogSettings -> m Logger
runLoggerLoggingT :: (MonadIO m, HasLogger env) => env -> LoggingT m a -> m a
-- | A Message captures a textual component and a metadata
-- component. The metadata component is a list of SeriesElem to
-- support tacking on arbitrary structured data to a log message.
--
-- With the OverloadedStrings extension enabled, Message
-- values can be constructed without metadata fairly conveniently, just
-- as if we were using Text directly:
--
-- -- logDebug "Some log message without metadata" ---- -- Metadata may be included in a Message via the :# -- constructor: -- --
-- logDebug $ "Some log message with metadata" :#
-- [ "bloorp" .= (42 :: Int)
-- , "bonk" .= ("abc" :: Text)
-- ]
--
--
-- The mnemonic for the :# constructor is that the #
-- symbol is sometimes referred to as a hash, a JSON object can be
-- thought of as a hash map, and so with :# (and enough
-- squinting), we are cons-ing a textual message onto a JSON
-- object. Yes, this mnemonic isn't well-typed, but hopefully it still
-- helps!
data Message
(:#) :: Text -> [SeriesElem] -> Message
infixr 5 :#
(.=) :: (KeyValue kv, ToJSON v) => Key -> v -> kv
infixr 8 .=
-- | A series of values that, when encoded, should be separated by commas.
-- Since 0.11.0.0, the .= operator is overloaded to create
-- either (Text, Value) or Series. You can use Series
-- when encoding directly to a bytestring builder as in the following
-- example:
--
--
-- toEncoding (Person name age) = pairs ("name" .= name <> "age" .= age)
--
data Series
-- | A class for monads which provide for the ability to account for all
-- possible exit points from a computation, and to mask asynchronous
-- exceptions. Continuation-based monads are invalid instances of this
-- class.
--
-- Instances should ensure that, in the following code:
--
-- -- fg = f `finally` g ---- -- The action g is called regardless of what occurs within -- f, including async exceptions. Some monads allow f -- to abort the computation via other effects than throwing an exception. -- For simplicity, we will consider aborting and throwing an exception to -- be two forms of "throwing an error". -- -- If f and g both throw an error, the error thrown by -- fg depends on which errors we're talking about. In a monad -- transformer stack, the deeper layers override the effects of the inner -- layers; for example, ExceptT e1 (Except e2) a represents a -- value of type Either e2 (Either e1 a), so throwing both an -- e1 and an e2 will result in Left e2. If -- f and g both throw an error from the same layer, -- instances should ensure that the error from g wins. -- -- Effects other than throwing an error are also overriden by the deeper -- layers. For example, StateT s Maybe a represents a value of -- type s -> Maybe (a, s), so if an error thrown from -- f causes this function to return Nothing, any -- changes to the state which f also performed will be erased. -- As a result, g will see the state as it was before -- f. Once g completes, f's error will be -- rethrown, so g' state changes will be erased as well. This is -- the normal interaction between effects in a monad transformer stack. -- -- By contrast, lifted-base's version of finally always -- discards all of g's non-IO effects, and g never sees -- any of f's non-IO effects, regardless of the layer ordering -- and regardless of whether f throws an error. This is not the -- result of interacting effects, but a consequence of -- MonadBaseControl's approach. class MonadCatch m => MonadMask (m :: Type -> Type) -- | This function lets us register structured, contextual info for the -- duration of the provided action. All messages logged within the -- provided action will automatically include this contextual info. This -- function is thread-safe, as the contextual info is scoped to the -- calling thread only. -- -- This function is additive: if we nest calls to it, each nested call -- will add to the existing thread context. In the case of overlapping -- keys, the nested call's Pair value(s) will win. Whenever the -- inner action completes, the thread context is rolled back to its value -- set in the enclosing action. -- -- If we wish to include the existing thread context from one thread in -- another thread, we must register the thread context explicitly on that -- other thread. myThreadContext can be leveraged in this case. -- -- Registering thread context for messages can be useful in many -- scenarios. One particularly apt scenario is in wai -- middlewares. We can generate an ID for each incoming request then -- include it in the thread context. Now all messages subsequently logged -- from our endpoint handler will automatically include that request ID: -- --
-- import Control.Monad.Logger.Aeson ((.=), withThreadContext) -- import Network.Wai (Middleware) -- import qualified Data.UUID.V4 as UUID -- -- addRequestId :: Middleware -- addRequestId app = \request sendResponse -> do -- uuid <- UUID.nextRandom -- withThreadContext ["requestId" .= uuid] do -- app request sendResponse ---- -- If we're coming from a Java background, it may be helpful for us to -- draw parallels between this function and log4j2's -- ThreadContext (or perhaps log4j's MDC). -- They all enable the same thing: setting some thread-local info that -- will be automatically pulled into each logged message. withThreadContext :: (MonadIO m, MonadMask m) => [Pair] -> m a -> m a -- | This function lets us retrieve the calling thread's thread context. -- For more detail, we can consult the docs for withThreadContext. -- -- Note that even though the type signature lists MonadThrow as a -- required constraint, the library guarantees that -- myThreadContext will never throw. myThreadContext :: (MonadIO m, MonadThrow m) => m (KeyMap Value) -- | A key/value pair for an Object. type Pair = (Key, Value) -- | A Monad which has the ability to log messages in some manner. class Monad m => MonadLogger (m :: Type -> Type) monadLoggerLog :: (MonadLogger m, ToLogStr msg) => Loc -> LogSource -> LogLevel -> msg -> m () -- | An extension of MonadLogger for the common case where the -- logging action is a simple IO action. The advantage of using -- this typeclass is that the logging function itself can be extracted as -- a first-class value, which can make it easier to manipulate monad -- transformer stacks, as an example. class (MonadLogger m, MonadIO m) => MonadLoggerIO (m :: Type -> Type) -- | Request the logging function itself. askLoggerIO :: MonadLoggerIO m => m (Loc -> LogSource -> LogLevel -> LogStr -> IO ()) -- | Monad transformer that adds a new logging function. data LoggingT (m :: Type -> Type) a -- | Logs a Message with the location provided by an implicit -- CallStack. logDebug :: (HasCallStack, MonadLogger m) => Message -> m () -- | See logDebug logInfo :: (HasCallStack, MonadLogger m) => Message -> m () -- | See logDebug logWarn :: (HasCallStack, MonadLogger m) => Message -> m () -- | See logDebug logError :: (HasCallStack, MonadLogger m) => Message -> m () -- | See logDebug logOther :: (HasCallStack, MonadLogger m) => LogLevel -> Message -> m () type LogSource = Text -- | See logDebugCS logDebugNS :: (HasCallStack, MonadLogger m) => LogSource -> Message -> m () -- | See logDebugNS logInfoNS :: (HasCallStack, MonadLogger m) => LogSource -> Message -> m () -- | See logDebugNS logWarnNS :: (HasCallStack, MonadLogger m) => LogSource -> Message -> m () -- | See logDebugNS logErrorNS :: (HasCallStack, MonadLogger m) => LogSource -> Message -> m () -- | See logDebugNS logOtherNS :: (HasCallStack, MonadLogger m) => LogSource -> LogLevel -> Message -> m () -- | Simplified out-of-the-box logging module Blammo.Logging.Simple -- | Construct a Logger configured via environment variables newLoggerEnv :: MonadIO m => m Logger -- | Construct a Logger configured via environment variables and use -- it runSimpleLoggingT :: MonadIO m => LoggingT m a -> m a module Network.Wai.Middleware.Logging -- | Add context to any logging done from the request-handling thread addThreadContext :: [Pair] -> Middleware -- | addThreadContext, but have the Request available addThreadContextFromRequest :: (Request -> [Pair]) -> Middleware -- | Log requests (more accurately, responses) as they happen -- -- In JSON format, logged messages look like: -- --
-- {
-- ...
-- message: {
-- text: "GET foobar => 200 OK",
-- meta: {
-- method: GET,
-- path: "foobar",
-- query: "?baz=bat&quix=quo",
-- status: {
-- code: 200,
-- message: OK
-- },
-- durationMs: 1322.2,
-- requestHeaders: {
-- Authorization: "***",
-- Accept: "text/html",
-- Cookie: "***"
-- },
-- responseHeaders: {
-- Set-Cookie: "***",
-- Expires: "never"
-- }
-- }
-- }
-- }
--
requestLogger :: HasLogger env => env -> Middleware
requestLoggerWith :: HasLogger env => Config -> env -> Middleware
data Config
defaultConfig :: Config
-- | Change the source used for log messages
--
-- Default is requestLogger.
setConfigLogSource :: LogSource -> Config -> Config
-- | Change how the destinationIp field is determined
--
-- Default is looking up the x-real-ip header.
setConfigGetDestinationIp :: (Request -> Maybe Text) -> Config -> Config