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
> #include
> #include
> +#include
> +#include
>
> #include
> #include
> @@ -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, ) == -1)
> +unix_error (errno, (char *) "clock_gettime", Val_unit);
> +
> + if (localtime_r (_sec, ) == 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.", );
> + 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", );
> + 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