package pplumbing

  1. Overview
  2. Docs

Module ErrSource

Err is an abstraction to report located errors and warnings to the user.

The canonical syntax for an error produced by this lib is:

  File "my-file", line 42, character 6-11:
  42 | Hello World
             ^^^^^
  Error: Some message that gives a general explanation of the issue.
  Followed by more details.

It is heavily inspired by dune's user_messages and uses dune's error message rendering under the hood.

Sourcetype t

A value of type t is an immutable piece of information that the programmer intends to report to the user, in a way that breaks flow. To give some concrete examples:

  • A message (or several) reporting an error condition in a program
  • A request to exit the program with an exit code.
Sourceexception E of t

The exception that is raised to break the control flow of programs using Err.

Examples:

  • When reporting an error:

      if had_error then Err.raise [ Pp.text "An error occurred." ]
  • When requesting an exit code:

      if shall_exit_42 then Err.exit 42

The standard usage for this library is to wrap entire sections in a protect, which takes care of catching E and handling it accordingly. You may also catch this exception manually if you need, for more advanced uses (in which case you'll take care of recording and re-raising backtraces, etc).

Sourceval sexp_of_t : t -> Sexplib0.Sexp.t

Return a Sexp to inspect what inside t. Note the sexp is not meant for supporting any kind of round trip serialization (there is no t_of_sexp). Rather, this is for interoperability with other error handling mechanisms based on sexps. We think exposing this can help accommodating some use cases, making it easier to write expect tests involving Err, etc.

Note that the exit code contained in t is not shown by sexp_of_t. See With_exit_code.sexp_of_t if needed.

Exit codes

This part allows breaking the control flow with an exception indicating a request to end the program with a given exit code.

Sourcemodule Exit_code : sig ... end

The handling of exit code is based on Cmdliner conventions.

Sourceval exit : Exit_code.t -> _

Request the termination of the program with the provided exit code. Make sure you have documented that particular exit code in the man page of your CLI. We recommend to stick to the error codes exposed by Exit_code, which are documented by default and pervasively used by existing CLIs built with cmdliner. Raises E.

Sourcemodule With_exit_code : sig ... end

Raising errors

Sourceval raise : ?loc:Loc.t -> ?hints:Pp_tty.t list -> ?exit_code:Exit_code.t -> Pp_tty.t list -> _

Raise a user error. You may override exit_code with the requested exit code to end the program with. It defaults to Exit_code.some_error.

Example:

  let unknown_var var =
    Err.raise
      ~loc
      [ Pp.textf "Unknown variable '%s'" var ]
      ~hints:(Err.did_you_mean var ~candidates:[ "foo"; "bar"; "baz" ])
  ;;
Sourceval create : ?loc:Loc.t -> ?context:Pp_tty.t list -> ?hints:Pp_tty.t list -> ?exit_code:Exit_code.t -> Pp_tty.t list -> t

Create a err and return it, instead of raising it right away.

Sourceval sexp : Sexplib0.Sexp.t -> _ Pp.t

sexp s is the preferred way to embed some context of type Sexp.t into a _ Pp.t paragraph as part of a error.

Sourceval exn : exn -> _ Pp.t

exn e is the preferred way to embed an exception into a _ Pp.t paragraph as part of an error.

Context

Sourceval add_context : t -> Pp_tty.t list -> t

add_context t items prepends the supplied items to the context of t. This approach reflects the idea of a stack: new context items are added to the front, representing the most recent layer of context.

When rendered in the console, context items are displayed before the main error paragraphs, with the most recently added context item appearing first. This mirrors the reverse navigation of the program's call stack, as context is typically added at the point where an error is caught and additional information is provided.

This mechanism is useful for incrementally building a high-level "stack trace" of user-defined context, helping to clarify the sequence of events leading to the error. See also reraise_with_context.

Sourceval reraise_with_context : t -> Printexc.raw_backtrace -> Pp_tty.t list -> _

Reraise with added context. See also add_context. Usage:

  match do_x (Y.to_x y) with
  | exception Err.E e ->
    let bt = Printexc.get_raw_backtrace () in
    Err.reraise_with_context e bt [ Pp.text "Trying to do x with y"; Y.pp y ]

Result

Sourceval ok_exn : ('a, t) result -> 'a

Helper to raise a user error from a result type.

  • ok_exn (Ok x) is x
  • ok_exn (Error msg) is Stdlib.raise (E msg)
Sourceval of_exn : exn -> t

Build an error from an exception. This retrieves e if the exception is Err.E e, otherwise this creates a new error using the sexp of the supplied exception.

Hints

Sourceval did_you_mean : string -> candidates:string list -> Pp_tty.t list

Produces a "Did you mean ...?" hint

State Getters

Sourceval am_running_test : unit -> bool

Set by the For_test.wrap when wrapping sections for tests, accessed by libraries if needed.

Sourceval error_count : unit -> int

This return the number of errors that have been emitted via Err.error since the last reset_counts (in practice that is the start of the program). Beware, note that errors raised as exceptions via functions such as Err.raise do not affect the error count. The motivation is to allow exceptions to be caught without impacting the overall exit code.

Sourceval had_errors : unit -> bool

A convenient wrapper for Err.error_count () > 0.

This is useful if you are trying not to stop at the first error encountered, but still want to stop the execution at a specific breakpoint after some numbers of errors. To be used in places where you want to stop the flow at a given point rather than returning meaningless data.

Sourceval warning_count : unit -> int

Return the number of warnings that have been emitted via Err.warning since the last reset_counts.

Color mode

Inspired by the git diff --color=<WHEN> command line parameter, this library allows to access the rendering mode that should be used to style the output aimed for the user, in the terminal or perhaps using a pager.

If you use Log_cli.set_config, your cli will also support the same --color flag as git diff. You can access the value that was set via the color_mode getter. The default mode is `Auto.

Even though it is traditionally called "color"-mode, this goes beyond simply colors and controls all forms of style rendering construct, such as bold, italic, and other ansi special characters.

Sourcemodule Color_mode : sig ... end
Sourceval color_mode : unit -> Color_mode.t

Messages and Log Levels

Inspired by logging conventions in many CLI tools, this library provides a mechanism to control the verbosity of log messages based on their severity level. This allows users to filter messages, ensuring that only relevant information is displayed during program execution.

The log level can be set programmatically or via command-line flags using Log_cli. The default log level is Warning, meaning only warnings and errors will be displayed unless a more verbose level is explicitly set.

The available log levels are:

  • Quiet: Suppresses all log messages, including errors.
  • Error: Displays only error messages.
  • Warning: Displays warnings and errors (default).
  • Info: Displays informational messages, warnings, and errors.
  • Debug: Displays all messages, including debug information.

Programs can query the current log level using log_level and check whether a specific level is enabled using log_enables. This is useful for conditionally executing code that should only run at certain verbosity levels.

Example usage:

  if Err.log_enables ~level:Debug
  then (
    (* Perform expensive debugging operations *)
    let debug_data = compute_debug_data () in
    Err.debug (lazy [ Pp.textf "Debug data: %s" debug_data ]))

Note: Functions such as Err.info, Err.warning, and Err.debug automatically check the log level before emitting messages. You do not need to call log_enables before using them.

When using Log_cli, the log level can be set via a command-line flag (e.g., --verbosity=debug). This ensures consistent behavior across applications using this library.

Note: A log level named App has been added to ensure compatibility with the Logs library, as this constructor is part of Logs.level. However, this module does not differentiate between the Quiet and App levels. The App level is primarily included to facilitate interoperability with third-party libraries that rely on Logs.

Sourcemodule Log_level : sig ... end
Sourcemodule Level : sig ... end

A level for individual messages (by contrast to the current level of the log).

Sourceval log_level : unit -> Log_level.t

Access the current log level.

Sourceval log_enables : level:Level.t -> bool

Tell whether the current log level enables the output of messages of the supplied level.

Printing messages

Sourceval prerr : ?reset_separator:bool -> t -> unit

Print to stderr (not thread safe). By default, prerr will start by writing a blank line on stderr if Err messages have already been emitted during the lifetime of the program. That is a reasonable default to ensure that err messages are always nicely separated by an empty line, to make them more readable. However, if you structure your output manually, perhaps you do not want this. If reset_separator=true, this behavior is turned off, and the first message of this batch will be printed directly without a leading blank line.

Sourceval to_string_hum : t -> string

to_string_hum t is shorthand to Sexp.to_string_hum (sexp_of_t t). This may be used if you want to embed t as a string. Note you'll lose all colors and other style formatting. For pretty printing of errors to the console, see prerr.

Non-raising user errors

This part of the library allows the production of messages that do not raise.

For example: - Emitting multiple errors before terminating - Non fatal Warnings - Debug and Info messages

Errors and warnings are going to affect error_count (and resp. warning_count), which is going to be used by protect to impact the exit code of the application. Use with care.

Sourceval error : ?loc:Loc.t -> ?hints:Pp_tty.t list -> Pp_tty.t list -> unit

Emit an error on stderr if the log level is Error or more (enabled by default). Note that this function increments the error count regardless of the log level (even when the message is not displayed that is).

Sourceval warning : ?loc:Loc.t -> ?hints:Pp_tty.t list -> Pp_tty.t list -> unit

Emit a warning on stderr if the log level is Warning or more (enabled by default). Note that this function increments the warning count regardless of the log level (even when the message is not displayed that is).

Sourceval info : ?loc:Loc.t -> ?hints:Pp_tty.t list -> Pp_tty.t list -> unit

Emit a information message on stderr. Requires a log level of Info or more, disabled by default.

Sourceval debug : ?loc:Loc.t -> ?hints:Pp_tty.t list -> Pp_tty.t list Lazy.t -> unit

The last argument to debug is lazy in order to avoid the allocation when debug messages are disabled. This isn't done with the other functions, because we don't expect other logging functions to be used in a way that impacts the program's performance, and using lazy causes added programming friction.

Sourceval emit : t -> level:Level.t -> unit

Emit a message from an existing error.

Rather than building a new Err.t, this part of the API allows you to emit a message from a previously created err value. For example, you may catch an error raised by some code, and make that a warning instead.

  let warn_on_error ~f =
    try f () with
    | Err.E e -> Err.emit e ~level:Warning
  ;;

The emit functions does check the current log level, and only emit the message if permitted it - for example, emit t ~level:Warning actually emits a warning only when log_enables ~level:Warning = true.

Emitting with level Error (resp. Warning) increments the global error count (resp. warning count), even when the log level is such that the message is not actually printed (such as in Quiet mode, for example).

Handler

To be used by command line handlers, as well as tests.

Sourceval protect : ?exn_handler:(exn -> t option) -> (unit -> 'a) -> ('a, int) Result.t

protect f will take care of running f, and catch any user error. If the exit code must be affected it is returned as an Error. This also takes care of catching uncaught exceptions and printing them to the screen. You may provide exn_handler to match on custom exceptions and turn them into Err for display and exit code. Any uncaught exception will be reported as an internal errors with a backtrace. When Err.am_running_test () is true the backtrace is redacted to avoid making expect test traces too brittle. protect starts by performing a reset of the error and warning counts with a call to reset_counts.

Sourcemodule For_test : sig ... end

Private

Sourcemodule Private : sig ... end

Private is used by Log_cli. We mean both libraries to work as companion libs. Note any of this can change without notice and without requiring a semver bump, so use at your own risk (or don't).

Deprecated

This part of the API is, or will be soon, deprecated. We have added ocamlmig annotations to help with migrating existing code.

Sourceval create_s : ?loc:Loc.t -> ?hints:Pp_tty.t list -> ?exit_code:Exit_code.t -> string -> Sexplib0.Sexp.t -> t

This is deprecated - use Err.create instead.

Sourceval raise_s : ?loc:Loc.t -> ?hints:Pp_tty.t list -> ?exit_code:Exit_code.t -> string -> Sexplib0.Sexp.t -> _

This is deprecated - use Err.raise instead.

Sourceval reraise_s : Printexc.raw_backtrace -> t -> ?loc:Loc.t -> ?hints:Pp_tty.t list -> ?exit_code:Exit_code.t -> string -> Sexplib0.Sexp.t -> _

This is deprecated - use Err.reraise_with_context instead.

Sourceval pp_of_sexp : Sexplib0.Sexp.t -> _ Pp.t

This was renamed Err.sexp.

OCaml

Innovation. Community. Security.