diff --git a/Blammo/Blammo.cabal b/Blammo/Blammo.cabal index e643d86..9327b31 100644 --- a/Blammo/Blammo.cabal +++ b/Blammo/Blammo.cabal @@ -26,8 +26,6 @@ source-repository head library exposed-modules: Blammo.Logging - Blammo.Logging.Colors - Blammo.Logging.Internal.Colors Blammo.Logging.Internal.Logger Blammo.Logging.Logger Blammo.Logging.LogSettings @@ -36,7 +34,7 @@ library Blammo.Logging.Setup Blammo.Logging.Simple Blammo.Logging.Terminal - Blammo.Logging.Terminal.LogPiece + Blammo.Logging.Terminal.Doc Blammo.Logging.Test Blammo.Logging.ThreadContext Blammo.Logging.WithLogger @@ -67,6 +65,8 @@ library , lens , monad-logger-aeson , mtl + , prettyprinter >=1.7.0 + , prettyprinter-ansi-terminal , text , time , unliftio @@ -136,7 +136,6 @@ test-suite spec Blammo , aeson , base <5 - , bytestring , envparse , hspec , mtl diff --git a/Blammo/package.yaml b/Blammo/package.yaml index 5aa45f9..c7059d8 100644 --- a/Blammo/package.yaml +++ b/Blammo/package.yaml @@ -57,6 +57,8 @@ library: - lens - monad-logger-aeson - mtl + - prettyprinter >= 1.7.0 # introduces Prettyprinter module tree + - prettyprinter-ansi-terminal - text - time - vector @@ -74,7 +76,6 @@ tests: - Blammo - aeson - envparse - - bytestring - hspec - mtl - text diff --git a/Blammo/src/Blammo/Logging/Colors.hs b/Blammo/src/Blammo/Logging/Colors.hs deleted file mode 100644 index 912c6b1..0000000 --- a/Blammo/src/Blammo/Logging/Colors.hs +++ /dev/null @@ -1,62 +0,0 @@ --- | Generic facilities for adding terminal escapes to 'Text' --- --- Recommended usage: --- --- @ --- Colors {..} <- 'getColorsLogger' -- for example --- pure $ "This text will be " <> red "red" <> "." --- @ -module Blammo.Logging.Colors - ( Colors (..) - , noColors - , getColors - , getColorsLogger - , getColorsHandle - , getColorsStdout - , getColorsStderr - ) where - -import Prelude - -import Blammo.Logging.Internal.Colors -import Blammo.Logging.Internal.Logger -import Blammo.Logging.LogSettings (adjustColors, shouldColorHandle) -import Control.Lens (to, view) -import Control.Monad.IO.Class (MonadIO (..)) -import Control.Monad.Reader (MonadReader) -import System.IO (Handle, stderr, stdout) - --- | Return 'Colors' consistent with whatever your logging is doing -getColorsLogger :: (MonadReader env m, HasLogger env) => m Colors -getColorsLogger = do - f <- view $ loggerL . to (adjustColors . lLogSettings) - view $ loggerL . to (f . getColors . lShouldColor) - --- | Return 'Colors' consistent with logging, but for 'Handle' --- --- This is useful if you are building text to print to a handle that is not the --- one you are logging to. --- --- For example, say you are using, --- --- @ --- LOG_COLOR=auto --- LOG_DESTINATION=@some-file.log --- @ --- --- That will not log with color, so 'getColorsLogger' will be 'noColor'. If --- you're building other text to be printed out, you probably want to respect --- that @LOG_COLOR=auto@, so you would use this function instead. -getColorsHandle - :: (MonadIO m, MonadReader env m, HasLogger env) => Handle -> m Colors -getColorsHandle h = do - ls <- view $ loggerL . to lLogSettings - adjustColors ls . getColors <$> shouldColorHandle ls h - --- | Short-cut for @'getColorsHandle' 'stdout'@ -getColorsStdout :: (MonadIO m, MonadReader env m, HasLogger env) => m Colors -getColorsStdout = getColorsHandle stdout - --- | Short-cut for @'getColorsHandle' 'stderr'@ -getColorsStderr :: (MonadIO m, MonadReader env m, HasLogger env) => m Colors -getColorsStderr = getColorsHandle stderr diff --git a/Blammo/src/Blammo/Logging/Internal/Colors.hs b/Blammo/src/Blammo/Logging/Internal/Colors.hs deleted file mode 100644 index 057695b..0000000 --- a/Blammo/src/Blammo/Logging/Internal/Colors.hs +++ /dev/null @@ -1,66 +0,0 @@ -module Blammo.Logging.Internal.Colors - ( Colors (..) - , colors - , noColors - , getColors - ) where - -import Prelude - -import Data.Text (Text) - -data Colors = Colors - { gray :: Text -> Text - , black :: Text -> Text - , cyan :: Text -> Text - , magenta :: Text -> Text - , blue :: Text -> Text - , yellow :: Text -> Text - , green :: Text -> Text - , red :: Text -> Text - , bold :: Text -> Text - , dim :: Text -> Text - } - -colors :: Colors -colors = - Colors - { gray = esc "0;37" - , cyan = esc "0;36" - , magenta = esc "0;35" - , blue = esc "0;34" - , yellow = esc "0;33" - , green = esc "0;32" - , red = esc "0;31" - , black = esc "0;30" - , bold = esc "1" - , dim = esc "2" - } - where - esc :: Text -> Text -> Text - esc code x = "\ESC[" <> code <> "m" <> x <> "\ESC[0m" - -noColors :: Colors -noColors = - Colors - { gray = id - , black = id - , cyan = id - , magenta = id - , blue = id - , yellow = id - , green = id - , red = id - , bold = id - , dim = id - } - --- | Return colorful 'Colors' if given 'True' --- --- __NOTE__: Direct use of this function is discouraged. It does not apply any --- color modifications done through 'LogSettings'. Use one of the @get@ --- functions in "Blammo.Logging.Colors" instead, which do. -getColors :: Bool -> Colors -getColors = \case - True -> colors - False -> noColors diff --git a/Blammo/src/Blammo/Logging/LogSettings.hs b/Blammo/src/Blammo/Logging/LogSettings.hs index ac5e846..240dfcc 100644 --- a/Blammo/src/Blammo/Logging/LogSettings.hs +++ b/Blammo/src/Blammo/Logging/LogSettings.hs @@ -40,9 +40,9 @@ module Blammo.Logging.LogSettings import Prelude -import Blammo.Logging.Internal.Colors (Colors) import Blammo.Logging.LogSettings.LogLevels (LogLevels) import qualified Blammo.Logging.LogSettings.LogLevels as LogLevels +import Blammo.Logging.Terminal.Doc import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.Logger.Aeson import System.IO (Handle, hIsTerminalDevice) @@ -53,7 +53,7 @@ data LogSettings = LogSettings , lsDestination :: LogDestination , lsFormat :: LogFormat , lsColor :: LogColor - , lsColors :: Colors -> Colors + , lsColors :: Ann -> AnsiStyle , lsBreakpoint :: Int , lsConcurrency :: Maybe Int } @@ -121,7 +121,7 @@ defaultLogSettings = , lsDestination = LogDestinationStdout , lsFormat = LogFormatTerminal , lsColor = LogColorAuto - , lsColors = id + , lsColors = annToAnsi , lsBreakpoint = 120 , lsConcurrency = Just 1 } @@ -173,8 +173,8 @@ setLogSettingsBreakpoint x ls = ls {lsBreakpoint = x} setLogSettingsConcurrency :: Maybe Int -> LogSettings -> LogSettings setLogSettingsConcurrency x ls = ls {lsConcurrency = x} --- | Set a function to modify 'Colors' used in logging -setLogSettingsColors :: (Colors -> Colors) -> LogSettings -> LogSettings +-- | Set a function to define ANSI colors used in terminal logging +setLogSettingsColors :: (Ann -> AnsiStyle) -> LogSettings -> LogSettings setLogSettingsColors f ls = ls {lsColors = f} getLogSettingsLevels :: LogSettings -> LogLevels @@ -195,7 +195,7 @@ getLogSettingsBreakpoint = lsBreakpoint getLogSettingsConcurrency :: LogSettings -> Maybe Int getLogSettingsConcurrency = lsConcurrency -adjustColors :: LogSettings -> Colors -> Colors +adjustColors :: LogSettings -> Ann -> AnsiStyle adjustColors = lsColors shouldLogLevel :: LogSettings -> LogSource -> LogLevel -> Bool diff --git a/Blammo/src/Blammo/Logging/LogSettings/Env.hs b/Blammo/src/Blammo/Logging/LogSettings/Env.hs index 1adb509..1fa1dd9 100644 --- a/Blammo/src/Blammo/Logging/LogSettings/Env.hs +++ b/Blammo/src/Blammo/Logging/LogSettings/Env.hs @@ -57,8 +57,8 @@ module Blammo.Logging.LogSettings.Env import Prelude -import Blammo.Logging.Colors (Colors (..)) import Blammo.Logging.LogSettings +import Blammo.Logging.Terminal.Doc import Data.Bifunctor (first) import Data.Bool (bool) import Data.Semigroup (Endo (..)) @@ -88,7 +88,7 @@ parserWith defaults = , endoVar readLogFormat setLogSettingsFormat "LOG_FORMAT" , endoSwitch (setLogSettingsColor LogColorNever) "NO_COLOR" , endoOn "dumb" (setLogSettingsColor LogColorNever) "TERM" - , endoOn "true" (setLogSettingsColors fixGitHubActions) "GITHUB_ACTIONS" + , endoOn "true" (setLogSettingsColors annToAnsiGHA) "GITHUB_ACTIONS" ] endoVar @@ -124,11 +124,12 @@ endoWhen f = bool mempty (Endo f) -- | -- --- GitHub Actions doesn't support 'dim' (such content just appears white). But --- if you use 'gray', it looks like 'dim' should. But one shouldn't just use --- 'gray' all the time because that won't look right /not/ in GitHub Actions. +-- GitHub Actions doesn't support 'faint' (such content just appears white). But +-- if you use gray (@'colorDull' 'White'@), it looks like 'faint' should. But +-- one shouldn't just use gray all the time because that won't look right /not/ +-- in GitHub Actions. -- --- We can help by automatically substituting 'gray' for 'dim', only in the +-- We can help by automatically substituting gray for 'faint', only in the -- GitHub Actions environment. We take on this extra complexity because: -- -- 1. It's trivial and zero dependency @@ -136,5 +137,13 @@ endoWhen f = bool mempty (Endo f) -- 3. GitHub Actions is a very common logging environment, and -- 4. I suspect we'll encounter more cases where GitHub Actions can be improved -- though such means, increasing its usefulness -fixGitHubActions :: Colors -> Colors -fixGitHubActions colors = colors {dim = gray colors} +-- +-- __NOTE__: for now, you can ignore all that. @prettyprinter-ansi-terminal@ +-- doesn't actually support 'faint' yet: +-- +-- +-- +-- So our normal 'annToAnsi' is already using gray always (and it just looks +-- bad) and this function uses it as-is for now. +annToAnsiGHA :: Ann -> AnsiStyle +annToAnsiGHA = annToAnsi diff --git a/Blammo/src/Blammo/Logging/Logger.hs b/Blammo/src/Blammo/Logging/Logger.hs index 63bd7d1..615e7e0 100644 --- a/Blammo/src/Blammo/Logging/Logger.hs +++ b/Blammo/src/Blammo/Logging/Logger.hs @@ -25,10 +25,11 @@ module Blammo.Logging.Logger import Prelude -import Blammo.Logging.Colors (Colors, getColors) import Blammo.Logging.Internal.Logger import Blammo.Logging.LogSettings import Blammo.Logging.Terminal +import Blammo.Logging.Terminal.Doc (Ann, Doc, RenderSettings (..)) +import qualified Blammo.Logging.Terminal.Doc as Doc import Blammo.Logging.Test hiding (getLoggedMessages) import qualified Blammo.Logging.Test as LoggedMessages import Control.Lens (view) @@ -42,6 +43,7 @@ import Data.Either (partitionEithers, rights) import Data.List (intercalate) import Data.Maybe (fromMaybe) import Data.Text (Text) +import Data.Text.Encoding (encodeUtf8) import GHC.Stack (HasCallStack) import System.IO (stderr, stdout) import System.Log.FastLogger (LoggerSet, defaultBufSize) @@ -94,18 +96,14 @@ getLoggerReformat :: Logger -> LogLevel -> ByteString -> ByteString getLoggerReformat = lReformat setLoggerReformat - :: (LogSettings -> Colors -> LogLevel -> LoggedMessage -> ByteString) + :: (LogLevel -> LoggedMessage -> Doc Ann) -> Logger -> Logger setLoggerReformat f logger = logger { lReformat = \level bytes -> fromMaybe bytes $ do lm <- Aeson.decodeStrict bytes - let colors = - adjustColors (lLogSettings logger) - $ getColors - $ lShouldColor logger - pure $ f (lLogSettings logger) colors level lm + pure $ encodeUtf8 $ renderDoc logger $ f level lm } getLoggerShouldLog :: Logger -> LogSource -> LogLevel -> Bool @@ -173,15 +171,15 @@ flushLogger = do logger <- view loggerL flushLogStr logger -pushLogger :: (MonadIO m, MonadReader env m, HasLogger env) => Text -> m () -pushLogger msg = do +pushLogger :: (MonadIO m, MonadReader env m, HasLogger env) => Doc Ann -> m () +pushLogger doc = do logger <- view loggerL - pushLogStr logger $ toLogStr msg + pushLogStr logger $ toLogStr $ renderDoc logger doc -pushLoggerLn :: (MonadIO m, MonadReader env m, HasLogger env) => Text -> m () -pushLoggerLn msg = do +pushLoggerLn :: (MonadIO m, MonadReader env m, HasLogger env) => Doc Ann -> m () +pushLoggerLn doc = do logger <- view loggerL - pushLogStrLn logger $ toLogStr msg + pushLogStrLn logger $ toLogStr $ renderDoc logger doc -- | Create a 'Logger' that will capture log messages instead of logging them -- @@ -222,3 +220,16 @@ getLoggedMessagesUnsafe = do $ "Messages were logged that didn't parse as LoggedMessage:" : failed ) + +renderDoc :: Logger -> Doc Ann -> Text +renderDoc = Doc.renderDoc . loggerRenderSettings + +loggerRenderSettings :: Logger -> RenderSettings +loggerRenderSettings logger = + RenderSettings + { rsUseColor = getLoggerShouldColor logger + , rsPageWidth = getLogSettingsBreakpoint settings + , rsAnnToAnsi = adjustColors settings + } + where + settings = getLoggerLogSettings logger diff --git a/Blammo/src/Blammo/Logging/Terminal.hs b/Blammo/src/Blammo/Logging/Terminal.hs index 298979e..fdbeb1e 100644 --- a/Blammo/src/Blammo/Logging/Terminal.hs +++ b/Blammo/src/Blammo/Logging/Terminal.hs @@ -19,99 +19,85 @@ module Blammo.Logging.Terminal import Prelude -import Blammo.Logging.Colors -import Blammo.Logging.LogSettings (LogSettings, getLogSettingsBreakpoint) -import Blammo.Logging.Terminal.LogPiece (LogPiece, logPiece) -import qualified Blammo.Logging.Terminal.LogPiece as LogPiece +import Blammo.Logging.Terminal.Doc import Control.Monad.Logger.Aeson -import Data.Aeson -import Data.Aeson.Compat (KeyMap) +import Data.Aeson (Value (..)) +import Data.Aeson.Compat import qualified Data.Aeson.Compat as Key import qualified Data.Aeson.Compat as KeyMap -import Data.ByteString (ByteString) import Data.List (sortOn) import Data.Maybe (fromMaybe) import Data.Text (Text, pack) import qualified Data.Text as T -import Data.Time (defaultTimeLocale, formatTime) +import Data.Time (UTCTime, defaultTimeLocale, formatTime) import qualified Data.Vector as V - -reformatTerminal - :: LogSettings -> Colors -> LogLevel -> LoggedMessage -> ByteString -reformatTerminal settings colors@Colors {..} logLevel LoggedMessage {..} = do - LogPiece.bytestring - $ if LogPiece.visibleLength oneLineLogPiece <= breakpoint - then oneLineLogPiece - else multiLineLogPiece +import Prettyprinter hiding (list) +import Prettyprinter.Util (reflow) + +reformatTerminal :: LogLevel -> LoggedMessage -> Doc Ann +reformatTerminal logLevel LoggedMessage {..} = + prettyTimestamp loggedMessageTimestamp + <+> prettyLogLevel logLevel + <+> align + ( fill 31 (prettyMessage loggedMessageText) + <> group (flatAlt multiline oneline) + ) where - breakpoint = getLogSettingsBreakpoint settings - - logTimestampPiece = - logPiece dim - $ pack - $ formatTime - defaultTimeLocale - "%F %X" - loggedMessageTimestamp - - logLevelPiece = case logLevel of - LevelDebug -> logPiece gray $ padTo 9 "debug" - LevelInfo -> logPiece green $ padTo 9 "info" - LevelWarn -> logPiece yellow $ padTo 9 "warn" - LevelError -> logPiece red $ padTo 9 "error" - LevelOther x -> logPiece blue $ padTo 9 x - - loggedSourceAsMap = - foldMap (KeyMap.singleton "source" . String) loggedMessageLogSource - - logPrefixPiece = - logTimestampPiece <> " [" <> logLevelPiece <> "] " - - logMessagePiece = logPiece bold $ padTo 31 loggedMessageText - - logAttrsPiece = - mconcat - [ colorizeKeyMap " " colors loggedSourceAsMap - , colorizeKeyMap " " colors loggedMessageThreadContext - , colorizeKeyMap " " colors loggedMessageMeta - ] - - oneLineLogPiece = mconcat [logPrefixPiece, logMessagePiece, logAttrsPiece] - - multiLineLogPiece = - let shift = "\n" <> LogPiece.offset (LogPiece.visibleLength logPrefixPiece) - in mconcat - [ logPrefixPiece - , logMessagePiece - , colorizeKeyMap shift colors loggedSourceAsMap - , colorizeKeyMap shift colors loggedMessageThreadContext - , colorizeKeyMap shift colors loggedMessageMeta - ] - -colorizeKeyMap :: LogPiece -> Colors -> KeyMap Value -> LogPiece -colorizeKeyMap sep Colors {..} km - | KeyMap.null km = mempty - | otherwise = foldMap (uncurry fromPair) $ sortOn fst $ KeyMap.toList km + oneline = " " <> hsep metas + multiline = hardline <> vsep metas + + metas :: [Doc Ann] + metas = + map (uncurry prettyPair) + $ maybe id (\s -> (("source", String s) :)) loggedMessageLogSource + $ sortOn fst (KeyMap.toList loggedMessageThreadContext) + <> sortOn fst (KeyMap.toList loggedMessageMeta) + +prettyTimestamp :: UTCTime -> Doc Ann +prettyTimestamp = + annotate AnnTimestamp . pretty . formatTime defaultTimeLocale "%F %X" + +prettyLogLevel :: LogLevel -> Doc Ann +prettyLogLevel l = enclose "[" "]" $ annotate (AnnByLevel l) $ levelDoc 9 where - fromPair k v = - sep <> logPiece cyan (Key.toText k) <> "=" <> logPiece magenta (fromValue v) - - fromValue = \case - Object m -> obj $ map (uncurry renderPairNested) $ KeyMap.toList m - Array a -> list $ map fromValue $ V.toList a - String x -> x - Number n -> sci n - Bool b -> pack $ show b - Null -> "null" - - renderPairNested k v = Key.toText k <> ": " <> fromValue v - - obj xs = "{" <> T.intercalate ", " xs <> "}" - list xs = "[" <> T.intercalate ", " xs <> "]" - sci = dropSuffix ".0" . pack . show + levelDoc :: Int -> Doc ann + levelDoc n = + fill n $ case l of + LevelDebug -> "debug" + LevelInfo -> "info" + LevelWarn -> "warn" + LevelError -> "error" + LevelOther x -> pretty $ T.take n x + +prettyMessage :: Text -> Doc Ann +prettyMessage = vsep . map reflow . T.lines + +prettyPair :: Key -> Value -> Doc Ann +prettyPair k v = + annotate AnnKey (pretty $ Key.toText k) + <> "=" + <> annotate AnnValue (fromValue v) + +fromValue :: Value -> Doc Ann +fromValue = \case + Object m -> list "{" "}" $ map (uncurry pair) $ KeyMap.toList m + Array a -> list "[" "]" $ map fromValue $ V.toList a + String x -> annotate AnnString $ pretty x + Number n -> annotate AnnNumber $ pretty $ dropSuffix ".0" $ pack $ show n + Bool b -> annotate AnnBoolean $ pretty $ show b + Null -> annotate AnnNull "null" + +pair :: Key -> Value -> Doc Ann +pair k v = + pretty (Key.toText k) + <> annotate AnnPunctuation ": " + <> fromValue v + +list :: Doc Ann -> Doc Ann -> [Doc Ann] -> Doc Ann +list l r = + enclose (annotate AnnPunctuation l) (annotate AnnPunctuation r) + . hcat + . punctuate (annotate AnnPunctuation ", ") dropSuffix :: Text -> Text -> Text dropSuffix suffix t = fromMaybe t $ T.stripSuffix suffix t - -padTo :: Int -> Text -> Text -padTo n t = t <> T.replicate pad " " where pad = max 0 $ n - T.length t diff --git a/Blammo/src/Blammo/Logging/Terminal/Doc.hs b/Blammo/src/Blammo/Logging/Terminal/Doc.hs new file mode 100644 index 0000000..177d4c1 --- /dev/null +++ b/Blammo/src/Blammo/Logging/Terminal/Doc.hs @@ -0,0 +1,81 @@ +module Blammo.Logging.Terminal.Doc + ( Ann (..) + , annToAnsi + + -- * Rendering + , RenderSettings (..) + , renderDoc + + -- * "Prettyprinter" re-exports + , Doc + + -- * "Prettyprinter.Render.Terminal" re-exports + , AnsiStyle + , Color (..) + , color + , colorDull + , bold + , italicized + , underlined + ) where + +import Prelude + +import Control.Monad.Logger.Aeson (LogLevel (..)) +import Data.Text (Text) +import Prettyprinter +import Prettyprinter.Render.Terminal hiding (renderStrict) +import Prettyprinter.Render.Terminal as Terminal +import Prettyprinter.Render.Text as Text + +data Ann + = AnnTimestamp + | AnnByLevel LogLevel + | AnnKey + | AnnValue + | AnnString + | AnnNumber + | AnnBoolean + | AnnNull + | AnnPunctuation + | -- | backdoor for external use-cases + AnnAnsi AnsiStyle + +annToAnsi :: Ann -> AnsiStyle +annToAnsi = \case + AnnTimestamp -> colorDull White -- TODO: faint, once supported + AnnByLevel l -> case l of + LevelDebug -> colorDull White -- TODO: faint, once supported + LevelInfo -> colorDull Green + LevelWarn -> colorDull Yellow + LevelError -> colorDull Red + LevelOther _ -> colorDull Blue + AnnKey -> colorDull Cyan + AnnValue -> colorDull Magenta + AnnString -> colorDull Green + AnnNumber -> colorDull Green + AnnBoolean -> colorDull Red + AnnNull -> colorDull Black + AnnPunctuation -> colorDull Black + AnnAnsi style -> style + +data RenderSettings = RenderSettings + { rsUseColor :: Bool + , rsPageWidth :: Int + , rsAnnToAnsi :: Ann -> AnsiStyle + } + +renderDoc :: RenderSettings -> Doc Ann -> Text +renderDoc RenderSettings {..} = + render + . layoutPretty layoutOptions + . reAnnotate rsAnnToAnsi + where + render + | rsUseColor = Terminal.renderStrict + | otherwise = Text.renderStrict + + layoutOptions = + defaultLayoutOptions + { layoutPageWidth = AvailablePerLine rsPageWidth 1.0 + } diff --git a/Blammo/src/Blammo/Logging/Terminal/LogPiece.hs b/Blammo/src/Blammo/Logging/Terminal/LogPiece.hs deleted file mode 100644 index 17da43e..0000000 --- a/Blammo/src/Blammo/Logging/Terminal/LogPiece.hs +++ /dev/null @@ -1,62 +0,0 @@ -module Blammo.Logging.Terminal.LogPiece - ( LogPiece - , logPiece - , render - , visibleLength - , bytestring - - -- * Built-in pieces - , offset - ) where - -import Prelude - -import Data.ByteString (ByteString) -import Data.Semigroup (Sum (..)) -import Data.String (IsString (..)) -import Data.Text (Text, pack) -import qualified Data.Text as T -import Data.Text.Encoding (encodeUtf8) - -data LogPiece = LogPiece - { lpRendered :: Text - , lpVisibleLength :: Sum Int - } - --- TODO: When we drop support for ghc-8.6: --- deriving stock Generic --- deriving (Semigroup, Monoid) via (GenericSemigroupMonoid LogPiece) - -instance Semigroup LogPiece where - a <> b = - LogPiece - { lpRendered = lpRendered a <> lpRendered b - , lpVisibleLength = lpVisibleLength a <> lpVisibleLength b - } - -instance Monoid LogPiece where - mempty = LogPiece mempty mempty - -instance IsString LogPiece where - fromString = logPiece id . pack - -logPiece - :: (Text -> Text) - -- ^ Non-visible decoration, such as color escapes - -> Text - -- ^ Raw - -> LogPiece -logPiece f t = - LogPiece {lpRendered = f t, lpVisibleLength = Sum $ T.length t} - -render :: LogPiece -> Text -render = lpRendered - -bytestring :: LogPiece -> ByteString -bytestring = encodeUtf8 . render - -visibleLength :: LogPiece -> Int -visibleLength = getSum . lpVisibleLength - -offset :: Int -> LogPiece -offset n = LogPiece {lpRendered = T.replicate n " ", lpVisibleLength = Sum n} diff --git a/Blammo/tests/Blammo/Logging/TerminalSpec.hs b/Blammo/tests/Blammo/Logging/TerminalSpec.hs index 8a7f80d..a475fe6 100644 --- a/Blammo/tests/Blammo/Logging/TerminalSpec.hs +++ b/Blammo/tests/Blammo/Logging/TerminalSpec.hs @@ -7,20 +7,13 @@ module Blammo.Logging.TerminalSpec import Prelude import Blammo.Logging -import Blammo.Logging.Colors (noColors) -import Blammo.Logging.LogSettings - ( LogSettings - , defaultLogSettings - , setLogSettingsBreakpoint - ) import Blammo.Logging.Logger (LoggedMessage (..)) import Blammo.Logging.Terminal +import Blammo.Logging.Terminal.Doc import Data.Aeson (object) import Data.Aeson.Types (Object, Pair, Value (..)) -import Data.ByteString (ByteString) -import qualified Data.ByteString as BS -import qualified Data.ByteString.Char8 as BS8 import Data.Text (Text) +import qualified Data.Text as T import Data.Time import Test.Hspec @@ -50,7 +43,7 @@ spec = do , " source=app x={y: True} a=[1, 2, 3]" ] - reformatTerminal (settings 120) noColors LevelInfo lm `shouldBe` expected + renderTerminal False 120 LevelInfo lm `shouldBe` expected it "moves attributes to multi-line at the given breakpoint" $ do let @@ -91,12 +84,53 @@ spec = do , " d=aaaaaaaaa" ] - breakpoint = BS.length single + breakpoint = T.length single - reformatTerminal (settings breakpoint) noColors LevelInfo lm `shouldBe` single - reformatTerminal (settings $ breakpoint - 1) noColors LevelInfo lm + renderTerminal False breakpoint LevelInfo lm `shouldBe` single + renderTerminal False (breakpoint - 1) LevelInfo lm `shouldBe` multi + it "reflows long and multi-line messages" $ do + let + lm = + LoggedMessage + { loggedMessageTimestamp = + UTCTime + { utctDay = fromGregorian 2022 1 1 + , utctDayTime = 0 + } + , loggedMessageLevel = LevelInfo + , loggedMessageLoc = Nothing + , loggedMessageLogSource = Just "app" + , loggedMessageThreadContext = mempty + , loggedMessageText = + "I'm a really really really long message " + <> "with multiple lines that are so long " + <> "they should get reflowed at the column " + <> "limit." + <> "\n" + <> "\nThey are:" + <> "\n" + <> "\n1- This" + <> "\n2- That" + , loggedMessageMeta = mempty + } + + expected = + mconcat + [ "2022-01-01 00:00:00 [info ] I'm a really really really long message with\n" + , " multiple lines that are so long they should get\n" + , " reflowed at the column limit.\n" + , "\n" + , " They are:\n" + , "\n" + , " 1- This\n" + , " 2- That source=app" + ] + + stripColor (renderTerminal True 80 LevelInfo lm) + `shouldBe` expected + it "aligns multi-line correctly even with color escapes" $ do let lm = @@ -130,22 +164,29 @@ spec = do , " d=aaaaaaaaa" ] - stripColor (reformatTerminal (settings 120) noColors LevelInfo lm) + stripColor (renderTerminal True 120 LevelInfo lm) `shouldBe` expected -settings :: Int -> LogSettings -settings breakpoint = setLogSettingsBreakpoint breakpoint defaultLogSettings - keyMap :: [Pair] -> Object keyMap ps = km where Object km = object ps -- Removes from any '\ESC' Char to the next 'm' Char -stripColor :: ByteString -> ByteString -stripColor = snd . BS8.foldl' go (False, "") +stripColor :: Text -> Text +stripColor = snd . T.foldl' go (False, "") where - go :: (Bool, ByteString) -> Char -> (Bool, ByteString) + go :: (Bool, Text) -> Char -> (Bool, Text) go (dropping, acc) = \case '\ESC' -> (True, acc) 'm' | dropping -> (False, acc) _ | dropping -> (True, acc) - c -> (False, BS8.snoc acc c) + c -> (False, T.snoc acc c) + +renderTerminal :: Bool -> Int -> LogLevel -> LoggedMessage -> Text +renderTerminal c w l = renderDoc settings . reformatTerminal l + where + settings = + RenderSettings + { rsUseColor = c + , rsPageWidth = w + , rsAnnToAnsi = annToAnsi + } diff --git a/stack-lts16.yaml b/stack-lts16.yaml index 1de544e..bcf3e22 100644 --- a/stack-lts16.yaml +++ b/stack-lts16.yaml @@ -1,11 +1,12 @@ resolver: lts-16.31 extra-deps: - aeson-1.5.2.0 + - context-0.2.0.0 - envparse-0.4.1 - fast-logger-3.2.3 - monad-logger-0.3.39 - monad-logger-aeson-0.4.0.3 - - context-0.2.0.0 + - prettyprinter-1.7.0 packages: - Blammo diff --git a/stack-lts21.yaml b/stack-lts21.yaml new file mode 100644 index 0000000..9ddfc9f --- /dev/null +++ b/stack-lts21.yaml @@ -0,0 +1,12 @@ +resolver: lts-21.25 +extra-deps: + - fast-logger-3.2.3 + - monad-logger-0.3.39 + +allow-newer: true +allow-newer-deps: + - monad-logger-aeson + +packages: + - Blammo + - Blammo-wai diff --git a/stack-lts22.yaml b/stack-lts22.yaml new file mode 100644 index 0000000..97c4e30 --- /dev/null +++ b/stack-lts22.yaml @@ -0,0 +1,5 @@ +resolver: lts-22.44 + +packages: + - Blammo + - Blammo-wai diff --git a/stack-lts24.yaml b/stack-lts24.yaml new file mode 100644 index 0000000..4eac6a7 --- /dev/null +++ b/stack-lts24.yaml @@ -0,0 +1,5 @@ +resolver: lts-24.23 + +packages: + - Blammo + - Blammo-wai diff --git a/stack.yaml b/stack.yaml index d7de56b..ad18933 120000 --- a/stack.yaml +++ b/stack.yaml @@ -1 +1 @@ -stack-lts23.yaml \ No newline at end of file +stack-lts24.yaml \ No newline at end of file