blob: 932698b95a45ac79fb642c146c798e0c4c3b777f [file] [log] [blame]
// 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);
}
}