// 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 // // https://www.apache.org/licenses/LICENSE-2.0 // // 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 #include #include #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_list() { static IntrusiveList 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); encoder.WriteLineLevel( (level & PW_LOG_LEVEL_BITMASK) | ((line_number << PW_LOG_LEVEL_BITWIDTH) & ~PW_LOG_LEVEL_BITMASK)); encoder.WriteFlags(flags); // TODO(pwbug/301): Insert reasonable values for thread and timestamp. encoder.WriteTimestamp(0); // Accumulate the log message in this buffer, then output it. pw::StringBuffer buffer; va_list args; va_start(args, message); buffer.FormatVaList(message, args); va_end(args); encoder.WriteMessageString(buffer.c_str()); encoder.WriteThreadString(""); 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 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) { drop_count++; return; } // 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) { drop_count++; } // 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) { sink.HandleDropped(drop_count); } if (is_entry_valid) { sink.HandleEntry(log_entry); } } // All sinks have been notified of any drops. drop_count = 0; } } void AddSink(Sink& sink) { const std::lock_guard lock(sink_list_lock()); sink_list().push_back(sink); } void RemoveSink(Sink& sink) { const std::lock_guard lock(sink_list_lock()); sink_list().remove(sink); } } // namespace pw::log_sink