blob: 04831a286bcd6a6852785142e8bef6965a81fa5d [file] [log] [blame]
#include "aos/logging/implementations.h"
#include <inttypes.h>
#include <stdarg.h>
#include <algorithm>
#include <chrono>
#include "absl/base/call_once.h"
#include "aos/die.h"
#include "aos/ipc_lib/queue.h"
#include "aos/logging/printf_formats.h"
#include "aos/time/time.h"
namespace aos {
namespace logging {
namespace {
namespace chrono = ::std::chrono;
// The root LogImplementation. It only logs to stderr/stdout.
// Some of the things specified in the LogImplementation documentation doesn't
// apply here (mostly the parts about being able to use AOS_LOG) because this is
// the root one.
class RootLogImplementation : public LogImplementation {
protected:
virtual ::aos::monotonic_clock::time_point monotonic_now() const {
return ::aos::monotonic_clock::now();
}
private:
__attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0))) void DoLog(
log_level level, const char *format, va_list ap) override {
LogMessage message;
internal::FillInMessage(level, monotonic_now(), format, ap, &message);
internal::PrintMessage(stderr, message);
}
};
RootLogImplementation *root_implementation = nullptr;
void SetGlobalImplementation(LogImplementation *implementation) {
if (root_implementation == nullptr) {
fputs("Somebody didn't call logging::Init()!\n", stderr);
abort();
}
internal::Context *context = internal::Context::Get();
context->implementation = implementation;
internal::global_top_implementation.store(implementation);
}
void NewContext() { internal::Context::Delete(); }
void DoInit() {
SetGlobalImplementation(root_implementation = new RootLogImplementation());
if (pthread_atfork(NULL /*prepare*/, NULL /*parent*/, NewContext /*child*/) !=
0) {
AOS_LOG(FATAL, "pthread_atfork(NULL, NULL, %p) failed\n", NewContext);
}
}
} // namespace
namespace internal {
namespace {
void FillInMessageBase(log_level level,
monotonic_clock::time_point monotonic_now,
LogMessage *message) {
Context *context = Context::Get();
message->level = level;
message->source = context->source;
memcpy(message->name, context->name, context->name_size);
message->name_length = context->name_size;
message->seconds =
chrono::duration_cast<chrono::seconds>(monotonic_now.time_since_epoch())
.count();
message->nseconds =
chrono::duration_cast<chrono::nanoseconds>(
monotonic_now.time_since_epoch() - chrono::seconds(message->seconds))
.count();
message->sequence = context->sequence++;
}
} // namespace
void FillInMessage(log_level level, monotonic_clock::time_point monotonic_now,
const char *format, va_list ap, LogMessage *message) {
FillInMessageBase(level, monotonic_now, message);
message->message_length =
ExecuteFormat(message->message, sizeof(message->message), format, ap);
message->type = LogMessage::Type::kString;
}
void PrintMessage(FILE *output, const LogMessage &message) {
#define BASE_ARGS \
AOS_LOGGING_BASE_ARGS( \
message.name_length, message.name, static_cast<int32_t>(message.source), \
message.sequence, message.level, message.seconds, message.nseconds)
switch (message.type) {
case LogMessage::Type::kString:
fprintf(output, AOS_LOGGING_BASE_FORMAT "%.*s", BASE_ARGS,
static_cast<int>(message.message_length), message.message);
break;
}
#undef BASE_ARGS
}
} // namespace internal
void HandleMessageLogImplementation::DoLog(log_level level, const char *format,
va_list ap) {
LogMessage message;
internal::FillInMessage(level, monotonic_now(), format, ap, &message);
HandleMessage(message);
}
StreamLogImplementation::StreamLogImplementation(FILE *stream)
: stream_(stream) {}
void StreamLogImplementation::HandleMessage(const LogMessage &message) {
internal::PrintMessage(stream_, message);
}
void SetImplementation(LogImplementation *implementation, bool update_global) {
internal::Context *context = internal::Context::Get();
context->implementation = implementation;
if (update_global) {
SetGlobalImplementation(implementation);
}
}
LogImplementation *SwapImplementation(LogImplementation *implementation) {
internal::Context *context = internal::Context::Get();
LogImplementation *old = context->implementation;
context->implementation = implementation;
return old;
}
LogImplementation *GetImplementation() {
return internal::Context::Get()->implementation;
}
void Init() {
static absl::once_flag once;
absl::call_once(once, DoInit);
}
void Load() { internal::Context::Get(); }
void Cleanup() { internal::Context::Delete(); }
namespace {
RawQueue *queue = NULL;
int dropped_messages = 0;
monotonic_clock::time_point dropped_start, backoff_start;
// Wait this long after dropping a message before even trying to write any more.
constexpr chrono::milliseconds kDropBackoff = chrono::milliseconds(100);
LogMessage *GetMessageOrDie() {
LogMessage *message = static_cast<LogMessage *>(queue->GetMessage());
if (message == NULL) {
AOS_LOG(FATAL, "%p->GetMessage() failed\n", queue);
} else {
return message;
}
}
void Write(LogMessage *msg) {
if (__builtin_expect(dropped_messages > 0, false)) {
monotonic_clock::time_point message_time(
chrono::seconds(msg->seconds) + chrono::nanoseconds(msg->nseconds));
if (message_time - backoff_start < kDropBackoff) {
++dropped_messages;
queue->FreeMessage(msg);
return;
}
LogMessage *dropped_message = GetMessageOrDie();
chrono::seconds dropped_start_sec = chrono::duration_cast<chrono::seconds>(
dropped_start.time_since_epoch());
chrono::nanoseconds dropped_start_nsec =
chrono::duration_cast<chrono::nanoseconds>(
dropped_start.time_since_epoch() - dropped_start_sec);
internal::FillInMessageVarargs(
ERROR, message_time, dropped_message,
"%d logs starting at %" PRId32 ".%" PRId32 " dropped\n",
dropped_messages, static_cast<int32_t>(dropped_start_sec.count()),
static_cast<int32_t>(dropped_start_nsec.count()));
if (queue->WriteMessage(dropped_message, RawQueue::kNonBlock)) {
dropped_messages = 0;
} else {
// Don't even bother trying to write this message because it's not likely
// to work and it would be confusing to have one log in the middle of a
// string of failures get through.
++dropped_messages;
backoff_start = message_time;
queue->FreeMessage(msg);
return;
}
}
if (!queue->WriteMessage(msg, RawQueue::kNonBlock)) {
if (dropped_messages == 0) {
monotonic_clock::time_point message_time(
chrono::seconds(msg->seconds) + chrono::nanoseconds(msg->nseconds));
dropped_start = backoff_start = message_time;
}
++dropped_messages;
}
}
class LinuxQueueLogImplementation : public LogImplementation {
virtual ::aos::monotonic_clock::time_point monotonic_now() const {
return ::aos::monotonic_clock::now();
}
__attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0))) void DoLog(
log_level level, const char *format, va_list ap) override {
LogMessage *message = GetMessageOrDie();
internal::FillInMessage(level, monotonic_now(), format, ap, message);
Write(message);
}
};
class CallbackLogImplementation : public HandleMessageLogImplementation {
public:
CallbackLogImplementation(
const ::std::function<void(const LogMessage &)> &callback)
: callback_(callback) {}
private:
void HandleMessage(const LogMessage &message) override { callback_(message); }
::std::function<void(const LogMessage &)> callback_;
};
} // namespace
RawQueue *GetLoggingQueue() {
return RawQueue::Fetch("LoggingQueue", sizeof(LogMessage), 1323, 10000);
}
void RegisterQueueImplementation() {
Init();
queue = GetLoggingQueue();
if (queue == NULL) {
Die("logging: couldn't fetch queue\n");
}
SetImplementation(new LinuxQueueLogImplementation());
}
void RegisterCallbackImplementation(
const ::std::function<void(const LogMessage &)> &callback,
bool update_global) {
Init();
SetImplementation(new CallbackLogImplementation(callback), update_global);
}
} // namespace logging
} // namespace aos