MXS-2005: Move Logger into maxutils
Moved the logger into the maxutils library as it is a generic utility. Also moved mxs_strerror into it as it depends on it.
This commit is contained in:
29
maxutils/maxbase/include/maxbase/error.h
Normal file
29
maxutils/maxbase/include/maxbase/error.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
/*
|
||||
* Copyright (c) 2016 MariaDB Corporation Ab
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
||||
*
|
||||
* Change Date: 2022-01-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2 or later of the General
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
#include <maxbase/cdefs.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
inline const char* mxb_strerror(int error)
|
||||
{
|
||||
// Enough for all errors
|
||||
static thread_local char errbuf[512];
|
||||
#ifdef HAVE_GLIBC
|
||||
return strerror_r(error, errbuf, sizeof(errbuf));
|
||||
#else
|
||||
strerror_r(error, errbuf, sizeof(errbuf));
|
||||
return errbuf;
|
||||
#endif
|
||||
}
|
||||
171
maxutils/maxbase/include/maxbase/logger.hh
Normal file
171
maxutils/maxbase/include/maxbase/logger.hh
Normal file
@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
/*
|
||||
* Copyright (c) 2016 MariaDB Corporation Ab
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
||||
*
|
||||
* Change Date: 2022-01-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2 or later of the General
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
#include <maxbase/ccdefs.hh>
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
namespace maxbase
|
||||
{
|
||||
|
||||
// Minimal logger interface
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
Logger(const Logger&) = delete;
|
||||
Logger& operator=(const Logger&) = delete;
|
||||
|
||||
virtual ~Logger()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a message to the log
|
||||
*
|
||||
* @param msg Message to write
|
||||
* @param len Length of message
|
||||
*
|
||||
* @return True on success
|
||||
*/
|
||||
virtual bool write(const char* msg, int len) = 0;
|
||||
|
||||
/**
|
||||
* Rotate the logfile
|
||||
*
|
||||
* @return True if the log was rotated
|
||||
*/
|
||||
virtual bool rotate() = 0;
|
||||
|
||||
/**
|
||||
* Get the name of the log file
|
||||
*
|
||||
* @return The name of the log file
|
||||
*/
|
||||
const char* filename() const
|
||||
{
|
||||
return m_filename.c_str();
|
||||
}
|
||||
|
||||
protected:
|
||||
Logger(const std::string& filename):
|
||||
m_filename(filename)
|
||||
{
|
||||
}
|
||||
|
||||
std::string m_filename;
|
||||
};
|
||||
|
||||
class FileLogger: public Logger
|
||||
{
|
||||
public:
|
||||
FileLogger(const FileLogger&) = delete;
|
||||
FileLogger& operator=(const FileLogger&) = delete;
|
||||
|
||||
/**
|
||||
* Create a new logger that writes to a file
|
||||
*
|
||||
* @param logdir Log file to open
|
||||
*
|
||||
* @return New logger instance or an empty unique_ptr on error
|
||||
*/
|
||||
static std::unique_ptr<Logger> create(const std::string& filename);
|
||||
|
||||
/**
|
||||
* Close the log
|
||||
*
|
||||
* A footer is written to the log and the file is closed.
|
||||
*/
|
||||
~FileLogger();
|
||||
|
||||
/**
|
||||
* Write a message to the log
|
||||
*
|
||||
* @param msg Message to write
|
||||
* @param len Length of message
|
||||
*
|
||||
* @return True on success
|
||||
*/
|
||||
bool write(const char* msg, int len);
|
||||
|
||||
/**
|
||||
* Rotate the logfile by reopening it
|
||||
*
|
||||
* @return True if the log was rotated. False if the opening of the new file
|
||||
* descriptor failed in which case the old file descriptor will be used.
|
||||
*/
|
||||
bool rotate();
|
||||
|
||||
private:
|
||||
int m_fd;
|
||||
std::mutex m_lock;
|
||||
|
||||
FileLogger(int fd, const std::string& filename);
|
||||
bool write_header();
|
||||
bool write_footer(const char* suffix);
|
||||
void close(const char* msg);
|
||||
};
|
||||
|
||||
class StdoutLogger: public Logger
|
||||
{
|
||||
public:
|
||||
StdoutLogger(const StdoutLogger&) = delete;
|
||||
StdoutLogger& operator=(const StdoutLogger&) = delete;
|
||||
|
||||
/**
|
||||
* Create a new logger that writes to stdout
|
||||
*
|
||||
* @param logdir Log file to open, has no functional effect on this logger
|
||||
*
|
||||
* @return New logger instance or an empty unique_ptr on error
|
||||
*/
|
||||
static std::unique_ptr<Logger> create(const std::string& filename)
|
||||
{
|
||||
return std::unique_ptr<Logger>(new StdoutLogger(filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a message to stdout
|
||||
*
|
||||
* @param msg Message to write
|
||||
* @param len Length of message
|
||||
*
|
||||
* @return True on success
|
||||
*/
|
||||
bool write(const char* msg, int len)
|
||||
{
|
||||
return ::write(STDOUT_FILENO, msg, len) != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the "logfile"
|
||||
*
|
||||
* @return Always true
|
||||
*/
|
||||
bool rotate()
|
||||
{
|
||||
return true;
|
||||
};
|
||||
|
||||
private:
|
||||
StdoutLogger(const std::string& filename):
|
||||
Logger(filename)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@ -1,3 +1,3 @@
|
||||
add_library(maxbase STATIC eventcount.cc stopwatch.cc stacktrace.cc)
|
||||
add_library(maxbase STATIC eventcount.cc stopwatch.cc stacktrace.cc logger.cc)
|
||||
set_target_properties(maxbase PROPERTIES VERSION "1.0.0" LINK_FLAGS -Wl,-z,defs)
|
||||
install(TARGETS maxbase DESTINATION lib)
|
||||
|
||||
240
maxutils/maxbase/src/logger.cc
Normal file
240
maxutils/maxbase/src/logger.cc
Normal file
@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (c) 2016 MariaDB Corporation Ab
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
||||
*
|
||||
* Change Date: 2022-01-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2 or later of the General
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
#include <maxbase/logger.hh>
|
||||
|
||||
#include <syslog.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
#include <maxbase/error.h>
|
||||
|
||||
// TODO: move <maxscale/debug.h> into maxbase
|
||||
#define ss_dassert(a)
|
||||
|
||||
/**
|
||||
* Error logging for the logger itself.
|
||||
*
|
||||
* For obvious reasons, it cannot use its own functions for reporting errors.
|
||||
*/
|
||||
#define LOG_ERROR(format, ...) do { fprintf(stderr, format, ##__VA_ARGS__); } while (false)
|
||||
|
||||
//
|
||||
// Helper functions
|
||||
//
|
||||
namespace
|
||||
{
|
||||
|
||||
int open_fd(const std::string& filename)
|
||||
{
|
||||
int fd = open(filename.c_str(), O_WRONLY | O_APPEND | O_CREAT,
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
|
||||
|
||||
if (fd == -1)
|
||||
{
|
||||
LOG_ERROR("Failed to open file '%s': %d, %s\n", filename.c_str(), errno, mxb_strerror(errno));
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
bool should_log_error()
|
||||
{
|
||||
using std::chrono::seconds;
|
||||
static auto last_write = std::chrono::steady_clock::now() - seconds(61);
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
bool rval = false;
|
||||
|
||||
if ((now - last_write).count() >= 60)
|
||||
{
|
||||
last_write = now;
|
||||
rval = true;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace maxbase
|
||||
{
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
|
||||
std::unique_ptr<Logger> FileLogger::create(const std::string& filename)
|
||||
{
|
||||
std::unique_ptr<FileLogger> logger;
|
||||
int fd = open_fd(filename);
|
||||
|
||||
if (fd != -1)
|
||||
{
|
||||
logger.reset(new (std::nothrow) FileLogger(fd, filename));
|
||||
|
||||
if (logger)
|
||||
{
|
||||
logger->write_header();
|
||||
}
|
||||
else
|
||||
{
|
||||
::close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
return std::move(logger);
|
||||
}
|
||||
|
||||
FileLogger::~FileLogger()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_lock);
|
||||
ss_dassert(m_fd != -1);
|
||||
close("MariaDB MaxScale is shut down.");
|
||||
}
|
||||
|
||||
bool FileLogger::write(const char* msg, int len)
|
||||
{
|
||||
bool rval = true;
|
||||
std::lock_guard<std::mutex> guard(m_lock);
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
int rc;
|
||||
do
|
||||
{
|
||||
rc = ::write(m_fd, msg, len);
|
||||
}
|
||||
while (rc == -1 && errno == EINTR);
|
||||
|
||||
if (rc == -1)
|
||||
{
|
||||
if (should_log_error()) // Coarse error suppression
|
||||
{
|
||||
LOG_ERROR("Failed to write to log: %d, %s\n", errno, mxb_strerror(errno));
|
||||
}
|
||||
|
||||
rval = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// If write only writes a part of the message, retry again
|
||||
len -= rc;
|
||||
msg += rc;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
bool FileLogger::rotate()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_lock);
|
||||
int fd = open_fd(m_filename);
|
||||
|
||||
if (fd != -1)
|
||||
{
|
||||
close("File closed due to log rotation.");
|
||||
m_fd = fd;
|
||||
}
|
||||
|
||||
return fd != -1;
|
||||
}
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
|
||||
FileLogger::FileLogger(int fd, const std::string& filename):
|
||||
Logger(filename),
|
||||
m_fd(fd)
|
||||
{
|
||||
}
|
||||
|
||||
void FileLogger::close(const char* msg)
|
||||
{
|
||||
write_footer(msg);
|
||||
::close(m_fd);
|
||||
m_fd = -1;
|
||||
}
|
||||
|
||||
// Nearly identical to the one in log_manager.cc
|
||||
bool FileLogger::write_header()
|
||||
{
|
||||
time_t t = time(NULL);
|
||||
struct tm tm;
|
||||
localtime_r(&t, &tm);
|
||||
|
||||
const char PREFIX[] = "MariaDB MaxScale "; // sizeof(PREFIX) includes the NULL.
|
||||
char time_string[32]; // 26 would be enough, according to "man asctime".
|
||||
asctime_r(&tm, time_string);
|
||||
|
||||
size_t size = sizeof(PREFIX) + m_filename.length() + 2 * sizeof(' ') + strlen(time_string);
|
||||
|
||||
char header[size + 2]; // For the 2 newlines.
|
||||
sprintf(header, "\n\n%s%s %s", PREFIX, m_filename.c_str(), time_string);
|
||||
|
||||
char line[sizeof(header) - 1];
|
||||
memset(line, '-', sizeof(line) - 1);
|
||||
line[sizeof(line) - 1] = '\n';
|
||||
|
||||
bool ok = ::write(m_fd, header, sizeof(header) - 1) != -1 &&
|
||||
::write(m_fd, line, sizeof(line)) != -1;
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
LOG_ERROR("Error: Writing log header failed due to %d, %s\n",
|
||||
errno, mxb_strerror(errno));
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Nearly identical to the one in log_manager.cc
|
||||
bool FileLogger::write_footer(const char* suffix)
|
||||
{
|
||||
time_t t = time(NULL);
|
||||
struct tm tm;
|
||||
localtime_r(&t, &tm);
|
||||
|
||||
const char FORMAT[] = "%04d-%02d-%02d %02d:%02d:%02d";
|
||||
char time_string[20]; // 19 chars + NULL.
|
||||
|
||||
sprintf(time_string, FORMAT,
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
|
||||
size_t size = sizeof(time_string) + 3 * sizeof(' ') + strlen(suffix) + sizeof('\n');
|
||||
|
||||
char header[size];
|
||||
sprintf(header, "%s %s\n", time_string, suffix);
|
||||
|
||||
char line[sizeof(header) - 1];
|
||||
memset(line, '-', sizeof(line) - 1);
|
||||
line[sizeof(line) - 1] = '\n';
|
||||
|
||||
bool ok = ::write(m_fd, header, sizeof(header) - 1) != -1 &&
|
||||
::write(m_fd, line, sizeof(line)) != -1;
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
LOG_ERROR("Error: Writing log footer failed due to %d, %s\n",
|
||||
errno, mxb_strerror(errno));
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user