summaryrefslogtreecommitdiff
path: root/src/Obs/Logging.idr
blob: 07617758010dd85b19546e65aeda90540ab66e3d (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
module Obs.Logging

import Data.List.Lazy

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
  tags : List String

pretty : Message ann -> Doc ann
pretty msg =
  hsep $
  [ fill 6 (pretty (show msg.lvl) <+> colon) ] ++
  reverse (map (\s => pretty s <+> colon) msg.tags) ++
  [ align msg.msg ]

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

export
record Logging (ann, t : Type) where
  constructor MkLogging
  out  : Maybe t
  msgs : LazyList (Message ann)

export
Functor (Logging ann) where
  map f = {out $= map f}

export
Applicative (Logging ann) where
  pure v = MkLogging {out = pure v, msgs = []}
  f <*> v = MkLogging {out = f.out <*> v.out, msgs = f.msgs ++ v.msgs}

export
Monad (Logging ann) where
  (MkLogging Nothing  msgs) >>= f = MkLogging Nothing msgs
  (MkLogging (Just v) msgs) >>= f = {msgs $= (msgs ++)} (f v)

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

export
discard : Logging ann a -> Maybe a
discard = out

export
logToTerminal : Level -> Logging AnsiStyle a -> IO a
logToTerminal lvl log = do
  Lazy.for_ (filter (\msg => msg.lvl <= lvl) log.msgs) (putDoc . pretty)
  case log.out of
    Just v => pure v
    Nothing => exitFailure

export
abort : Logging ann a
abort = MkLogging Nothing []

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

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 = MkLogging (pure ()) [MkMessage lvl msg []]

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

export
Loggable ann x => Loggable ann (WithBounds x) where
  log lvl msg =
    let f : Logging ann () -> Logging ann ()
        f = case msg.isIrrelevant of
          True => id
          False => inScope $
            show (1 + msg.bounds.startLine) ++
            ":" ++
            show msg.bounds.startCol ++
            "--" ++
            show (1 + msg.bounds.endLine) ++
            ":" ++
            show msg.bounds.endCol
    in f (log lvl msg.val)