libreoffice-online/common/Log.cpp
Ashod Nakashian 054ab5f61f wsd: specialize thread ID serialization
On non-Linux systems we should default to std:🧵:id
which needs to be serialized using ostream interface.

While Util::getThreadId does specialize for Linux, the
code using it doesn't always handle the different return
types.

While std:🧵:id is the standard interface to the
thread ID, using such abstraction has proven to be costly
when converting the thread ID on each and every log via
ostringstream (due to the cost of memory allocation).
In practice Linux is the primary and so far only platform,
so the getThreadId is optimized for it. Other systems
can either use the default std:🧵:id, or can also
specialize as necessary.

Change-Id: I91cf279a8fdff12636a534957db5069dee51bd65
Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
2020-11-21 19:49:46 -05:00

358 lines
9.4 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <config.h>
#ifdef __linux__
#include <sys/prctl.h>
#include <sys/syscall.h>
#endif
#include <unistd.h>
#include <atomic>
#include <cassert>
#include <cstring>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <Poco/AutoPtr.h>
#include <Poco/ConsoleChannel.h>
#include <Poco/FileChannel.h>
#include <Poco/FormattingChannel.h>
#include <Poco/PatternFormatter.h>
#include <Poco/SplitterChannel.h>
#include "Log.hpp"
#include "Util.hpp"
namespace Log
{
using namespace Poco;
/// Helper to avoid destruction ordering issues.
struct StaticNameHelper
{
private:
Poco::Logger* _logger;
std::string _name;
std::string _id;
std::atomic<bool> _inited;
public:
StaticNameHelper() :
_logger(nullptr),
_inited(true)
{
}
~StaticNameHelper()
{
_inited = false;
}
bool getInited() const { return _inited; }
void setId(const std::string& id) { _id = id; }
const std::string& getId() const { return _id; }
void setName(const std::string& name) { _name = name; }
const std::string& getName() const { return _name; }
void setLogger(Poco::Logger* logger) { _logger = logger; };
Poco::Logger* getLogger() const { return _logger; }
};
static StaticNameHelper Source;
bool IsShutdown = false;
// We need a signal safe means of writing messages
// $ man 7 signal
void signalLog(const char *message)
{
while (true)
{
const int length = std::strlen(message);
const int written = write(STDERR_FILENO, message, length);
if (written < 0)
{
if (errno == EINTR)
continue; // ignore.
else
break;
}
message += written;
if (message[0] == '\0')
break;
}
}
// We need a signal safe means of writing messages
// $ man 7 signal
void signalLogNumber(std::size_t num)
{
int i;
char buf[22];
buf[21] = '\0';
for (i = 20; i > 0 && num > 0; --i)
{
buf[i] = '0' + num % 10;
num /= 10;
}
signalLog(buf + i + 1);
}
/// Convert an unsigned number to ascii with 0 padding.
template <int Width> void to_ascii_fixed(char* buf, std::size_t num)
{
buf[Width - 1] = '0' + num % 10; // Units.
if (Width > 1)
{
num /= 10;
buf[Width - 2] = '0' + num % 10; // Tens.
}
if (Width > 2)
{
num /= 10;
buf[Width - 3] = '0' + num % 10; // Hundreds.
}
if (Width > 3)
{
num /= 10;
buf[Width - 4] = '0' + num % 10; // Thousands.
}
if (Width > 4)
{
num /= 10;
buf[Width - 5] = '0' + num % 10; // Ten-Thousands.
}
if (Width > 5)
{
num /= 10;
buf[Width - 6] = '0' + num % 10; // Hundred-Thousands.
}
static_assert(Width >= 1 && Width <= 6, "Width is invalid.");
}
/// Copy a null-terminated string into another.
/// Expects the destination to be large enough.
/// Note: unlike strcpy, this returns the *new* out
/// (destination) pointer, which saves a strlen call.
char* strcopy(const char* in, char* out)
{
while (*in)
*out++ = *in++;
return out;
}
/// Convert unsigned long num to base-10 ascii in place.
/// Returns the *end* position.
char* to_ascii(char* buf, std::size_t num)
{
int i = 0;
do
{
buf[i++] = '0' + num % 10;
num /= 10;
} while (num > 0);
// Reverse.
for (char *front = buf, *back = buf + i - 1; back > front; ++front, --back)
{
const char t = *front;
*front = *back;
*back = t;
}
return buf + i;
}
char* prefix(const Poco::DateTime& time, char* buffer, const char* level)
{
#if defined(IOS) || defined(__FreeBSD__)
// Don't bother with the "Source" which would be just "Mobile" always and non-informative as
// there is just one process in the app anyway.
char *pos = buffer;
// Don't bother with the thread identifier either. We output the thread name which is much
// more useful anyway.
#else
// Note that snprintf is deemed signal-safe in most common implementations.
char* pos = strcopy((Source.getInited() ? Source.getId().c_str() : "<shutdown>"), buffer);
*pos++ = '-';
// Thread ID.
const auto osTid = Util::getThreadId();
#if defined(__linux__)
// On Linux osTid is pid_t.
if (osTid > 99999)
{
if (osTid > 999999)
pos = to_ascii(pos, osTid);
else
{
to_ascii_fixed<6>(pos, osTid);
pos += 6;
}
}
else
{
to_ascii_fixed<5>(pos, osTid);
pos += 5;
}
#else
// On all other systems osTid is std::thread::id.
std::stringstream ss;
ss << osTid;
pos = strcopy(ss.str().c_str(), pos);
#endif
*pos++ = ' ';
#endif
// YYYY-MM-DD.
to_ascii_fixed<4>(pos, time.year());
pos[4] = '-';
pos += 5;
to_ascii_fixed<2>(pos, time.month());
pos[2] = '-';
pos += 3;
to_ascii_fixed<2>(pos, time.day());
pos[2] = ' ';
pos += 3;
// HH:MM:SS.uS
to_ascii_fixed<2>(pos, time.hour());
pos[2] = ':';
pos += 3;
to_ascii_fixed<2>(pos, time.minute());
pos[2] = ':';
pos += 3;
to_ascii_fixed<2>(pos, time.second());
pos[2] = '.';
pos += 3;
to_ascii_fixed<6>(pos, time.millisecond() * 1000 + time.microsecond());
pos[6] = ' ';
pos[7] = '[';
pos[8] = ' ';
pos += 9;
pos = strcopy(Util::getThreadName(), pos);
pos[0] = ' ';
pos[1] = ']';
pos[2] = ' ';
pos += 3;
pos = strcopy(level, pos);
pos[0] = ' ';
pos[1] = ' ';
pos[2] = '\0';
return buffer;
}
void signalLogPrefix()
{
char buffer[1024];
prefix<sizeof(buffer) - 1>(buffer, "SIG");
signalLog(buffer);
}
void initialize(const std::string& name,
const std::string& logLevel,
const bool withColor,
const bool logToFile,
const std::map<std::string, std::string>& config)
{
Source.setName(name);
std::ostringstream oss;
oss << Source.getName();
#if !MOBILEAPP // Just one process in a mobile app, the pid is uninteresting.
oss << '-'
<< std::setw(5) << std::setfill('0') << getpid();
#endif
Source.setId(oss.str());
// Configure the logger.
AutoPtr<Channel> channel;
if (logToFile)
{
channel = static_cast<Poco::Channel*>(new FileChannel("loolwsd.log"));
for (const auto& pair : config)
{
channel->setProperty(pair.first, pair.second);
}
}
else if (withColor)
{
channel = static_cast<Poco::Channel*>(new Poco::ColorConsoleChannel());
channel->setProperty("traceColor", "green");
channel->setProperty("warningColor", "magenta");
}
else
channel = static_cast<Poco::Channel*>(new Poco::ConsoleChannel());
/**
* Open the channel explicitly, instead of waiting for first log message
* This is important especially for the kit process where opening the channel
* after chroot can cause file creation inside the jail instead of outside
* */
channel->open();
auto& logger = Poco::Logger::create(Source.getName(), channel, Poco::Message::PRIO_TRACE);
Source.setLogger(&logger);
logger.setLevel(logLevel.empty() ? std::string("trace") : logLevel);
const std::time_t t = std::time(nullptr);
oss.str("");
oss.clear();
oss << "Initializing " << name << '.';
// TODO: replace with std::put_time when we move to gcc 5+.
char buf[32];
if (strftime(buf, sizeof(buf), "%a %F %T%z", std::localtime(&t)) > 0)
{
oss << " Local time: " << buf << '.';
}
oss << " Log level is [" << logger.getLevel() << "].";
LOG_INF(oss.str());
}
Poco::Logger& logger()
{
Poco::Logger* pLogger = Source.getLogger();
return pLogger ? *pLogger
: Poco::Logger::get(Source.getInited() ? Source.getName() : std::string());
}
void shutdown()
{
#if !MOBILEAPP
IsShutdown = true;
Poco::Logger::shutdown();
// Flush
std::flush(std::cout);
fflush(stdout);
std::flush(std::cerr);
fflush(stderr);
#endif
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */