summaryrefslogtreecommitdiff
path: root/src/Obs/Logging.idr
blob: ef0bae6d510b18ff567ce2be791452d2cff812eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
module Obs.Logging

import System

import Text.Bounded
import public Text.PrettyPrint.Prettyprinter
import Text.PrettyPrint.Prettyprinter.Render.Terminal

-- Logging Levels --------------------------------------------------------------

public export
data Level = Trace | Info | Debug | Warn | Error

export
Show Level where
  show Trace = "trace"
  show Info  = "info"
  show Debug = "debug"
  show Warn  = "warn"
  show Error = "error"

levelToNat : Level -> Nat
levelToNat Trace = 4
levelToNat Info  = 3
levelToNat Debug = 2
levelToNat Warn  = 1
levelToNat Error = 0

export
Eq Level where
  l == l' = levelToNat l == levelToNat l'

export
Ord Level where
  compare l l' = compare (levelToNat l) (levelToNat l')

-- Logging Messages ------------------------------------------------------------

record Message (ann : Type) where
  constructor MkMessage
  lvl : Level
  msg : Doc ann
  bounds : Maybe Bounds
  tags : List String

pretty : Message ann -> Doc ann
pretty msg =
  let leader =
    hsep $
      [ fill 6 (pretty (show msg.lvl) <+> colon) ] ++
      [ Doc.pretty $ concat {t = List} $
        case msg.bounds of
          Nothing => []
          Just bounds =>
            [ show (1 + bounds.startLine)
            , ":"
            , show bounds.startCol
            , "--"
            , show (1 + bounds.endLine)
            , ":"
            , show bounds.endCol
            , ":"
            ]
      ] ++
      map (\s => pretty s <+> colon) msg.tags
  in group $ width (group leader <+> line) (\width => group $ indent (2 - width) msg.msg)

-- Logging Monad ---------------------------------------------------------------

export
data Logging : (ann, t : Type) -> Type where
  Cont : (msg : Message ann) -> (l : Lazy (Logging ann t)) -> Logging ann t
  Abort : Logging ann t
  End : (v : t) -> Logging ann t

%name Logging l, l'

export
Functor (Logging ann) where
  map f (Cont msg l) = Cont msg (map f l)
  map f Abort = Abort
  map f (End v) = End (f v)

export
Applicative (Logging ann) where
  pure = End
  (Cont msg f) <*> v = Cont msg (f <*> v)
  Abort <*> v = Abort
  (End f) <*> (Cont msg v) = Cont msg (End f <*> v)
  (End f) <*> Abort = Abort
  (End f) <*> (End v) = End (f v)

export
Monad (Logging ann) where
  join (Cont msg l) = Cont msg (join l)
  join Abort = Abort
  join (End v) = v

-- Eliminators -----------------------------------------------------------------

export
discard : Logging ann a -> Maybe a
discard (Cont msg l) = discard l
discard Abort = Nothing
discard (End v) = Just v

export
logToTerminal : Level -> Logging AnsiStyle a -> IO a
logToTerminal lvl (Cont msg l) = do
  () <- if msg.lvl <= lvl then putDoc (pretty msg) else pure ()
  logToTerminal lvl l
logToTerminal lvl Abort = exitFailure
logToTerminal lvl (End v) = pure v

export
abort : Logging ann a
abort = Abort

mapMsg : (Message ann -> Message ann') -> Logging ann a -> Logging ann' a
mapMsg f (Cont msg l) = Cont (f msg) (mapMsg f l)
mapMsg f Abort = Abort
mapMsg f (End v) = End v

export
inScope : (tag : String) -> Logging ann a -> Logging ann a
inScope tag = mapMsg {tags $= (tag ::)}

export
inBounds : Bounds -> Logging ann a -> Logging ann a
inBounds bounds = mapMsg {bounds $= maybe (Just bounds) Just}

public export
interface Loggable (0 ann, ty : Type) where
  log : Level -> ty -> Logging ann ()

export
trace : Loggable ann ty => ty -> Logging ann ()
trace = log Trace

export
info : Loggable ann ty => ty -> Logging ann ()
info = log Info

export
debug : Loggable ann ty => ty -> Logging ann ()
debug = log Debug

export
warn : Loggable ann ty => ty -> Logging ann ()
warn = log Warn

export
error : Loggable ann ty => ty -> Logging ann ()
error = log Error

export
fatal : Loggable ann ty => ty -> Logging ann a
fatal msg = do error msg; abort

export
Loggable ann (Doc ann) where
  log lvl msg = Cont (MkMessage lvl msg Nothing []) $ End ()

export
Pretty x => Loggable ann x where
  log lvl msg = log lvl $ pretty {ann = ann} msg