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/ _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://www.redhat.com/mailman/listinfo/libguestfs