text: // Licensed to the Apache Software Foundation (ASF) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The ASF licenses this file
  // to you under the Apache License, Version 2.0 (the
  // "License"); you may not use this file except in compliance
  // with the License.  You may obtain a copy of the License at
  //
  //   http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing,
  // software distributed under the License is distributed on an
  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  // KIND, either express or implied.  See the License for the
  // specific language governing permissions and limitations
  // under the License.
  
  #include "util/logging-support.h"
  
  #include <glob.h>
  #include <sys/stat.h>
  #include <rapidjson/document.h>
  #include <gflags/gflags.h>
  
  #include "common/logging.h"
  #include "rpc/jni-thrift-util.h"
  #include "util/jni-util.h"
  #include "util/webserver.h"
  
  #include "common/names.h"
  
  using namespace impala;
  using namespace rapidjson;
  using namespace std;
  
  DEFINE_int32(non_impala_java_vlog, 0, "(Advanced) The log level (equivalent to --v) for "
      "non-Impala Java classes (0: INFO, 1 and 2: DEBUG, 3: TRACE)");
  
  // Requires JniUtil::Init() to have been called. Called by the frontend and catalog
  // service to log messages to Glog.
  extern "C"
  JNIEXPORT void JNICALL
  Java_org_apache_impala_util_NativeLogger_Log(
      JNIEnv* env, jclass caller_class, int severity, jstring msg, jstring file,
      int line_number) {
    DCHECK(file != nullptr);
    JniUtfCharGuard filename_guard;
    RETURN_VOID_IF_ERROR(JniUtfCharGuard::create(env, file, &filename_guard));
    JniUtfCharGuard msg_guard;
    const char* str = "";
    if (msg != nullptr) {
      RETURN_VOID_IF_ERROR(JniUtfCharGuard::create(env, msg, &msg_guard));
      str = msg_guard.get();
    }
  
    int log_level = google::INFO;
    switch (severity) {
      case TLogLevel::VLOG:
      case TLogLevel::VLOG_2:
      case TLogLevel::VLOG_3:
        log_level = google::INFO;
        break;
      case TLogLevel::INFO:
        log_level = google::INFO;
        break;
      case TLogLevel::WARN:
        log_level = google::WARNING;
        break;
      case TLogLevel::ERROR:
        log_level = google::ERROR;
        break;
      case TLogLevel::FATAL:
        log_level = google::FATAL;
        break;
      default:
        DCHECK(false) << "Unrecognised TLogLevel: " << log_level;
    }
    google::LogMessage(filename_guard.get(), line_number, log_level).stream() << str;
  }
  
  namespace {
  // Defaults to startup flag --v. FLAGS_v can be overriden at runtime for
  // debugging, so we save the original value here in case we need to restore
  // the defaults. Set in GetThriftBackendGflags().
  int FLAGS_v_original_value;
  
  static jclass log4j_logger_class_;
  // Jni method descriptors corresponding to getLogLevel() and setLogLevel() operations.
  static jmethodID get_log_level_method; // GlogAppender.getLogLevel()
  static jmethodID set_log_level_method; // GlogAppender.setLogLevel()
  static jmethodID reset_log_levels_method; // GlogAppender.resetLogLevels()
  
  // Helper method to set a message into a member in the document
  void AddDocumentMember(const string& message, const char* member,
      Document* document) {
    Value key(member, document->GetAllocator());
    Value output(message.c_str(), document->GetAllocator());
    document->AddMember(key, output, document->GetAllocator());
  }
  
  template<class F>
  Webserver::UrlCallback MakeCallback(const F& fnc, bool display_log4j_handlers) {
    return [fnc, display_log4j_handlers](const auto& req, auto* doc) {
      // Display log4j log level handlers only when display_log4j_handlers is true.
      if (display_log4j_handlers) AddDocumentMember("true", "include_log4j_handlers", doc);
      (*fnc)(req, doc);
    };
  }
  
  void InitDynamicLoggingSupport() {
    JNIEnv* env = JniUtil::GetJNIEnv();
    ABORT_IF_ERROR(JniUtil::GetGlobalClassRef(env, "org/apache/impala/util/GlogAppender",
          &log4j_logger_class_));
    JniMethodDescriptor get_log_level_method_desc =
        {"getLogLevel", "([B)Ljava/lang/String;", &get_log_level_method};
    JniMethodDescriptor set_log_level_method_desc =
        {"setLogLevel", "([B)Ljava/lang/String;", &set_log_level_method};
    JniMethodDescriptor reset_log_level_method_desc =
        {"resetLogLevels", "()V", &reset_log_levels_method};
    ABORT_IF_ERROR(JniUtil::LoadStaticJniMethod(
        env, log4j_logger_class_, &get_log_level_method_desc));
    ABORT_IF_ERROR(JniUtil::LoadStaticJniMethod(
        env, log4j_logger_class_, &set_log_level_method_desc));
    ABORT_IF_ERROR(JniUtil::LoadStaticJniMethod(
        env, log4j_logger_class_, &reset_log_level_method_desc));
  
    FLAGS_v_original_value = FLAGS_v;
    // Register a validator function for FLAGS_v to make sure it is in the [0-3]
    // range. This is called everytime we try to override FLAGS_v using
    // SetCommandLineOption().
    google::RegisterFlagValidator(&FLAGS_v,
        [](const char* flagname, int value) { return value >= 0 && value <= 3; });
  }
  
  // Helper method to get the log level of given Java class. It is a JNI wrapper around
  // GlogAppender.getLogLevel().
  Status GetJavaLogLevel(const TGetJavaLogLevelParams& params, string* result) {
    return JniCall::static_method(log4j_logger_class_, get_log_level_method)
        .with_thrift_arg(params).Call(result);
  }
  
  Status ResetJavaLogLevels() {
    return JniCall::static_method(log4j_logger_class_, reset_log_levels_method).Call();
  }
  
  // Callback handler for /get_java_loglevel.
  void GetJavaLogLevelCallback(const Webserver::WebRequest& req, Document* document) {
    const auto& args = req.parsed_args;
    Webserver::ArgumentMap::const_iterator log_getclass = args.find("class");
    if (log_getclass == args.end() || log_getclass->second.empty()) {
      AddDocumentMember("Invalid input class name", "error", document);
      return;
    }
    string result;
    TGetJavaLogLevelParams params;
    params.__set_class_name(log_getclass->second);
    Status status = GetJavaLogLevel(params, &result);
    if (!status.ok()) {
      AddDocumentMember(status.GetDetail(), "error", document);
      return;
    }
    if (result.empty()) {
      AddDocumentMember("Invalid input class name", "error", document);
      return;
    }
    AddDocumentMember(result, "get_java_loglevel_result", document);
  }
  
  // Callback handler for /set_java_loglevel.
  void SetJavaLogLevelCallback(const Webserver::WebRequest& req, Document* document) {
    const auto& args = req.parsed_args;
    Webserver::ArgumentMap::const_iterator classname = args.find("class");
    Webserver::ArgumentMap::const_iterator level = args.find("level");
    if (classname == args.end() || classname->second.empty() ||
        level == args.end() || level->second.empty()) {
      AddDocumentMember("Invalid input parameters. Either class name or log level "
          "is empty.", "error", document);
      return;
    }
    TSetJavaLogLevelParams params;
    string result;
    params.__set_class_name(classname->second);
    params.__set_log_level(level->second);
    Status status = JniCall::static_method(log4j_logger_class_, set_log_level_method)
      .with_thrift_arg(params).Call(&result);
    if (!status.ok()) {
      AddDocumentMember(status.GetDetail(), "error", document);
      return;
    }
    if (result.empty()) {
      AddDocumentMember("Invalid input parameters. Either class name or log level "
          "is empty.", "error", document);
      return;
    }
    AddDocumentMember(result, "set_java_loglevel_result", document);
  }
  
  // Callback handler for /reset_java_loglevel.
  void ResetJavaLogLevelCallback(const Webserver::WebRequest& req, Document* document) {
    Status status = ResetJavaLogLevels();
    if (!status.ok()) {
      AddDocumentMember(status.GetDetail(), "error", document);
      return;
    }
    AddDocumentMember("Java log levels reset.", "reset_java_loglevel_result", document);
  }
  
  // Callback handler for /set_glog_level
  void SetGlogLevelCallback(const Webserver::WebRequest& req, Document* document) {
    const auto& args = req.parsed_args;
    Webserver::ArgumentMap::const_iterator glog_level = args.find("glog");
    if (glog_level == args.end() || glog_level->second.empty()) {
      AddDocumentMember("Bad glog level input. Valid inputs are integers in the "
          "range [0-3].", "error", document);
      return;
    }
    string new_log_level = google::SetCommandLineOption("v", glog_level->second.data());
    if (new_log_level.empty()) {
      AddDocumentMember("Bad glog level input. Valid inputs are integers in the "
          "range [0-3].", "error", document);
      return;
    }
    AddDocumentMember(new_log_level, "set_glog_level_result", document);
    AddDocumentMember(to_string(FLAGS_v_original_value), "default_glog_level", document);
  }
  
  // Callback handler for /reset_glog_level
  void ResetGlogLevelCallback(const Webserver::WebRequest& req, Document* document) {
    string new_log_level = google::SetCommandLineOption("v",
        to_string(FLAGS_v_original_value).data());
    AddDocumentMember(new_log_level, "reset_glog_level_result", document);
  }
  
  }
  
  namespace impala {
  
  void InitJvmLoggingSupport() {
    JNIEnv* env = JniUtil::GetJNIEnv();
    JNINativeMethod nm;
    jclass native_backend_cl = env->FindClass("org/apache/impala/util/NativeLogger");
    nm.name = const_cast<char*>("Log");
    nm.signature = const_cast<char*>("(ILjava/lang/String;Ljava/lang/String;I)V");
    nm.fnPtr = reinterpret_cast<void*>(::Java_org_apache_impala_util_NativeLogger_Log);
    env->RegisterNatives(native_backend_cl, &nm, 1);
    ABORT_IF_EXC(env);
    InitDynamicLoggingSupport();
  }
  
  TLogLevel::type FlagToTLogLevel(int flag) {
    switch (flag) {
      case 0: return TLogLevel::INFO;
      case 1: return TLogLevel::VLOG;
      case 2: return TLogLevel::VLOG_2;
      case 3:
      default: return TLogLevel::VLOG_3;
    }
  }
  
  void LoggingSupport::DeleteOldLogs(const string& path_pattern, int max_log_files) {
    // Ignore bad input or disable log rotation
    if (max_log_files <= 0) return;
  
    // Map capturing mtimes, oldest files first
    typedef map<time_t, string> LogFileMap;
  
    LogFileMap log_file_mtime;
    glob_t result;
    int glob_ret = glob(path_pattern.c_str(), GLOB_TILDE, NULL, &result);
    if (glob_ret != 0) {
      if (glob_ret != GLOB_NOMATCH) {
        LOG(ERROR) << "glob failed in LoggingSupport::DeleteOldLogs on " << path_pattern
                   << " with ret = " << glob_ret;
      }
      globfree(&result);
      return;
    }
  
    for (size_t i = 0; i < result.gl_pathc; ++i) {
      // Get the mtime for each match
      struct stat stat_val;
      if (stat(result.gl_pathv[i], &stat_val) != 0) {
        LOG(ERROR) << "Could not read last-modified-timestamp for log file "
                   << result.gl_pathv[i] << ", will not delete (error was: "
                   << strerror(errno) << ")";
        continue;
      }
      log_file_mtime[stat_val.st_mtime] = result.gl_pathv[i];
    }
    globfree(&result);
  
    // Iterate over the map and remove oldest log files first when too many
    // log files exist
    if (log_file_mtime.size() <= max_log_files) return;
    int files_to_delete = log_file_mtime.size() - max_log_files;
    DCHECK_GT(files_to_delete, 0);
    for (LogFileMap::const_reference val: log_file_mtime) {
      if (unlink(val.second.c_str()) == 0) {
        LOG(INFO) << "Old log file deleted during log rotation: " << val.second;
      } else {
        LOG(ERROR) << "Failed to delete old log file: "
                   << val.second << "(error was: " << strerror(errno) << ")";
      }
      if (--files_to_delete == 0) break;
    }
  }
  
  void RegisterLogLevelCallbacks(Webserver* webserver, bool register_log4j_handlers) {
    webserver->RegisterUrlCallback("/log_level", "log_level.tmpl",
        MakeCallback([](const Webserver::WebRequest& req, Document* document){},
        register_log4j_handlers), true);
    webserver->RegisterUrlCallback("/set_glog_level", "log_level.tmpl",
        MakeCallback(&SetGlogLevelCallback, register_log4j_handlers), false);
    webserver->RegisterUrlCallback("/reset_glog_level", "log_level.tmpl",
        MakeCallback(&ResetGlogLevelCallback, register_log4j_handlers), false);
    if (!register_log4j_handlers) return;
    webserver->RegisterUrlCallback("/get_java_loglevel", "log_level.tmpl",
        MakeCallback(&GetJavaLogLevelCallback, register_log4j_handlers), false);
    webserver->RegisterUrlCallback("/set_java_loglevel", "log_level.tmpl",
        MakeCallback(&SetJavaLogLevelCallback, register_log4j_handlers), false);
    webserver->RegisterUrlCallback("/reset_java_loglevel", "log_level.tmpl",
        MakeCallback(&ResetJavaLogLevelCallback, register_log4j_handlers), false);
  }
  
  }
  