blob: efa86469f27c8b3fc89611a106325a59a7ada66d [file] [log] [blame]
// Copyright 2019 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 "log_listener.h"
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/logger.h>
#include <lib/syslog/wire_format.h>
#include <src/lib/fxl/logging.h>
#include <zircon/status.h>
#include "format.h"
namespace netemul {
namespace internal {
/*
* LogListenerImpl
*
* public
*/
LogListenerImpl::LogListenerImpl(
fidl::InterfaceRequest<fuchsia::logger::LogListener> request,
std::string prefix, std::ostream* stream, bool klogs_enabled,
async_dispatcher_t* dispatcher, zx::socket log_sink)
: binding_(this, std::move(request), dispatcher),
prefix_(std::move(prefix)),
stream_(stream),
dropped_logs_(0),
klogs_enabled_(klogs_enabled) {
binding_.set_error_handler([](zx_status_t status) {
FXL_LOG(ERROR) << "LogListenerImpl error: " << zx_status_get_string(status)
<< std::endl;
});
if (!stream_) {
// modify the prefix when logging to syslog
prefix_ = "@" + prefix_;
if (log_sink.is_valid()) {
log_sock_ = std::move(log_sink);
} else {
auto ctx = sys::ComponentContext::Create();
fuchsia::logger::LogSinkPtr log;
ctx->svc()->Connect(log.NewRequest(dispatcher));
zx::socket out;
zx::socket::create(ZX_SOCKET_DATAGRAM, &out, &log_sock_);
log->Connect(std::move(out));
}
}
}
void LogListenerImpl::Log(fuchsia::logger::LogMessage m) {
// TODO(ghanan): Filter out kernel logs before reaching here.
// Ignore kernel logs
if (!klogs_enabled_ &&
std::find(m.tags.begin(), m.tags.end(), "klog") != m.tags.end()) {
return;
}
if (stream_) {
*stream_ << "[" << prefix_ << "]";
FormatTime(m.time);
*stream_ << "[" << m.pid << "]"
<< "[" << m.tid << "]";
FormatTags(m.tags);
FormatLogLevel(m.severity);
*stream_ << " " << m.msg << std::endl;
} else {
// if we don't have a stream, we're just going to append environment tags
// to the system log.
fx_log_packet_t packet;
packet.metadata.dropped_logs = dropped_logs_;
packet.metadata.pid = m.pid;
packet.metadata.tid = m.tid;
packet.metadata.severity = m.severity;
packet.metadata.time = m.time;
bool inline_prefix = true;
if (m.tags.size() < FX_LOG_MAX_TAGS) {
m.tags.push_back(prefix_);
inline_prefix = false;
}
// insert tags:
size_t pos = 0;
for (const auto& tag : m.tags) {
auto len = tag.length();
if (len > FX_LOG_MAX_TAG_LEN - 1) {
len = FX_LOG_MAX_TAG_LEN - 1;
}
packet.data[pos] = len;
pos++;
memcpy(&packet.data[pos], tag.c_str(), len);
pos += len;
}
packet.data[pos++] = 0;
if (inline_prefix && prefix_.length() + 4 + pos < sizeof(packet.data)) {
packet.data[pos++] = '[';
memcpy(&packet.data[pos], prefix_.c_str(), prefix_.length());
pos += prefix_.length();
packet.data[pos++] = ']';
packet.data[pos++] = ' ';
}
ZX_ASSERT(pos <= sizeof(packet.data));
auto remain = sizeof(packet.data) - pos;
if (m.msg.length() + 1 < remain) {
remain = m.msg.length() + 1;
}
if (remain > 0) {
memcpy(&packet.data[pos], m.msg.c_str(), remain);
packet.data[pos + remain - 1] = 0;
pos += remain;
}
size_t actual;
pos += sizeof(packet.metadata);
if (log_sock_.write(0, &packet, pos, &actual) == ZX_OK && actual == pos) {
dropped_logs_ = 0;
} else {
dropped_logs_++;
}
}
}
void LogListenerImpl::LogMany(std::vector<fuchsia::logger::LogMessage> ms) {
for (auto& m : ms) {
Log(std::move(m));
}
}
void LogListenerImpl::Done() {
FXL_LOG(INFO) << "DONE";
return;
}
/*
* LogListenerImpl
*
* private
*/
void LogListenerImpl::FormatTime(const zx_time_t timestamp) {
internal::FormatTime(stream_, timestamp);
}
void LogListenerImpl::FormatTags(const std::vector<std::string>& tags) {
auto it = tags.begin();
*stream_ << "[";
while (it != tags.end()) {
*stream_ << *it;
it = std::next(it);
if (it != tags.end()) {
*stream_ << ",";
}
}
*stream_ << "]";
}
void LogListenerImpl::FormatLogLevel(const int32_t severity) {
switch (severity) {
case 0:
*stream_ << "[INFO]";
break;
case 1:
*stream_ << "[WARNING]";
break;
case 2:
*stream_ << "[ERROR]";
break;
case 3:
*stream_ << "[FATAL]";
break;
default:
if (severity > 3) {
*stream_ << "[INVALID]";
} else {
*stream_ << "[VLOG(" << severity << ")]";
}
}
}
} // namespace internal
/*
* LogListener
*
* public
*/
LogListener::LogListener(std::unique_ptr<internal::LogListenerImpl> impl)
: loglistener_impl_(std::move(impl)) {}
std::unique_ptr<LogListener> LogListener::Create(
fuchsia::netemul::environment::ManagedEnvironment* env,
const fuchsia::netemul::environment::LoggerOptions& logger_options,
const std::string& prefix, async_dispatcher_t* dispatcher) {
if (!logger_options.has_enabled() || !logger_options.enabled()) {
return nullptr;
}
// Create the client side interface and connect to a remote
// that implementes the fuchsia.logger.Log interface within
// `env`.
fuchsia::logger::LogPtr log_service;
log_service.set_error_handler([](zx_status_t status) {
FXL_LOG(ERROR) << "LogListenerImpl error: " << zx_status_get_string(status)
<< std::endl;
});
env->ConnectToService(fuchsia::logger::Log::Name_,
log_service.NewRequest().TakeChannel());
bool klogs_enabled =
logger_options.has_klogs_enabled() && logger_options.klogs_enabled();
auto* stream =
logger_options.has_syslog_output() && logger_options.syslog_output()
? nullptr
: &std::cout;
// Create an instance of the LogListener implementation
// and start listening for logs
fidl::InterfaceHandle<fuchsia::logger::LogListener> loglistener_h;
std::unique_ptr<internal::LogListenerImpl> impl(new internal::LogListenerImpl(
loglistener_h.NewRequest(), prefix, stream, klogs_enabled, dispatcher));
if (logger_options.has_filter_options()) {
log_service->Listen(std::move(loglistener_h),
std::make_unique<fuchsia::logger::LogFilterOptions>(
logger_options.filter_options()));
} else {
log_service->Listen(std::move(loglistener_h), nullptr);
}
return std::make_unique<LogListener>(std::move(impl));
}
} // namespace netemul