blob: c1524109cfa5d899e372b3b3bf159efc64ce08df [file]
// 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 "common/logging.h"
#include <glob.h>
#include <stdio.h>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <gutil/strings/substitute.h>
#include "common/thread-debug-info.h"
#include "kudu/util/flags.h"
#include "util/container-util.h"
#include "util/debug-util.h"
#include "util/error-util.h"
#include "util/filesystem-util.h"
#include "util/logging-support.h"
#include "util/redactor.h"
#include "util/test-info.h"
#include "common/names.h"
DECLARE_string(redaction_rules_file);
DECLARE_string(log_filename);
DECLARE_bool(redirect_stdout_stderr);
DECLARE_uint32(max_log_size);
DECLARE_bool(log_rotation_match_pid);
using boost::uuids::random_generator;
using impala::TUniqueId;
namespace {
bool logging_initialized = false;
// A 0 unique id, which indicates that one has not been set.
const TUniqueId ZERO_UNIQUE_ID;
string last_info_log_path = "";
string last_error_log_path = "";
// Prepends fragment id, when available. If unavailable, looks
// for query id. If unavailable, prepends nothing.
void PrependFragment(string* s, bool* changed) {
impala::ThreadDebugInfo* tdi = impala::GetThreadDebugInfo();
if (tdi != nullptr) {
for (const auto& id : { tdi->GetInstanceId(), tdi->GetQueryId() }) {
if (id == ZERO_UNIQUE_ID) continue;
s->insert(0, PrintId(id) + "] ");
if (changed != nullptr) *changed = true;
return;
}
}
}
// Manipulates log messages by:
// - Applying redaction rules (if necessary)
// - Prepending fragment id (if available)
void MessageListener(string* s, bool* changed) {
if (!FLAGS_redaction_rules_file.empty()) {
impala::Redact(s, changed);
}
PrependFragment(s, changed);
}
}
mutex logging_mutex;
// Resolve 'symlink_path' into its 'canonical_path'.
// If 'symlink_path' is not a symlink, copy it to 'canonical_path'.
impala::Status ResolveLogSymlink(const string& symlink_path, string& canonical_path) {
bool is_symbolic_link;
string resolved_path;
RETURN_IF_ERROR(impala::FileSystemUtil::IsSymbolicLink(
symlink_path, &is_symbolic_link, &resolved_path));
canonical_path = is_symbolic_link ? resolved_path : symlink_path;
return impala::Status::OK();
}
// We specifically target the base file name created by glog.
// Glog's default base file name follow this pattern:
// "<program name>.<hostname>.<user name>.log.<severity level>.<date>-<time>.<pid>"
// IMPALA-12595: FLAGS_log_rotation_match_pid is added to control whether to match against
// PID or not.
inline string GlobPatternForLog(google::LogSeverity severity) {
if (FLAGS_log_rotation_match_pid) {
return strings::Substitute("$0/$1*.log.$2.*.$3", FLAGS_log_dir,
google::ProgramInvocationShortName(), google::GetLogSeverityName(severity),
getpid());
} else {
return strings::Substitute("$0/$1*.log.$2.*", FLAGS_log_dir,
google::ProgramInvocationShortName(), google::GetLogSeverityName(severity));
}
}
impala::Status GetLatestCanonicalLogPath(
google::LogSeverity severity, std::string& log_path) {
log_path = "";
string path_pattern = GlobPatternForLog(severity);
glob_t result;
int glob_ret = glob(path_pattern.c_str(), GLOB_TILDE, NULL, &result);
if (glob_ret != 0) {
string msg;
if (glob_ret == GLOB_NOMATCH) {
msg = Substitute("No match found for glob pattern $0", path_pattern);
} else if (glob_ret == GLOB_NOSPACE) {
msg = Substitute("Running out of memory for glob pattern $0", path_pattern);
} else if (glob_ret == GLOB_ABORTED) {
msg = Substitute("Read error for glob pattern $0", path_pattern);
} else {
msg = Substitute(
"glob failed in impala::GetLatestCanonicalLogPath on $0 with ret = $1",
path_pattern, glob_ret);
}
globfree(&result);
return impala::Status(impala::ErrorMsg(impala::TErrorCode::RUNTIME_ERROR, msg));
}
// Find the largest full file path which is also the latest.
for (size_t i = 0; i < result.gl_pathc; ++i) {
if (i == 0 || log_path.compare(result.gl_pathv[i]) < 0) {
log_path = result.gl_pathv[i];
}
}
globfree(&result);
return impala::Status::OK();
}
bool impala::HasLog(google::LogSeverity severity) {
string log_path;
Status status = GetLatestCanonicalLogPath(severity, log_path);
return status.ok();
}
// The main implementation of AttachStdoutStderr().
// Caller must hold lock over logging_mutex.
impala::Status AttachStdoutStderrLocked() {
// Redirect stdout to INFO log and stderr to ERROR log.
// Needs to be done after InitGoogleLogging, to get the INFO/ERROR file paths.
string info_log_path, error_log_path;
RETURN_IF_ERROR(GetLatestCanonicalLogPath(google::INFO, info_log_path));
RETURN_IF_ERROR(GetLatestCanonicalLogPath(google::ERROR, error_log_path));
bool info_log_rotated = last_info_log_path != info_log_path;
bool error_log_rotated = last_error_log_path != error_log_path;
if (info_log_rotated != error_log_rotated) {
// Since we're not holding glog's log_mutex during log file lookup, it is
// possible that glog rotates the INFO and ERROR log independently in between our
// lookup. Log a warning and continue if this happens. The next
// AttachStdoutStderrLocked call should redirect the stream to the newer file.
LOG(WARNING) << "INFO and ERROR log is not rotated at the same time "
<< "(info_log_rotated=" << info_log_rotated << ", "
<< "error_log_rotated=" << error_log_rotated << "). "
<< "STDOUT/STDERR might still write to an older log file.";
}
if (info_log_rotated) {
// Print to stdout before redirecting so people looking for these logs in the standard
// place know where to look.
cout << "Redirecting stdout to " << info_log_path << endl;
// TODO: how to handle these errors? Maybe abort the process?
if (freopen(info_log_path.c_str(), "a", stdout) == NULL) {
cout << "Could not redirect stdout: " << impala::GetStrErrMsg();
}
last_info_log_path = info_log_path;
}
if (error_log_rotated) {
// Similar to stdout, do the same thing for stderr.
cerr << "Redirecting stderr to " << error_log_path << endl;
if (freopen(error_log_path.c_str(), "a", stderr) == NULL) {
cerr << "Could not redirect stderr: " << impala::GetStrErrMsg();
}
last_error_log_path = error_log_path;
}
return impala::Status::OK();
}
void impala::InitGoogleLoggingSafe(const char* arg) {
lock_guard<mutex> logging_lock(logging_mutex);
if (logging_initialized) return;
if (!FLAGS_log_filename.empty()) {
for (int severity = google::INFO; severity <= google::FATAL; ++severity) {
google::SetLogSymlink(severity, FLAGS_log_filename.c_str());
}
}
// This forces our logging to use /tmp rather than looking for a
// temporary directory if none is specified. This is done so that we
// can reliably construct the log file name without duplicating the
// complex logic that glog uses to guess at a temporary dir.
if (FLAGS_log_dir.empty()) {
FLAGS_log_dir = "/tmp";
}
// Don't double log to stderr on any threshold.
FLAGS_stderrthreshold = google::FATAL + 1;
if (RedirectStdoutStderr()) {
// We will be redirecting stdout/stderr to INFO/LOG so override any glog settings
// that log to stdout/stderr...
FLAGS_logtostderr = false;
FLAGS_alsologtostderr = false;
}
if (!FLAGS_logtostderr) {
// Verify that a log file can be created in log_dir by creating a tmp file.
stringstream ss;
random_generator uuid_generator;
ss << FLAGS_log_dir << "/" << "impala_test_log." << uuid_generator();
const string file_name = ss.str();
ofstream test_file(file_name.c_str());
if (!test_file.is_open()) {
stringstream error_msg;
error_msg << "Could not open file in log_dir " << FLAGS_log_dir;
perror(error_msg.str().c_str());
// Unlock the mutex before exiting the program to avoid mutex d'tor assert.
logging_mutex.unlock();
exit(1);
}
remove(file_name.c_str());
}
google::InitGoogleLogging(arg);
google::InstallLogMessageListenerFunction(MessageListener);
// Needs to be done after InitGoogleLogging
if (FLAGS_log_filename.empty()) {
FLAGS_log_filename = google::ProgramInvocationShortName();
}
if (RedirectStdoutStderr()) {
// The log files are created on first use, log something to each and flush before
// redirecting.
LOG(INFO) << "stdout will be logged to this file.";
LOG(ERROR) << "stderr will be logged to this file.";
google::FlushLogFiles(google::GLOG_INFO);
Status status = AttachStdoutStderrLocked();
if (!status.ok()) {
LOG(ERROR) << "Failed to attach STDOUT/STDERR: " << status.GetDetail();
}
}
logging_initialized = true;
}
void impala::AttachStdoutStderr() {
lock_guard<mutex> logging_lock(logging_mutex);
Status status = AttachStdoutStderrLocked();
if (!status.ok()) {
LOG(ERROR) << "Failed to attach STDOUT/STDERR: " << status.GetDetail();
}
}
bool impala::CheckLogSize(bool log_error) {
lock_guard<mutex> logging_lock(logging_mutex);
// FLAGS_max_log_size is measured in megabytes. Thus, we convert it to bytes.
uintmax_t max_log_size = FLAGS_max_log_size * 1024 * 1024;
int log_to_check[2] = {google::INFO, google::ERROR};
bool max_log_file_exceeded = false;
Status status;
int log_level = log_to_check[0];
for (int i : log_to_check) {
uintmax_t file_size = 0;
log_level = i;
string log_path;
status = GetLatestCanonicalLogPath(log_level, log_path);
if (!status.ok()) break;
status = FileSystemUtil::ApproximateFileSize(log_path, file_size);
if (!status.ok()) break;
max_log_file_exceeded |= file_size > max_log_size;
}
if (!status.ok()) {
if (log_error) {
LOG(ERROR) << "Failed to check log file size for log level "
<< (log_level == google::INFO ? "INFO" : "ERROR") << ": "
<< status.GetDetail();
}
return false;
}
return max_log_file_exceeded;
}
void impala::ForceRotateLog() {
lock_guard<mutex> logging_lock(logging_mutex);
google::SetLogFilenameExtension(".cut");
google::SetLogFilenameExtension("");
LOG(INFO) << "INFO log rotated by Impala due to max_log_size exceeded.";
LOG(ERROR) << "ERROR log rotated by Impala due to max_log_size exceeded.";
google::FlushLogFiles(google::GLOG_INFO);
}
bool impala::RedirectStdoutStderr() {
return FLAGS_redirect_stdout_stderr && !TestInfo::is_test();
}
void impala::GetFullLogFilename(google::LogSeverity severity, string* filename) {
stringstream ss;
ss << FLAGS_log_dir << "/" << FLAGS_log_filename << "."
<< google::GetLogSeverityName(severity);
*filename = ss.str();
}
void impala::ShutdownLogging() {
// This method may only correctly be called once (which this lock does not
// enforce), but this lock protects against concurrent calls with
// InitGoogleLoggingSafe
lock_guard<mutex> logging_lock(logging_mutex);
google::ShutdownGoogleLogging();
}
void impala::LogCommandLineFlags() {
LOG(INFO) << "Flags (see also /varz are on debug webserver):" << endl
<< kudu::CommandlineFlagsIntoString(kudu::EscapeMode::NONE, kudu::Selection::ALL);
vector<google::CommandLineFlagInfo> flags;
google::GetAllFlags(&flags, true);
stringstream ss;
for (const auto& flag: flags) {
if (flag.hidden) {
string flag_value = CheckFlagAndRedact(flag, kudu::EscapeMode::NONE);
ss << "--" << flag.name << "=" << flag_value << "\n";
}
}
string experimental_flags = ss.str();
if (!experimental_flags.empty()) {
LOG(WARNING) << "Experimental flags:" << endl << experimental_flags;
}
}
void impala::CheckAndRotateLogFiles(int max_log_files) {
lock_guard<mutex> logging_lock(logging_mutex);
// Ignore bad input or disable log rotation
if (max_log_files <= 1) return;
// Check log files for all severities
for (int severity = 0; severity < google::NUM_SEVERITIES; ++severity) {
// Build glob pattern for target severity
// e.g. /tmp/impalad.*.log.INFO.*.5477
string fname = GlobPatternForLog(severity);
impala::LoggingSupport::DeleteOldLogs(fname, max_log_files);
}
}
static const uint32_t ONE_BILLION = 1000000000;
// Print the value in base 10 by converting v into parts that are base
// 1 billion (large multiple of 10 that's easy to work with).
ostream& impala::operator<<(ostream& os, const __int128_t& val) {
__int128_t v = val;
if (v == 0) {
os << "0";
return os;
}
if (v < 0) {
v = -v;
os << "-";
}
// 1B^5 covers the range for __int128_t
// parts[0] is the least significant place.
uint32_t parts[5];
int index = 0;
while (v > 0) {
parts[index++] = v % ONE_BILLION;
v /= ONE_BILLION;
}
--index;
// Accumulate into a temporary stringstream so format options on 'os' do
// not mess up printing val.
// TODO: This is likely pretty expensive with the string copies. We don't
// do this in paths we care about currently but might need to revisit.
stringstream ss;
ss << parts[index];
for (int i = index - 1; i >= 0; --i) {
// The remaining parts need to be padded with leading zeros.
ss << setfill('0') << setw(9) << parts[i];
}
os << ss.str();
return os;
}