MXS-2005: Add Logger class
The interface defines a very simple logging API. The main logger, FileLogger, implements a very basic logger that writes to a file. The StdoutLogger implements a logger that simply writes to stdout. The logging to shm could be implemented quite easily but the usefulness of it is very questionable and it has the problem of losing log files on a server shutdown. A ring buffer of INFO messages would be more useful and would provide a way to limit the amount of memory used for it.
This commit is contained in:
parent
32b1711684
commit
1326b3a950
171
server/core/internal/logger.hh
Normal file
171
server/core/internal/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 <maxscale/ccdefs.hh>
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
namespace maxscale
|
||||
{
|
||||
|
||||
// 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)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
}
|
237
server/core/logger.cc
Normal file
237
server/core/logger.cc
Normal file
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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 "internal/logger.hh"
|
||||
|
||||
#include <syslog.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
#include <maxscale/debug.h>
|
||||
|
||||
/**
|
||||
* 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, mxs_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 maxscale
|
||||
{
|
||||
|
||||
//
|
||||
// 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, mxs_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, mxs_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, mxs_strerror(errno));
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user