@@ -1,20 +1,26 @@
1+{-# LANGUAGE BangPatterns #-}
12{-# LANGUAGE RecordWildCards #-}
2−
33module Katip.Scribes.Handle where
44
55-------------------------------------------------------------------------------
6−import Control.Applicative as A
6+import Control.Applicative as A
77import Control.Monad
88import Data.Aeson
9−import qualified Data.HashMap.Strict as HM
9+import Data.Aeson.Internal.Time
10+import qualified Data.ByteString.Builder as B
11+import Data.ByteString.Builder.Prim ((>$<), (>*<))
12+import qualified Data.ByteString.Builder.Prim as BP
13+import Data.Char (chr)
14+import qualified Data.HashMap.Strict as HM
1015import Data.Monoid
11−import Data.Text (Text)
16+import Data.Text (Text)
1217import Data.Text.Lazy.Builder
13−import Data.Text.Lazy.IO as T
18+import Data.Text.Lazy.Encoding (decodeUtf8)
19+import Data.Text.Lazy.IO as T
1420import Data.Time
15−import qualified Data.Time.Locale.Compat as LC
21+import qualified Data.Time.Locale.Compat as LC
1622import System.IO
17−import System.IO.Unsafe (unsafePerformIO)
23+import System.IO.Unsafe (unsafePerformIO)
1824-------------------------------------------------------------------------------
1925import Katip.Core
2026-------------------------------------------------------------------------------
@@ -96,6 +102,124 @@ formatItem withColor verb Item{..} =
96102 | otherwise = s
97103
98104
105+-------------------------------------------------------------------------------
106+formatItem' :: LogItem a => Bool -> Verbosity -> Item a -> Builder
107+formatItem' withColor verb Item{..} =
108+ brackets nowStr <>
109+ brackets (mconcat $ map fromText $ intercalateNs _itemNamespace) <>
110+ brackets (fromText (renderSeverity' _itemSeverity)) <>
111+ brackets (fromString _itemHost) <>
112+ brackets (fromString (show _itemProcess)) <>
113+ brackets (fromText (getThreadIdText _itemThread)) <>
114+ mconcat ks <>
115+ maybe mempty (brackets . fromString . locationToString) _itemLoc <>
116+ fromText " " <> (unLogStr _itemMessage)
117+ where
118+ nowStr = fromLazyText $ decodeUtf8 $ B.toLazyByteString $ utcTime _itemTime
119+ ks = map brackets $ getKeys verb _itemPayload
120+ renderSeverity' s = case s of
121+ EmergencyS -> red $ renderSeverity s
122+ AlertS -> red $ renderSeverity s
123+ CriticalS -> red $ renderSeverity s
124+ ErrorS -> red $ renderSeverity s
125+ WarningS -> yellow $ renderSeverity s
126+ _ -> renderSeverity s
127+ red = colorize "31"
128+ yellow = colorize "33"
129+ colorize c s
130+ | withColor = "\ESC["<> c <> "m" <> s <> "\ESC[0m"
131+ | otherwise = s
132+
133+
134+-------------------------------------------------------------------------------
135+utcTime :: UTCTime -> B.Builder
136+utcTime (UTCTime d s) = dayTime d (diffTimeOfDay64 s) <> B.char7 'Z'
137+{-# INLINE utcTime #-}
138+
139+
140+dayTime :: Day -> TimeOfDay64 -> B.Builder
141+dayTime d t = day d <> B.char7 'T' <> timeOfDay64 t
142+{-# INLINE dayTime #-}
143+
144+
145+timeOfDay64 :: TimeOfDay64 -> B.Builder
146+timeOfDay64 (TOD h m s)
147+ | frac == 0 = hhmmss -- omit subseconds if 0
148+ | otherwise = hhmmss <> BP.primBounded showFrac frac
149+ where
150+ hhmmss = BP.primBounded (ascii8 (hh,(hl,(':',(mh,(ml,(':',(sh,sl')))))))) ()
151+ !(T hh hl) = twoDigits h
152+ !(T mh ml) = twoDigits m
153+ !(T sh sl') = twoDigits (fromIntegral real)
154+ (real,frac) = s `quotRem` pico
155+ showFrac = (\x -> ('.', x)) >$< (BP.liftFixedToBounded BP.char7 >*< trunc12)
156+ trunc12 = (`quotRem` micro) >$<
157+ BP.condB (\(_,y) -> y == 0) (fst >$< trunc6) (digits6 >*< trunc6)
158+ digits6 = ((`quotRem` milli) . fromIntegral) >$< (digits3 >*< digits3)
159+ trunc6 = ((`quotRem` milli) . fromIntegral) >$<
160+ BP.condB (\(_,y) -> y == 0) (fst >$< trunc3) (digits3 >*< trunc3)
161+ digits3 = (`quotRem` 10) >$< (digits2 >*< digits1)
162+ digits2 = (`quotRem` 10) >$< (digits1 >*< digits1)
163+ digits1 = BP.liftFixedToBounded (digit >$< BP.char7)
164+ trunc3 = BP.condB (== 0) BP.emptyB $
165+ (`quotRem` 100) >$< (digits1 >*< trunc2)
166+ trunc2 = BP.condB (== 0) BP.emptyB $
167+ (`quotRem` 10) >$< (digits1 >*< trunc1)
168+ trunc1 = BP.condB (== 0) BP.emptyB digits1
169+
170+ pico = 1000000000000 -- number of picoseconds in 1 second
171+ micro = 1000000 -- number of microseconds in 1 second
172+ milli = 1000 -- number of milliseconds in 1 second
173+{-# INLINE timeOfDay64 #-}
174+
175+
176+day :: Day -> B.Builder
177+day dd = encodeYear yr <>
178+ BP.primBounded (ascii6 ('-',(mh,(ml,('-',(dh,dl)))))) ()
179+ where (yr,m,d) = toGregorian dd
180+ !(T mh ml) = twoDigits m
181+ !(T dh dl) = twoDigits d
182+ encodeYear y
183+ | y >= 1000 = B.integerDec y
184+ | y > 0 =
185+ let (ab,c) = fromIntegral y `quotRem` 10
186+ (a,b) = ab `quotRem` 10
187+ in BP.primBounded (ascii4 ('0',(digit a,(digit b,digit c)))) ()
188+ | otherwise =
189+ error "Data.Aeson.Encode.Builder.day: years BCE not supported"
190+{-# INLINE day #-}
191+
192+
193+ascii8 :: (Char, (Char, (Char, (Char, (Char, (Char, (Char, Char)))))))
194+ -> BP.BoundedPrim a
195+ascii8 cs = BP.liftFixedToBounded $ const cs >$<
196+ BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7 >*<
197+ BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7
198+{-# INLINE ascii8 #-}
199+
200+
201+data T = T {-# UNPACK #-} !Char {-# UNPACK #-} !Char
202+
203+
204+twoDigits :: Int -> T
205+twoDigits a = T (digit hi) (digit lo)
206+ where (hi,lo) = a `quotRem` 10
207+
208+digit :: Int -> Char
209+digit x = chr (x + 48)
210+
211+
212+ascii4 :: (Char, (Char, (Char, Char))) -> BP.BoundedPrim a
213+ascii4 cs = BP.liftFixedToBounded $ const cs >$<
214+ BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7
215+{-# INLINE ascii4 #-}
216+
217+
218+ascii6 :: (Char, (Char, (Char, (Char, (Char, Char))))) -> BP.BoundedPrim a
219+ascii6 cs = BP.liftFixedToBounded $ const cs >$<
220+ BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7
221+{-# INLINE ascii6 #-}
222+
99223-------------------------------------------------------------------------------
100224-- | An implicit environment to enable logging directly ouf of the IO monad.
101225_ioLogEnv :: LogEnv