[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