package asai
The signature of a logger.
module Code : Diagnostic.Code
Sending Messages
val emit :
?severity:Diagnostic.severity ->
?loc:Span.t ->
?backtrace:Diagnostic.backtrace ->
?additional_messages:Diagnostic.message list ->
Code.t ->
string ->
unit
emit code str
emits a string and continues the computation.
Example:
Logger.emit `TypeError "This type is extremely unnatural:\nNat"
val emitf :
?severity:Diagnostic.severity ->
?loc:Span.t ->
?backtrace:Diagnostic.backtrace ->
?additional_messages:Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, unit) Stdlib.format4 ->
'a
emitf code format ...
formats and emits a message, and then continues the computation. Note that there should not be any literal control characters. See Diagnostic.text
.
Example:
Logger.emitf `TypeError "Type %a is too ugly" Syntax.pp tp
val emit_diagnostic : Code.t Diagnostic.t -> unit
Emit a diagnostic and continue the computation.
val fatal :
?severity:Diagnostic.severity ->
?loc:Span.t ->
?backtrace:Diagnostic.backtrace ->
?additional_messages:Diagnostic.message list ->
Code.t ->
string ->
'a
fatal code str
aborts the current computation with the string str
.
Example:
Logger.fatal `FileError "Forgot to feed the cat"
val fatalf :
?severity:Diagnostic.severity ->
?loc:Span.t ->
?backtrace:Diagnostic.backtrace ->
?additional_messages:Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, 'b) Stdlib.format4 ->
'a
fatalf code format ...
constructs a diagnostic and aborts the current computation with the diagnostic. Note that there should not be any literal control characters. See Diagnostic.text
.
Example:
Logger.fatalf `FileError "Failed to write the password to %s" file_path
val fatal_diagnostic : Code.t Diagnostic.t -> 'a
Abort the computation with a diagnostic.
Backtraces
val get_backtrace : unit -> Diagnostic.backtrace
get_backtrace()
returns the current backtrace.
val with_backtrace : Diagnostic.backtrace -> (unit -> 'a) -> 'a
with_backtrace bt f
runs the thunk f
with bt
as the initial backtrace.
Example:
(* running code with a fresh backtrace *)
with_backtrace Emp @@ fun () -> ...
val trace : ?loc:Span.t -> string -> (unit -> 'a) -> 'a
trace str f
records the string str
and runs the thunk f
with the new backtrace.
val tracef :
?loc:Span.t ->
('a, Stdlib.Format.formatter, unit, (unit -> 'b) -> 'b) Stdlib.format4 ->
'a
tracef format ... f
formats and records a message as a frame in the backtrace, and runs the thunk f
with the new backtrace. Note that there should not be any literal control characters. See Diagnostic.text
.
val trace_text : ?loc:Span.t -> Diagnostic.text -> (unit -> 'a) -> 'a
trace_text text f
records the message text
and runs the thunk f
with the new backtrace.
val trace_message : Diagnostic.message -> (unit -> 'a) -> 'a
trace_message msg f
records the message msg
and runs the thunk f
with the new backtrace.
Locations
val get_loc : unit -> Span.t option
get_loc()
returns the current location.
val with_loc : Span.t option -> (unit -> 'a) -> 'a
with_loc loc f
runs the thunk f
with loc
as the initial location loc
. Note that with_loc None
will clear the current location, while merge_loc None
will keep it. See merge_loc
.
val merge_loc : Span.t option -> (unit -> 'a) -> 'a
merge_loc loc f
"merges" loc
into the current location and runs the thunk f
. By "merge", it means that if loc
is None
, then the current location is kept; otherwise, it is overwritten. Note that with_loc None
will clear the current location, while merge_loc None
will keep it. See with_loc
.
Constructing Diagnostics
Functions in this section differ from the ones in Diagnostic
(for example, Diagnostic.make
) in that they fill out the current location, the current backtrace, and the severity automatically. (One can still overwrite them with optional arguments.)
val diagnostic :
?severity:Diagnostic.severity ->
?loc:Span.t ->
?backtrace:Diagnostic.backtrace ->
?additional_messages:Diagnostic.message list ->
Code.t ->
string ->
Code.t Diagnostic.t
diagnostic code str
constructs a diagnostic with the message str
along with the backtrace frames recorded via tracef
.
Example:
Logger.diagnostic `TypeError "This\nis\ntoo\nmuch."
val diagnosticf :
?severity:Diagnostic.severity ->
?loc:Span.t ->
?backtrace:Diagnostic.backtrace ->
?additional_messages:Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, Code.t Diagnostic.t) Stdlib.format4 ->
'a
diagnosticf code format ...
constructs a diagnostic along with the backtrace frames recorded via trace
. Note that there should not be any literal control characters. See Diagnostic.text
.
Example:
Logger.diagnosticf `TypeError "Term %a does not type check, or does it?" Syntax.pp tm
val kdiagnosticf :
?severity:Diagnostic.severity ->
?loc:Span.t ->
?backtrace:Diagnostic.backtrace ->
?additional_messages:Diagnostic.message list ->
(Code.t Diagnostic.t -> 'b) ->
Code.t ->
('a, Stdlib.Format.formatter, unit, 'b) Stdlib.format4 ->
'a
kdiagnosticf kont code format ...
is kont (diagnosticf code format ...)
. Note that there should not be any literal control characters. See Diagnostic.text
.
Algebraic Effects
val run :
?init_loc:Span.t ->
?init_backtrace:Diagnostic.backtrace ->
emit:(Code.t Diagnostic.t -> unit) ->
fatal:(Code.t Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a
val adopt :
('code Diagnostic.t -> Code.t Diagnostic.t) ->
(?init_loc:Span.t ->
?init_backtrace:Diagnostic.backtrace ->
emit:('code Diagnostic.t -> unit) ->
fatal:('code Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a) ->
(unit -> 'a) ->
'a
adopt m run f
runs the thunk f
that uses a different Logger
instance. It takes the runner run
from that Logger
instance as an argument to handle effects, and will use m
to transform diagnostics generated by f
into ones in the current Logger
instance. The backtrace within f
will include the backtrace that leads to adopt
, and the innermost specified location will be carried over, too. The intended use case is to integrate diagnostics from a library into those in the main application.
adopt
is a convenience function that can be implemented as follows:
let adopt m f run =
run
?init_loc:(get_loc())
?init_backtrace:(Some (get_backtrace()))
~emit:(fun d -> emit_diagnostic (m d))
~fatal:(fun d -> fatal_diagnostic (m d))
f
Here shows the intended usage, where Lib
is the library to be used in the main application:
module MainLogger = Logger.Make(Code)
module LibLogger = Lib.Logger
let _ = MainLogger.adopt (Diagnostic.map code_mapper) LibLogger.run @@ fun () -> ...
val try_with :
?emit:(Code.t Diagnostic.t -> unit) ->
?fatal:(Code.t Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a
try_with ~emit ~fatal f
runs the thunk f
, using emit
to intercept non-fatal diagnostics before continuing the computation (see emit
and emitf
), and fatal
to intercept fatal diagnostics that have aborted the computation (see fatal
and fatalf
). The default interceptors re-emit or re-raise the intercepted diagnostics.
val register_printer :
([ `Trace | `Emit of Code.t Diagnostic.t | `Fatal of Code.t Diagnostic.t ] ->
string option) ->
unit
register_printer p
registers a printer p
via Printexc.register_printer
to convert unhandled internal effects and exceptions into strings for the OCaml runtime system to display. Ideally, all internal effects and exceptions should have been handled by run
and there is no need to use this function, but when it is not the case, this function can be helpful for debugging. The functor Logger.Make
always registers a simple printer to suggest using run
, but you can register new ones to override it. The return type of the printer p
should return Some s
where s
is the resulting string, or None
if it chooses not to convert a particular effect or exception. The registered printers are tried in reverse order until one of them returns Some s
for some s
; that is, the last registered printer is tried first. Note that this function is a wrapper of Printexc.register_printer
and all the registered printers (via this function or Printexc.register_printer
) are put into the same list.
The input type of the printer p
is a variant representation of all internal effects and exceptions used in this module:
`Trace
corresponds to the effect triggered bytrace
; and`Emit diag
corresponds to the effect triggered byemit
; and`Fatal diag
corresponds to the exception triggered byfatal
.
Note: Diagnostic.string_of_text
can be handy for converting a message into a string.