blob: 98bb0863db1c745f558ed45f995d27ae16fd3ebd [file] [log] [blame]
// Copyright 2021 The Pigweed Authors
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_log_sink/log_sink.h"
#include <atomic>
#include <cstring>
#include <mutex>
#include "pw_log/levels.h"
#include "pw_log/proto/log.pwpb.h"
#include "pw_protobuf/wire_format.h"
#include "pw_status/try.h"
#include "pw_string/string_builder.h"
#include "pw_sync/interrupt_spin_lock.h"
namespace pw::log_sink {
namespace {
// TODO: Make buffer sizes configurable.
constexpr size_t kMaxMessageStringSize = 32;
constexpr size_t kEncodeBufferSize = 128;
size_t drop_count = 0;
// The sink list and its corresponding lock are Meyer's singletons, to ensure
// they are constructed before use. This enables us to use logging before C++
// global construction has completed.
IntrusiveList<Sink>& sink_list() {
static IntrusiveList<Sink> sink_list;
return sink_list;
pw::sync::InterruptSpinLock& sink_list_lock() {
// TODO(pwbug/304): Make lock selection configurable, some applications may
// not be able to tolerate interrupt jitter and may prefer a pw::sync::Mutex.
static pw::sync::InterruptSpinLock sink_list_lock;
return sink_list_lock;
} // namespace
// This is a fully loaded, inefficient-at-the-callsite, log implementation.
extern "C" void pw_LogSink_Log(int level,
unsigned int flags,
const char* /* module_name */,
const char* /* file_name */,
int line_number,
const char* /* function_name */,
const char* message,
...) {
// Encode message to the LogEntry protobuf.
std::byte encode_buffer[kEncodeBufferSize];
pw::protobuf::NestedEncoder nested_encoder(encode_buffer);
pw::log::LogEntry::Encoder encoder(&nested_encoder);
((line_number << PW_LOG_LEVEL_BITS) & ~PW_LOG_LEVEL_BITMASK));
// TODO(pwbug/301): Insert reasonable values for thread and timestamp.
// Accumulate the log message in this buffer, then output it.
pw::StringBuffer<kMaxMessageStringSize> buffer;
va_list args;
va_start(args, message);
buffer.FormatVaList(message, args);
ConstByteSpan log_entry;
Status status = nested_encoder.Encode(&log_entry);
bool is_entry_valid = buffer.status().ok() && status.ok();
// TODO(pwbug/305): Consider using a shared buffer between users. For now,
// only lock after completing the encoding.
const std::lock_guard<pw::sync::InterruptSpinLock> lock(sink_list_lock());
// If no sinks are configured, ignore the message. When sinks are attached,
// they will receive this drop count to indicate logs drop to early boot.
// The drop count is cleared after it is sent to a sink, so sinks attached
// later will not receive drop counts from early boot.
if (sink_list().size() == 0) {
// If an encoding failure occurs or the constructed log entry is larger
// than the maximum allowed size, the log is dropped.
if (!is_entry_valid) {
// Push entries to all attached sinks. This is a synchronous operation, so
// attached sinks should avoid blocking when processing entries. If the log
// entry is not valid, only the drop notification is sent to the sinks.
for (auto& sink : sink_list()) {
// The drop count is always provided before sending entries, to ensure the
// sink processes drops in-order.
if (drop_count > 0) {
if (is_entry_valid) {
// All sinks have been notified of any drops.
drop_count = 0;
void AddSink(Sink& sink) {
const std::lock_guard lock(sink_list_lock());
void RemoveSink(Sink& sink) {
const std::lock_guard lock(sink_list_lock());
} // namespace pw::log_sink