blob: c6188a468208fc072ec28d341d3918292f247c24 [file] [log] [blame]
// Copyright 2020 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 <assert.h>
#include <fcntl.h>
#include <fuchsia/diagnostics/stream/cpp/fidl.h>
#include <fuchsia/logger/cpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/stdcompat/variant.h>
#include <lib/syslog/cpp/log_level.h>
#include <lib/syslog/cpp/logging_backend.h>
#include <lib/syslog/cpp/logging_backend_fuchsia_globals.h>
#include <lib/syslog/streams/cpp/encode.h>
#include <lib/zx/channel.h>
#include <lib/zx/process.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include "lib/syslog/streams/cpp/fields.h"
#include "logging_backend_fuchsia_private.h"
#include "logging_backend_shared.h"
#include "macros.h"
namespace syslog_backend {
// Flushes a record to the legacy fx_logger, if available.
// Returns true on success.
bool fx_log_compat_flush_record(LogBuffer* buffer);
// Attempts to reconfigure the legacy fx_logger if available.
// Returns the file descriptor if one was configured.
// A return value of -1 indicates that no file was opened.
int fx_log_compat_reconfigure(syslog::LogSettings& settings,
const std::initializer_list<std::string>& tags);
bool HasStructuredBackend() { return true; }
using log_word_t = uint64_t;
zx_koid_t GetKoid(zx_handle_t handle) {
zx_info_handle_basic_t info;
// We need to use _zx_object_get_info to avoid breaking the driver ABI.
// fake_ddk can fake out this method, which results in us deadlocking
// when used in certain drivers because the fake doesn't properly pass-through
// to the real syscall in this case.
zx_status_t status =
_zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.koid : ZX_KOID_INVALID;
}
static zx_koid_t pid = GetKoid(zx_process_self());
static thread_local zx_koid_t tid = GetCurrentThreadKoid();
// Represents a slice of a buffer of type T.
template <typename T>
class DataSlice {
public:
DataSlice(T* ptr, WordOffset<T> slice) : ptr_(ptr), slice_(slice) {}
T& operator[](WordOffset<T> offset) {
offset.AssertValid();
return ptr_[offset];
}
const T& operator[](WordOffset<T> offset) const { return ptr_[offset.unsafe_get()]; }
WordOffset<T> slice() { return slice_; }
T* data() { return ptr_; }
private:
T* ptr_;
WordOffset<T> slice_;
};
static DataSlice<const char> SliceFromString(const std::string& string) {
return DataSlice<const char>(
string.data(), WordOffset<const char>::FromByteOffset(ByteOffset::Unbounded(string.size())));
}
template <typename T, size_t size>
static DataSlice<const T> SliceFromArray(const T (&array)[size]) {
return DataSlice<const T>(array, size);
}
template <size_t size>
static DataSlice<const char> SliceFromArray(const char (&array)[size]) {
return DataSlice<const char>(
array, WordOffset<const char>::FromByteOffset(ByteOffset::Unbounded(size - 1)));
}
static constexpr int WORD_SIZE = sizeof(log_word_t); // See sdk/lib/syslog/streams/cpp/encode.cc
class DataBuffer {
public:
static constexpr auto kBufferSize = (1 << 15) / WORD_SIZE;
void Write(const log_word_t* data, WordOffset<log_word_t> length) {
for (size_t i = 0; i < length.unsafe_get(); i++) {
buffer_[cursor_.unsafe_get() + i] = data[i];
}
cursor_ = cursor_ + length;
}
WordOffset<log_word_t> WritePadded(const void* msg, const ByteOffset& length) {
auto retval = WritePaddedInternal(&buffer_[cursor_.unsafe_get()], msg, length);
cursor_ = cursor_ + retval;
return retval;
}
template <typename T>
void Write(const T& data) {
static_assert(sizeof(T) >= sizeof(log_word_t));
static_assert(alignof(T) >= sizeof(log_word_t));
Write(reinterpret_cast<const log_word_t*>(&data), cursor_ + (sizeof(T) / sizeof(log_word_t)));
}
DataSlice<log_word_t> GetSlice() { return DataSlice<log_word_t>(buffer_, cursor_); }
private:
WordOffset<log_word_t> cursor_;
log_word_t buffer_[kBufferSize];
};
struct RecordState {
RecordState()
: arg_size(WordOffset<log_word_t>::FromByteOffset(
ByteOffset::FromBuffer(0, sizeof(LogBuffer::data)))),
current_key_size(ByteOffset::FromBuffer(0, sizeof(LogBuffer::data))),
cursor(WordOffset<log_word_t>::FromByteOffset(
ByteOffset::FromBuffer(0, sizeof(LogBuffer::data)))) {}
// Header of the record itself
uint64_t* header;
syslog::LogSeverity log_severity;
::fuchsia::diagnostics::Severity severity;
// arg_size in words
WordOffset<log_word_t> arg_size;
zx_handle_t socket = ZX_HANDLE_INVALID;
// key_size in bytes
ByteOffset current_key_size;
// Header of the current argument being encoded
uint64_t* current_header_position = 0;
uint32_t dropped_count = 0;
// Current position (in 64-bit words) into the buffer.
WordOffset<log_word_t> cursor;
// True if encoding was successful, false otherwise
bool encode_success = true;
// Message string -- valid if severity is FATAL. For FATAL
// logs the caller is responsible for ensuring the string
// is valid for the duration of the call (which our macros
// will ensure for current users).
const char* msg_string;
static RecordState* CreatePtr(LogBuffer* buffer) {
return reinterpret_cast<RecordState*>(&buffer->record_state);
}
size_t PtrToIndex(void* ptr) {
return reinterpret_cast<size_t>(static_cast<uint8_t*>(ptr)) - reinterpret_cast<size_t>(header);
}
};
static_assert(sizeof(RecordState) <= sizeof(LogBuffer::record_state));
static_assert(std::alignment_of<RecordState>() == sizeof(uint64_t));
// Used for accessing external data buffers provided by clients.
// Used by the Encoder to do in-place encoding of data
class ExternalDataBuffer {
public:
explicit ExternalDataBuffer(LogBuffer* buffer)
: buffer_(&buffer->data[0]), cursor_(RecordState::CreatePtr(buffer)->cursor) {}
ExternalDataBuffer(log_word_t* data, size_t length, WordOffset<log_word_t>& cursor)
: buffer_(data), cursor_(cursor) {}
__WARN_UNUSED_RESULT bool Write(const log_word_t* data, WordOffset<log_word_t> length) {
if (!cursor_.in_bounds(length)) {
return false;
}
for (size_t i = 0; i < length.unsafe_get(); i++) {
buffer_[(cursor_ + i).unsafe_get()] = data[i];
}
cursor_ = cursor_ + length;
return true;
}
__WARN_UNUSED_RESULT bool WritePadded(const void* msg, const ByteOffset& byte_count,
WordOffset<log_word_t>* written) {
assert(written != nullptr);
WordOffset<log_word_t> word_count = cursor_.begin().AddPadded(byte_count);
if (!cursor_.in_bounds(word_count)) {
return false;
}
auto retval = WritePaddedInternal(buffer_ + cursor_.unsafe_get(), msg, byte_count);
cursor_ = cursor_ + retval;
*written = retval;
return true;
}
template <typename T>
__WARN_UNUSED_RESULT bool Write(const T& data) {
static_assert(sizeof(T) >= sizeof(log_word_t));
static_assert(alignof(T) >= sizeof(log_word_t));
return Write(reinterpret_cast<const log_word_t*>(&data),
WordOffset<log_word_t>::FromByteOffset(
ByteOffset::Unbounded((sizeof(T) / sizeof(log_word_t)) * sizeof(log_word_t))));
}
uint64_t* data() { return buffer_ + cursor_.unsafe_get(); }
DataSlice<log_word_t> GetSlice() { return DataSlice<log_word_t>(buffer_, cursor_); }
private:
// Start of buffer
log_word_t* buffer_ = nullptr;
// Current location in buffer (in words)
WordOffset<log_word_t>& cursor_;
};
template <typename T>
class Encoder {
public:
explicit Encoder(T& buffer) { buffer_ = &buffer; }
void Begin(RecordState& state, zx_time_t timestamp, ::fuchsia::diagnostics::Severity severity) {
state.severity = severity;
state.header = buffer_->data();
log_word_t empty_header = 0;
state.encode_success &= buffer_->Write(empty_header);
state.encode_success &= buffer_->Write(timestamp);
}
void FlushPreviousArgument(RecordState& state) { state.arg_size.reset(); }
void AppendArgumentKey(RecordState& state, DataSlice<const char> key) {
FlushPreviousArgument(state);
auto header_position = buffer_->data();
log_word_t empty_header = 0;
state.encode_success &= buffer_->Write(empty_header);
WordOffset<log_word_t> s_size =
WordOffset<log_word_t>::FromByteOffset(ByteOffset::Unbounded(0));
state.encode_success &= buffer_->WritePadded(key.data(), key.slice().ToByteOffset(), &s_size);
state.arg_size = s_size + 1; // offset by 1 for the header
state.current_key_size = key.slice().ToByteOffset();
state.current_header_position = header_position;
}
uint64_t ComputeArgHeader(RecordState& state, int type, uint64_t value_ref = 0) {
return ArgumentFields::Type::Make(type) |
ArgumentFields::SizeWords::Make(state.arg_size.unsafe_get()) |
ArgumentFields::NameRefVal::Make(state.current_key_size.unsafe_get()) |
ArgumentFields::NameRefMSB::Make(state.current_key_size.unsafe_get() > 0 ? 1 : 0) |
ArgumentFields::ValueRef::Make(value_ref) | ArgumentFields::Reserved::Make(0);
}
void AppendArgumentValue(RecordState& state, int64_t value) {
int type = 3;
state.encode_success &= buffer_->Write(value);
state.arg_size = state.arg_size + 1;
*state.current_header_position = ComputeArgHeader(state, type);
}
void AppendArgumentValue(RecordState& state, uint64_t value) {
int type = 4;
state.encode_success &= buffer_->Write(value);
state.arg_size = state.arg_size + 1;
*state.current_header_position = ComputeArgHeader(state, type);
}
void AppendArgumentValue(RecordState& state, double value) {
int type = 5;
state.encode_success &= buffer_->Write(value);
state.arg_size = state.arg_size + 1;
*state.current_header_position = ComputeArgHeader(state, type);
}
void AppendArgumentValue(RecordState& state, DataSlice<const char> string) {
int type = 6;
WordOffset<log_word_t> written =
WordOffset<log_word_t>::FromByteOffset(ByteOffset::Unbounded(0));
state.encode_success &=
buffer_->WritePadded(string.data(), string.slice().ToByteOffset(), &written);
state.arg_size = state.arg_size + written;
*state.current_header_position = ComputeArgHeader(
state, type, string.slice().unsafe_get() > 0 ? (1 << 15) | string.slice().unsafe_get() : 0);
}
void End(RecordState& state) {
// See src/lib/diagnostics/stream/rust/src/lib.rs
constexpr auto kTracingFormatLogRecordType = 9;
FlushPreviousArgument(state);
uint64_t header =
HeaderFields::Type::Make(kTracingFormatLogRecordType) |
HeaderFields::SizeWords::Make(static_cast<size_t>(buffer_->data() - state.header)) |
HeaderFields::Reserved::Make(0) | HeaderFields::Severity::Make(state.severity);
*state.header = header;
}
private:
T* buffer_;
};
const size_t kMaxTags = 4; // Legacy from ulib/syslog. Might be worth rethinking.
// Compiler thinks this is unused even though WriteLogToSocket uses it.
__UNUSED const char kMessageFieldName[] = "message";
const char kVerbosityFieldName[] = "verbosity";
const char kPidFieldName[] = "pid";
const char kTidFieldName[] = "tid";
const char kDroppedLogsFieldName[] = "dropped_logs";
const char kTagFieldName[] = "tag";
const char kFileFieldName[] = "file";
const char kLineFieldName[] = "line";
class LogState {
public:
static void Set(const syslog::LogSettings& settings);
static void Set(const syslog::LogSettings& settings,
const std::initializer_list<std::string>& tags);
static const LogState& Get();
syslog::LogSeverity min_severity() const { return min_severity_; }
template <typename T>
void WriteLog(syslog::LogSeverity severity, const char* file_name, unsigned int line,
const char* msg, const char* condition, const T& value) const;
const std::string* tags() const { return tags_; }
size_t tag_count() const { return num_tags_; }
// Allowed to be const because descriptor_ is mutable
cpp17::variant<zx::socket, std::ofstream>& descriptor() const { return descriptor_; }
cpp17::optional<int> fd() const { return fd_; }
private:
LogState(const syslog::LogSettings& settings, const std::initializer_list<std::string>& tags);
bool WriteLogToFile(std::ofstream* file_ptr, zx_time_t time, zx_koid_t pid, zx_koid_t tid,
syslog::LogSeverity severity, const char* file_name, unsigned int line,
const char* tag, const char* condition, const std::string& msg) const;
syslog::LogSeverity min_severity_;
cpp17::optional<int> fd_;
zx_koid_t pid_;
mutable cpp17::variant<zx::socket, std::ofstream> descriptor_ = zx::socket();
std::string tags_[kMaxTags];
std::string tag_str_;
size_t num_tags_ = 0;
};
void BeginRecordInternal(LogBuffer* buffer, syslog::LogSeverity severity, const char* file_name,
unsigned int line, const char* msg, const char* condition,
zx_handle_t socket) {
// Ensure we have log state
auto& log_state = LogState::Get();
cpp17::optional<int8_t> raw_severity;
if (log_state.fd() != -1) {
BeginRecordLegacy(buffer, severity, file_name, line, msg, condition);
return;
}
// Validate that the severity matches the FIDL definition in
// sdk/fidl/fuchsia.diagnostics/severity.fidl.
if ((severity % 0x10) || (severity > 0x60) || (severity < 0x10)) {
raw_severity = severity;
severity = syslog::LOG_DEBUG;
}
zx_time_t time = zx_clock_get_monotonic();
auto* state = RecordState::CreatePtr(buffer);
RecordState& record = *state;
// Invoke the constructor of RecordState to construct a valid RecordState
// inside the LogBuffer.
new (state) RecordState;
if (socket != ZX_HANDLE_INVALID) {
state->socket = socket;
} else {
state->socket = cpp17::get<zx::socket>(log_state.descriptor()).get();
}
if (severity == syslog::LOG_FATAL) {
state->msg_string = msg;
}
state->log_severity = severity;
ExternalDataBuffer external_buffer(buffer);
Encoder<ExternalDataBuffer> encoder(external_buffer);
encoder.Begin(*state, time, ::fuchsia::diagnostics::Severity(severity));
encoder.AppendArgumentKey(record, SliceFromArray(kPidFieldName));
encoder.AppendArgumentValue(record, static_cast<uint64_t>(pid));
encoder.AppendArgumentKey(record, SliceFromArray(kTidFieldName));
encoder.AppendArgumentValue(record, static_cast<uint64_t>(tid));
auto dropped_count = GetAndResetDropped();
record.dropped_count = dropped_count;
if (raw_severity) {
encoder.AppendArgumentKey(record, SliceFromString(kVerbosityFieldName));
encoder.AppendArgumentValue(record, static_cast<int64_t>(raw_severity.value()));
}
if (dropped_count) {
encoder.AppendArgumentKey(record, SliceFromString(kDroppedLogsFieldName));
encoder.AppendArgumentValue(record, static_cast<uint64_t>(dropped_count));
}
for (size_t i = 0; i < GetState()->tag_count(); i++) {
encoder.AppendArgumentKey(record, SliceFromString(kTagFieldName));
encoder.AppendArgumentValue(record, SliceFromString(GetState()->tags()[i]));
}
if (msg) {
encoder.AppendArgumentKey(record, SliceFromString(kMessageFieldName));
encoder.AppendArgumentValue(record, SliceFromString(msg));
}
if (file_name) {
encoder.AppendArgumentKey(record, SliceFromString(kFileFieldName));
encoder.AppendArgumentValue(record, SliceFromString(file_name));
}
encoder.AppendArgumentKey(record, SliceFromString(kLineFieldName));
encoder.AppendArgumentValue(record, static_cast<uint64_t>(line));
}
void BeginRecord(LogBuffer* buffer, syslog::LogSeverity severity, const char* file_name,
unsigned int line, const char* msg, const char* condition) {
BeginRecordInternal(buffer, severity, file_name, line, msg, condition, ZX_HANDLE_INVALID);
}
void BeginRecordWithSocket(LogBuffer* buffer, syslog::LogSeverity severity, const char* file_name,
unsigned int line, const char* msg, const char* condition,
zx_handle_t socket) {
BeginRecordInternal(buffer, severity, file_name, line, msg, condition, socket);
}
void WriteKeyValue(LogBuffer* buffer, const char* key, const char* value) {
auto& log_state = LogState::Get();
if (log_state.fd() != -1) {
WriteKeyValueLegacy(buffer, key, value);
return;
}
auto* state = RecordState::CreatePtr(buffer);
ExternalDataBuffer external_buffer(buffer);
Encoder<ExternalDataBuffer> encoder(external_buffer);
encoder.AppendArgumentKey(
*state, DataSlice<const char>(
key, WordOffset<const char>::FromByteOffset(ByteOffset::Unbounded(strlen(key)))));
encoder.AppendArgumentValue(
*state, DataSlice<const char>(value, WordOffset<const char>::FromByteOffset(
ByteOffset::Unbounded(strlen(value)))));
}
void WriteKeyValue(LogBuffer* buffer, const char* key, int64_t value) {
auto& log_state = LogState::Get();
if (log_state.fd() != -1) {
WriteKeyValueLegacy(buffer, key, value);
return;
}
auto* state = RecordState::CreatePtr(buffer);
ExternalDataBuffer external_buffer(buffer);
Encoder<ExternalDataBuffer> encoder(external_buffer);
encoder.AppendArgumentKey(
*state, DataSlice<const char>(
key, WordOffset<const char>::FromByteOffset(ByteOffset::Unbounded(strlen(key)))));
encoder.AppendArgumentValue(*state, value);
}
void WriteKeyValue(LogBuffer* buffer, const char* key, uint64_t value) {
auto& log_state = LogState::Get();
if (log_state.fd() != -1) {
WriteKeyValueLegacy(buffer, key, value);
return;
}
auto* state = RecordState::CreatePtr(buffer);
ExternalDataBuffer external_buffer(buffer);
Encoder<ExternalDataBuffer> encoder(external_buffer);
encoder.AppendArgumentKey(
*state, DataSlice<const char>(
key, WordOffset<const char>::FromByteOffset(ByteOffset::Unbounded(strlen(key)))));
encoder.AppendArgumentValue(*state, value);
}
void WriteKeyValue(LogBuffer* buffer, const char* key, double value) {
auto& log_state = LogState::Get();
if (log_state.fd() != -1) {
WriteKeyValueLegacy(buffer, key, value);
return;
}
auto* state = RecordState::CreatePtr(buffer);
ExternalDataBuffer external_buffer(buffer);
Encoder<ExternalDataBuffer> encoder(external_buffer);
encoder.AppendArgumentKey(
*state, DataSlice<const char>(
key, WordOffset<const char>::FromByteOffset(ByteOffset::Unbounded(strlen(key)))));
encoder.AppendArgumentValue(*state, value);
}
void EndRecord(LogBuffer* buffer) {
auto& log_state = LogState::Get();
if (log_state.fd() != -1) {
EndRecordLegacy(buffer);
return;
}
auto* state = RecordState::CreatePtr(buffer);
ExternalDataBuffer external_buffer(buffer);
Encoder<ExternalDataBuffer> encoder(external_buffer);
encoder.End(*state);
}
bool FlushRecord(LogBuffer* buffer) {
auto& log_state = LogState::Get();
if (log_state.fd() != -1) {
return fx_log_compat_flush_record(buffer);
}
auto* state = RecordState::CreatePtr(buffer);
if (!state->encode_success) {
return false;
}
ExternalDataBuffer external_buffer(buffer);
Encoder<ExternalDataBuffer> encoder(external_buffer);
auto slice = external_buffer.GetSlice();
auto status = zx_socket_write(state->socket, 0, slice.data(),
slice.slice().ToByteOffset().unsafe_get(), nullptr);
if (status != ZX_OK) {
AddDropped(state->dropped_count + 1);
}
if (state->log_severity == syslog::LOG_FATAL) {
std::cerr << state->msg_string << std::endl;
abort();
}
return status != ZX_ERR_BAD_STATE && status != ZX_ERR_PEER_CLOSED &&
state->log_severity != syslog::LOG_FATAL;
}
bool LogState::WriteLogToFile(std::ofstream* file_ptr, zx_time_t time, zx_koid_t pid, zx_koid_t tid,
syslog::LogSeverity severity, const char* file_name,
unsigned int line, const char* tag, const char* condition,
const std::string& msg) const {
auto& file = *file_ptr;
file << "[" << std::setw(5) << std::setfill('0') << time / 1000000000UL << "." << std::setw(6)
<< (time / 1000UL) % 1000000UL << std::setw(0) << "][" << pid << "][" << tid << "][";
auto& tag_str = tag_str_;
file << tag_str;
if (tag) {
if (!tag_str.empty()) {
file << ", ";
}
file << tag;
}
file << "] ";
switch (severity) {
case syslog::LOG_TRACE:
file << "TRACE";
break;
case syslog::LOG_DEBUG:
file << "DEBUG";
break;
case syslog::LOG_INFO:
file << "INFO";
break;
case syslog::LOG_WARNING:
file << "WARNING";
break;
case syslog::LOG_ERROR:
file << "ERROR";
break;
case syslog::LOG_FATAL:
file << "FATAL";
break;
default:
file << "VLOG(" << (syslog::LOG_INFO - severity) << ")";
}
file << ": [" << file_name << "(" << line << ")] " << msg << std::endl;
return severity != syslog::LOG_FATAL;
}
const LogState& LogState::Get() {
auto state = GetState();
if (!state) {
Set(syslog::LogSettings());
state = GetState();
}
return *state;
}
void LogState::Set(const syslog::LogSettings& settings) { Set(settings, {}); }
void LogState::Set(const syslog::LogSettings& settings,
const std::initializer_list<std::string>& tags) {
if (auto old = SetState(new LogState(settings, tags))) {
delete old;
}
}
LogState::LogState(const syslog::LogSettings& in_settings,
const std::initializer_list<std::string>& tags)
: min_severity_(in_settings.min_log_level), fd_(in_settings.log_fd), pid_(pid) {
syslog::LogSettings settings = in_settings;
min_severity_ = in_settings.min_log_level;
std::ostringstream tag_str;
for (auto& tag : tags) {
if (num_tags_) {
tag_str << ", ";
}
tag_str << tag;
tags_[num_tags_++] = tag;
if (num_tags_ >= kMaxTags)
break;
}
tag_str_ = tag_str.str();
std::ofstream file;
if (!settings.log_file.empty()) {
fd_ = fx_log_compat_reconfigure(settings, tags);
}
if (fd_ != -1) {
return;
}
if (file.is_open()) {
descriptor_ = std::move(file);
} else {
zx::channel logger, logger_request;
if (zx::channel::create(0, &logger, &logger_request) != ZX_OK) {
return;
}
::fuchsia::logger::LogSink_SyncProxy logger_client(std::move(logger));
if (fdio_service_connect("/svc/fuchsia.logger.LogSink", logger_request.release()) != ZX_OK) {
return;
}
zx::socket local, remote;
if (zx::socket::create(ZX_SOCKET_DATAGRAM, &local, &remote) != ZX_OK) {
return;
}
auto result = logger_client.ConnectStructured(std::move(remote));
if (result != ZX_OK) {
return;
}
descriptor_ = std::move(local);
}
}
template <typename T>
void LogState::WriteLog(syslog::LogSeverity severity, const char* file_name, unsigned int line,
const char* msg, const char* condition, const T& value) const {
zx_time_t time = zx_clock_get_monotonic();
// Cached getter for a stringified version of the log message, so we stringify at most once.
auto msg_str = [as_str = std::string(), condition, msg, &value]() mutable -> const std::string& {
if (as_str.empty()) {
as_str = FullMessageString(condition, msg, value);
}
return as_str;
};
if (cpp17::holds_alternative<std::ofstream>(descriptor_)) {
auto& file = cpp17::get<std::ofstream>(descriptor_);
if (WriteLogToFile(&file, time, pid_, tid, severity, file_name, line, nullptr, condition,
msg_str())) {
return;
}
} else if (cpp17::holds_alternative<zx::socket>(descriptor_)) {
auto& socket = cpp17::get<zx::socket>(descriptor_);
std::string message;
if (msg) {
message.assign(msg);
}
if (WriteLogToSocket(&socket, time, pid_, tid, severity, file_name, line, message, condition,
value)) {
return;
}
}
std::cerr << msg_str() << std::endl;
}
void SetLogSettings(const syslog::LogSettings& settings) { LogState::Set(settings); }
void SetLogSettings(const syslog::LogSettings& settings,
const std::initializer_list<std::string>& tags) {
LogState::Set(settings, tags);
}
syslog::LogSeverity GetMinLogLevel() { return LogState::Get().min_severity(); }
} // namespace syslog_backend