blob: 28923d531a73e5207cc2f840b9ac1922962e4ac7 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <runtests-utils/log-exporter.h>
#include <errno.h>
#include <stdint.h>
#include <fuchsia/logger/c/fidl.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/message_buffer.h>
#include <lib/zx/channel.h>
#include <zircon/status.h>
#include <utility>
namespace runtests {
namespace {
fbl::String ToFblString(fidl_string_t string) {
return fbl::String(string.data, string.size);
}
} // namespace
LogExporter::LogExporter(zx::channel channel, FILE* output_file)
: loop_(&kAsyncLoopConfigNoAttachToThread),
channel_(std::move(channel)),
wait_(this, channel_.get(), ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED),
output_file_(output_file) {
wait_.Begin(loop_.dispatcher());
}
LogExporter::~LogExporter() {
// Quit so that current work is completed and loop can stop.
loop_.Quit();
// wait for current work to be completed.
loop_.JoinThreads();
// Run one more time until there are no more messages.
loop_.ResetQuit();
RunUntilIdle();
// Shutdown
loop_.Shutdown();
if (output_file_ != nullptr) {
fclose(output_file_);
}
}
zx_status_t LogExporter::StartThread() {
return loop_.StartThread();
}
zx_status_t LogExporter::RunUntilIdle() {
return loop_.RunUntilIdle();
}
void LogExporter::OnHandleReady(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
NotifyError(status);
return;
}
if (signal->observed & ZX_CHANNEL_READABLE) {
fidl::MessageBuffer buffer;
for (uint64_t i = 0; i < signal->count; i++) {
status = ReadAndDispatchMessage(&buffer);
if (status == ZX_ERR_SHOULD_WAIT) {
break;
} else if (status != ZX_OK) {
NotifyError(status);
return;
}
}
status = wait_.Begin(dispatcher);
if (status != ZX_OK) {
NotifyError(status);
}
return;
}
ZX_DEBUG_ASSERT(signal->observed & ZX_CHANNEL_PEER_CLOSED);
// We don't notify an error until we've drained all the messages.
NotifyError(ZX_ERR_PEER_CLOSED);
}
zx_status_t LogExporter::ReadAndDispatchMessage(fidl::MessageBuffer* buffer) {
fidl::Message message = buffer->CreateEmptyMessage();
zx_status_t status = message.Read(channel_.get(), 0);
if (status != ZX_OK) {
return status;
}
if (!message.has_header()) {
return ZX_ERR_INVALID_ARGS;
}
// This is an if statement because, depending on the state of the ordinal
// migration, GenOrdinal and Ordinal may be the same value. See FIDL-372
uint32_t ordinal = message.ordinal();
if (ordinal == fuchsia_logger_LogListenerLogGenOrdinal ||
ordinal == fuchsia_logger_LogListenerLogOrdinal) {
return Log(std::move(message));
} else if (ordinal == fuchsia_logger_LogListenerLogManyGenOrdinal ||
ordinal == fuchsia_logger_LogListenerLogManyOrdinal) {
return LogMany(std::move(message));
} else {
return ZX_ERR_NOT_SUPPORTED;
}
}
// Returns only seconds part
uint64_t GetSeconds(uint64_t nanoseconds) {
return nanoseconds / 1000000000UL;
}
// Returns only nano seconds part
uint64_t GetNanoSeconds(uint64_t nanoseconds) {
return (nanoseconds / 1000UL) % 1000000UL;
}
#define RETURN_IF_ERROR(expr) \
do { \
int n = (expr); \
if (n < 0) { \
return n; \
} \
} while (false)
int LogExporter::WriteSeverity(int32_t severity) {
switch (severity) {
case fuchsia_logger_LogLevelFilter_INFO:
return fputs(" INFO", output_file_);
case fuchsia_logger_LogLevelFilter_WARN:
return fputs(" WARNING", output_file_);
case fuchsia_logger_LogLevelFilter_ERROR:
return fputs(" ERROR", output_file_);
case fuchsia_logger_LogLevelFilter_FATAL:
return fputs(" FATAL", output_file_);
default:
// all other cases severity would be a negative nuber so print it as
// VLOG(n) where severity=-n
return fprintf(output_file_, " VLOG(%d)", -severity);
}
}
int LogExporter::LogMessage(fuchsia_logger_LogMessage* log_message) {
RETURN_IF_ERROR(fprintf(output_file_, "[%05ld.%06ld][%lu][%lu]", GetSeconds(log_message->time),
GetNanoSeconds(log_message->time), log_message->pid, log_message->tid));
RETURN_IF_ERROR(fputs("[", output_file_));
fidl_string_t* tags = static_cast<fidl_string_t*>(log_message->tags.data);
for (size_t i = 0; i < log_message->tags.count; ++i) {
RETURN_IF_ERROR(fprintf(output_file_, "%s", ToFblString(tags[i]).c_str()));
if (i < log_message->tags.count - 1) {
RETURN_IF_ERROR(fputs(", ", output_file_));
}
}
RETURN_IF_ERROR(fputs("]", output_file_));
RETURN_IF_ERROR(WriteSeverity(log_message->severity));
RETURN_IF_ERROR(fprintf(output_file_, ": %s\n", ToFblString(log_message->msg).c_str()));
if (log_message->dropped_logs > 0) {
bool log = true;
bool found = false;
for (DroppedLogs& dl : dropped_logs_) {
if (dl.pid == log_message->pid) {
found = true;
// only update our vector when we get new dropped_logs value.
if (dl.dropped_logs < log_message->dropped_logs) {
dl.dropped_logs = log_message->dropped_logs;
} else {
log = false;
}
break;
}
}
if (!found) {
dropped_logs_.push_back(DroppedLogs{log_message->pid, log_message->dropped_logs});
}
if (log) {
RETURN_IF_ERROR(fprintf(output_file_, "[%05ld.%06ld][%lu][%lu]", GetSeconds(log_message->time),
GetNanoSeconds(log_message->time), log_message->pid,
log_message->tid));
RETURN_IF_ERROR(fputs("[", output_file_));
fidl_string_t* tags = static_cast<fidl_string_t*>(log_message->tags.data);
for (size_t i = 0; i < log_message->tags.count; ++i) {
RETURN_IF_ERROR(fprintf(output_file_, "%s", ToFblString(tags[i]).c_str()));
if (i < log_message->tags.count - 1) {
RETURN_IF_ERROR(fputs(", ", output_file_));
}
}
RETURN_IF_ERROR(fputs("]", output_file_));
RETURN_IF_ERROR(fprintf(output_file_, " WARNING: Dropped logs count:%d\n",
log_message->dropped_logs));
}
}
return 0;
}
zx_status_t LogExporter::Log(fidl::Message message) {
const char* error_msg = nullptr;
zx_status_t status = message.Decode(&fuchsia_logger_LogListenerLogRequestTable, &error_msg);
if (status != ZX_OK) {
fprintf(stderr, "log-listener: error: Log: %s\n", error_msg);
return status;
}
fuchsia_logger_LogMessage* log_message = message.GetPayloadAs<fuchsia_logger_LogMessage>();
if (LogMessage(log_message) < 0) {
NotifyFileError(strerror(errno));
}
return ZX_OK;
}
zx_status_t LogExporter::LogMany(fidl::Message message) {
const char* error_msg = nullptr;
zx_status_t status = message.Decode(&fuchsia_logger_LogListenerLogManyRequestTable, &error_msg);
if (status != ZX_OK) {
fprintf(stderr, "log-listener: error: LogMany: %s\n", error_msg);
return status;
}
fidl_vector_t* log_messages = message.GetPayloadAs<fidl_vector_t>();
fuchsia_logger_LogMessage* msgs = static_cast<fuchsia_logger_LogMessage*>(log_messages->data);
for (size_t i = 0; i < log_messages->count; ++i) {
if (LogMessage(&msgs[i]) < 0) {
NotifyFileError(strerror(errno));
return ZX_OK;
}
}
return ZX_OK;
}
void LogExporter::NotifyError(zx_status_t error) {
channel_.reset();
fclose(output_file_);
output_file_ = nullptr;
if (error_handler_) {
error_handler_(error);
}
}
void LogExporter::NotifyFileError(const char* error) {
channel_.reset();
fclose(output_file_);
output_file_ = nullptr;
if (file_error_handler_) {
file_error_handler_(error);
}
}
std::unique_ptr<LogExporter> LaunchLogExporter(const fbl::StringPiece syslog_path,
ExporterLaunchError* error) {
*error = NO_ERROR;
fbl::String syslog_path_str = fbl::String(syslog_path.data());
FILE* syslog_file = fopen(syslog_path_str.c_str(), "w");
if (syslog_file == nullptr) {
fprintf(stderr, "Error: Could not open syslog file: %s.\n", syslog_path_str.c_str());
*error = OPEN_FILE;
return nullptr;
}
// Try and connect to logger service if available. It would be only
// available in garnet and above layer
zx::channel logger, logger_request;
zx_status_t status;
status = zx::channel::create(0, &logger, &logger_request);
if (status != ZX_OK) {
fprintf(stderr, "LaunchLogExporter: cannot create channel for logger service: %d (%s).\n",
status, zx_status_get_string(status));
*error = CREATE_CHANNEL;
return nullptr;
}
status = fdio_service_connect("/svc/fuchsia.logger.Log", logger_request.release());
if (status != ZX_OK) {
fprintf(stderr, "LaunchLogExporter: cannot connect to logger service: %d (%s).\n",
status, zx_status_get_string(status));
*error = CONNECT_TO_LOGGER_SERVICE;
return nullptr;
}
// Create a log exporter channel and pass it to logger service.
zx::channel listener, listener_request;
status = zx::channel::create(0, &listener, &listener_request);
if (status != ZX_OK) {
fprintf(stderr, "LaunchLogExporter: cannot create channel for listener: %d (%s).\n",
status, zx_status_get_string(status));
*error = CREATE_CHANNEL;
return nullptr;
}
fuchsia_logger_LogListenRequest req = {};
req.hdr.ordinal = fuchsia_logger_LogListenOrdinal;
req.log_listener = FIDL_HANDLE_PRESENT;
zx_handle_t listener_handle = listener.release();
status = logger.write(0, &req, sizeof(req), &listener_handle, 1);
if (status != ZX_OK) {
fprintf(stderr, "LaunchLogExporter: cannot pass listener to logger service: %d (%s).\n",
status, zx_status_get_string(status));
*error = FIDL_ERROR;
return nullptr;
}
// Connect log exporter channel to object and start message loop on it.
auto log_exporter = std::make_unique<LogExporter>(std::move(listener_request),
syslog_file);
log_exporter->set_error_handler([](zx_status_t status) {
if (status != ZX_ERR_CANCELED) {
fprintf(stderr, "log exporter: Failed: %d (%s).\n",
status, zx_status_get_string(status));
}
});
log_exporter->set_file_error_handler([](const char* error) {
fprintf(stderr, "log exporter: Error writing to file: %s.\n", error);
});
status = log_exporter->StartThread();
if (status != ZX_OK) {
fprintf(stderr, "LaunchLogExporter: Failed to start log exporter: %d (%s).\n",
status, zx_status_get_string(status));
*error = START_LISTENER;
return nullptr;
}
return log_exporter;
}
} // namespace runtests