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.
--
2.20.1