// file      : bbot/diagnostics.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <bbot/diagnostics.hxx>

#ifdef __linux__
#  define BBOT_SYSTEMD
#endif

#ifdef BBOT_SYSTEMD
#  include <dlfcn.h>
#  include <syslog.h> // LOG_*
#endif

#include <bbot/utility.hxx>

using namespace std;
using namespace butl;

namespace bbot
{
  // Diagnostics verbosity level.
  //
  uint16_t verb;

  // Diagnostic facility, project specifics.
  //

  void simple_prologue_base::
  operator() (const diag_record& r) const
  {
    if (type_ != nullptr)
      r << type_;

    if (name_ != nullptr)
      r << name_;

    if (data_ != nullptr)
      r << '(' << data_ << ')';

    if (name_ != nullptr || data_ != nullptr)
      r << ": ";
  }

  const char* trace_type = "trace: ";
  const char* trace_indent = "\n  ";

  trace_mark_base::
  trace_mark_base (const char* name, const char* data)
      : basic_mark_base (trace_type, trace_indent, name, data)
  {
  }

  basic_mark error ("error: ");
  basic_mark warn  ("warning: ");
  basic_mark info  ("info: ");
  basic_mark text  (nullptr);
  fail_mark  fail  ("error: ");

  const fail_end endf;

#ifndef BBOT_SYSTEMD
  void
  systemd_diagnostics (bool)
  {
    fail << "systemd mode only supported on Linux";
  }
#else

  static const char systemd_indent[] = "\n";

  static const char libsystemd[] = "libsystemd.so.0";
  static int (*sd_journal_print) (int priority, const char *format, ...);

  static void (*default_writer) (const diag_record&);

  static void
  systemd_writer (const diag_record& dr)
  {
    const string& m (dr.os.str ());
    const char* s (m.c_str ());

    // Map "<N>..." prefix to priority. Absent is info.
    //
    int p (LOG_INFO);
    if (m.size () > 2 && m[0] == '<' && m[2] == '>')
    {
      char c (m[1]);
      p = (c == '0' ? LOG_EMERG   :
           c == '1' ? LOG_ALERT   :
           c == '2' ? LOG_CRIT    :
           c == '3' ? LOG_ERR     :
           c == '4' ? LOG_WARNING :
           c == '5' ? LOG_NOTICE  :
           c == '6' ? LOG_INFO    :
           c == '7' ? LOG_DEBUG   : LOG_ALERT);
      s += 3;
    }

    if (sd_journal_print (p, "%s", s) != 0)
      default_writer (dr);
  }

  void
  systemd_diagnostics (bool with_critical)
  {
    trace_indent    =
      fail.indent_  =
      error.indent_ =
      warn.indent_  =
      info.indent_  =
      text.indent_  = systemd_indent;

    // We don't always see colorized output so keep 'error: ', etc.
    //
    // Note that lucky for us the default level is info (<6>) and so we can
    // omit it. Failed that, we would see the '<6>' prefixes in "followup"
    // entries (unless we clean them up in systemd_writer() or some such).
    //
    fail.type_  = with_critical ? "<2>fatal: " : "<3>error: ";
    error.type_ = "<3>error: ";
    warn.type_  = "<4>warning: ";
    info.type_  = nullptr;
    trace_type  = "<7>";

    // Using sd_journal_print() allows us to print multi-line records nicely.
    // However, requiring libsystemd-dev (or equivalent) is a bit of a schlep
    // so we use the dynamic loading trick.
    //
    // Note that we could also fallback to stderr output (which is how it
    // worked before) if we cannot load the library or resolve the symbol for
    // some reason. But for now we fail to keep things simple.
    //
    void* libsd (dlopen (libsystemd, RTLD_LAZY | RTLD_GLOBAL));

    if (libsd == nullptr)
      fail << "unable to load " << libsystemd << ": " << dlerror ();

    sd_journal_print =
      function_cast<decltype(sd_journal_print)> (
        dlsym (libsd, "sd_journal_print"));

    if (sd_journal_print == nullptr)
      fail << "unable to lookup sd_journal_print() in " << libsystemd << ": "
           << dlerror ();

    default_writer = diag_record::writer;
    diag_record::writer = &systemd_writer;
  }
#endif

  // tracer
  //
  void tracer::
  operator() (const char* const args[], size_t n) const
  {
    if (verb >= 3)
    {
      diag_record dr (*this);
      process::print (dr.os, args, n);
    }
  }
}
