[Libguestfs] [PATCH 4/4] OCaml tools: output messages into JSON for machine readable
Richard W.M. Jones
rjones at redhat.com
Mon Mar 25 14:13:04 UTC 2019
On Fri, Mar 22, 2019 at 04:33:43PM +0100, Pino Toscano wrote:
> When the machine readable mode is enabled, print all the messages
> (progress, info, warning, and errors) also as JSON in the machine
> readable stream: this way, users can easily parse the status of the
> OCaml tool, and report that back.
>
> The formatting of the current date time into the RFC 3999 format is done
> in C, because of the lack of OCaml APIs for this.
> ---
> common/mltools/Makefile.am | 2 +-
> common/mltools/tools_utils-c.c | 51 ++++++++++++++++++++++++++++++++++
> common/mltools/tools_utils.ml | 16 +++++++++++
> lib/guestfs.pod | 19 +++++++++++++
> 4 files changed, 87 insertions(+), 1 deletion(-)
>
> diff --git a/common/mltools/Makefile.am b/common/mltools/Makefile.am
> index 37d10e610..ee8c319fd 100644
> --- a/common/mltools/Makefile.am
> +++ b/common/mltools/Makefile.am
> @@ -45,12 +45,12 @@ SOURCES_MLI = \
>
> SOURCES_ML = \
> getopt.ml \
> + JSON.ml \
> tools_utils.ml \
> URI.ml \
> planner.ml \
> registry.ml \
> regedit.ml \
> - JSON.ml \
> JSON_parser.ml \
> curl.ml \
> checksums.ml \
> diff --git a/common/mltools/tools_utils-c.c b/common/mltools/tools_utils-c.c
> index 553aa6631..977f932d9 100644
> --- a/common/mltools/tools_utils-c.c
> +++ b/common/mltools/tools_utils-c.c
> @@ -23,6 +23,8 @@
> #include <unistd.h>
> #include <errno.h>
> #include <error.h>
> +#include <time.h>
> +#include <string.h>
>
> #include <caml/alloc.h>
> #include <caml/fail.h>
> @@ -41,6 +43,7 @@ extern value guestfs_int_mllib_inspect_decrypt (value gv, value gpv, value keysv
> extern value guestfs_int_mllib_set_echo_keys (value unitv);
> extern value guestfs_int_mllib_set_keys_from_stdin (value unitv);
> extern value guestfs_int_mllib_open_out_channel_from_fd (value fdv);
> +extern value guestfs_int_mllib_rfc3999_date_time_string (value unitv);
>
> /* Interface with the guestfish inspection and decryption code. */
> int echo_keys = 0;
> @@ -120,3 +123,51 @@ guestfs_int_mllib_open_out_channel_from_fd (value fdv)
>
> CAMLreturn (caml_alloc_channel (chan));
> }
> +
> +value
> +guestfs_int_mllib_rfc3999_date_time_string (value unitv)
> +{
> + CAMLparam1 (unitv);
> + char buf[64];
> + struct timespec ts;
> + struct tm tm;
> + size_t ret;
> + size_t total = 0;
> +
> + if (clock_gettime (CLOCK_REALTIME, &ts) == -1)
> + unix_error (errno, (char *) "clock_gettime", Val_unit);
> +
> + if (localtime_r (&ts.tv_sec, &tm) == NULL)
> + unix_error (errno, (char *) "localtime_r", caml_copy_int64 (ts.tv_sec));
> +
> + /* Sadly strftime does not support nanoseconds, so what we do is:
> + * - stringify everything before the nanoseconds
> + * - print the nanoseconds
> + * - stringify the rest (i.e. the timezone)
> + * then place ':' between the hours, and the minutes of the
> + * timezone offset.
> + */
> +
> + ret = strftime (buf, sizeof (buf), "%Y-%m-%dT%H:%M:%S.", &tm);
> + if (ret == 0)
> + unix_error (errno, (char *) "strftime", Val_unit);
> + total += ret;
> +
> + ret = snprintf (buf + total, sizeof (buf) - total, "%09ld", ts.tv_nsec);
> + if (ret == 0)
> + unix_error (errno, (char *) "sprintf", caml_copy_int64 (ts.tv_nsec));
> + total += ret;
> +
> + ret = strftime (buf + total, sizeof (buf) - total, "%z", &tm);
> + if (ret == 0)
> + unix_error (errno, (char *) "strftime", Val_unit);
> + total += ret;
> +
> + /* Move the timezone minutes one character to the right, moving the
> + * null character too.
> + */
> + memmove (buf + total - 1, buf + total - 2, 3);
> + buf[total - 2] = ':';
> +
> + CAMLreturn (caml_copy_string (buf));
> +}
> diff --git a/common/mltools/tools_utils.ml b/common/mltools/tools_utils.ml
> index 3c54cd4a0..1a1d11075 100644
> --- a/common/mltools/tools_utils.ml
> +++ b/common/mltools/tools_utils.ml
> @@ -33,6 +33,7 @@ external c_inspect_decrypt : Guestfs.t -> int64 -> (string * key_store_key) list
> external c_set_echo_keys : unit -> unit = "guestfs_int_mllib_set_echo_keys" "noalloc"
> external c_set_keys_from_stdin : unit -> unit = "guestfs_int_mllib_set_keys_from_stdin" "noalloc"
> external c_out_channel_from_fd : int -> out_channel = "guestfs_int_mllib_open_out_channel_from_fd"
> +external c_rfc3999_date_time_string : unit -> string = "guestfs_int_mllib_rfc3999_date_time_string"
>
> type machine_readable_fn = {
> pr : 'a. ('a, unit, string, unit) format4 -> 'a;
> @@ -85,12 +86,24 @@ let ansi_magenta ?(chan = stdout) () =
> let ansi_restore ?(chan = stdout) () =
> if colours () || istty chan then output_string chan "\x1b[0m"
>
> +let log_as_json msgtype msg =
> + match machine_readable () with
> + | None -> ()
> + | Some { pr } ->
> + let json = [
> + "message", JSON.String msg;
> + "timestamp", JSON.String (c_rfc3999_date_time_string ());
> + "type", JSON.String msgtype;
> + ] in
> + pr "%s\n" (JSON.string_of_doc ~fmt:JSON.Compact json)
> +
> (* Timestamped progress messages, used for ordinary messages when not
> * --quiet.
> *)
> let start_t = Unix.gettimeofday ()
> let message fs =
> let display str =
> + log_as_json "message" str;
> if not (quiet ()) then (
> let t = sprintf "%.1f" (Unix.gettimeofday () -. start_t) in
> printf "[%6s] " t;
> @@ -105,6 +118,7 @@ let message fs =
> (* Error messages etc. *)
> let error ?(exit_code = 1) fs =
> let display str =
> + log_as_json "error" str;
> let chan = stderr in
> ansi_red ~chan ();
> wrap ~chan (sprintf (f_"%s: error: %s") prog str);
> @@ -123,6 +137,7 @@ let error ?(exit_code = 1) fs =
>
> let warning fs =
> let display str =
> + log_as_json "warning" str;
> let chan = stdout in
> ansi_blue ~chan ();
> wrap ~chan (sprintf (f_"%s: warning: %s") prog str);
> @@ -133,6 +148,7 @@ let warning fs =
>
> let info fs =
> let display str =
> + log_as_json "info" str;
> let chan = stdout in
> ansi_magenta ~chan ();
> wrap ~chan (sprintf (f_"%s: %s") prog str);
> diff --git a/lib/guestfs.pod b/lib/guestfs.pod
> index f11028466..3c1d635c5 100644
> --- a/lib/guestfs.pod
> +++ b/lib/guestfs.pod
> @@ -3279,6 +3279,25 @@ Some of the tools support a I<--machine-readable> option, which is
> generally used to make the output more machine friendly, for easier
> parsing for example. By default, this output goes to stdout.
>
> +When using the I<--machine-readable> option, the progress,
> +information, warning, and error messages are also printed in JSON
> +format for easier log tracking. Thus, it is highly recommended to
> +redirect the machine-readable output to a different stream. The
> +format of these JSON messages is like the following (actually printed
> +within a single line, below it is indented for readability):
> +
> + {
> + "message": "Finishing off",
> + "timestamp": "2019-03-22T14:46:49.067294446+01:00",
> + "type": "message"
> + }
> +
> +C<type> can be: C<message> for progress messages, C<info> for
> +information messages, C<warning> for warning messages, and C<error>
> +for error message.
> +C<timestamp> is the L<RFC 3999|https://www.ietf.org/rfc/rfc3339.txt>
> +timestamp of the message.
> +
> In addition to that, a subset of these tools support an extra string
> passed to the I<--machine-readable> option: this string specifies
> where the machine-readable output will go.
> --
Yes this looks fine (and could go upstream independent of 3/4).
Such a shame that strftime can't format nanoseconds though :-(
ACK
Rich.
--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
virt-df lists disk usage of guests without needing to install any
software inside the virtual machine. Supports Linux and Windows.
http://people.redhat.com/~rjones/virt-df/
More information about the Libguestfs
mailing list