blob: 98bb0863db1c745f558ed45f995d27ae16fd3ebd [file] [log] [blame]
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -08001// Copyright 2021 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15#include "pw_log_sink/log_sink.h"
16
17#include <atomic>
18#include <cstring>
19#include <mutex>
20
21#include "pw_log/levels.h"
Wyatt Hepler76c3a5c2021-05-26 09:57:11 -070022#include "pw_log/proto/log.pwpb.h"
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -080023#include "pw_protobuf/wire_format.h"
24#include "pw_status/try.h"
25#include "pw_string/string_builder.h"
Ewout van Bekkumda2a62d2021-03-12 11:34:47 -080026#include "pw_sync/interrupt_spin_lock.h"
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -080027
28namespace pw::log_sink {
29namespace {
30// TODO: Make buffer sizes configurable.
31constexpr size_t kMaxMessageStringSize = 32;
32constexpr size_t kEncodeBufferSize = 128;
33
34size_t drop_count = 0;
35
36// The sink list and its corresponding lock are Meyer's singletons, to ensure
37// they are constructed before use. This enables us to use logging before C++
38// global construction has completed.
39IntrusiveList<Sink>& sink_list() {
40 static IntrusiveList<Sink> sink_list;
41 return sink_list;
42}
43
Ewout van Bekkumda2a62d2021-03-12 11:34:47 -080044pw::sync::InterruptSpinLock& sink_list_lock() {
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -080045 // TODO(pwbug/304): Make lock selection configurable, some applications may
46 // not be able to tolerate interrupt jitter and may prefer a pw::sync::Mutex.
Ewout van Bekkumda2a62d2021-03-12 11:34:47 -080047 static pw::sync::InterruptSpinLock sink_list_lock;
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -080048 return sink_list_lock;
49}
50
51} // namespace
52
53// This is a fully loaded, inefficient-at-the-callsite, log implementation.
54extern "C" void pw_LogSink_Log(int level,
55 unsigned int flags,
56 const char* /* module_name */,
57 const char* /* file_name */,
58 int line_number,
59 const char* /* function_name */,
60 const char* message,
61 ...) {
62 // Encode message to the LogEntry protobuf.
63 std::byte encode_buffer[kEncodeBufferSize];
64 pw::protobuf::NestedEncoder nested_encoder(encode_buffer);
65 pw::log::LogEntry::Encoder encoder(&nested_encoder);
66
67 encoder.WriteLineLevel(
68 (level & PW_LOG_LEVEL_BITMASK) |
Wyatt Heplerbe3390b2021-05-26 22:59:46 -070069 ((line_number << PW_LOG_LEVEL_BITS) & ~PW_LOG_LEVEL_BITMASK));
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -080070 encoder.WriteFlags(flags);
71
72 // TODO(pwbug/301): Insert reasonable values for thread and timestamp.
73 encoder.WriteTimestamp(0);
74
75 // Accumulate the log message in this buffer, then output it.
76 pw::StringBuffer<kMaxMessageStringSize> buffer;
77 va_list args;
78
79 va_start(args, message);
80 buffer.FormatVaList(message, args);
81 va_end(args);
Wyatt Hepler0d4c9162021-05-26 09:27:22 -070082 encoder.WriteMessage(std::as_bytes(std::span(buffer.view())));
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -080083
84 ConstByteSpan log_entry;
85 Status status = nested_encoder.Encode(&log_entry);
86 bool is_entry_valid = buffer.status().ok() && status.ok();
87
88 // TODO(pwbug/305): Consider using a shared buffer between users. For now,
89 // only lock after completing the encoding.
90 {
Ewout van Bekkumda2a62d2021-03-12 11:34:47 -080091 const std::lock_guard<pw::sync::InterruptSpinLock> lock(sink_list_lock());
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -080092
93 // If no sinks are configured, ignore the message. When sinks are attached,
94 // they will receive this drop count to indicate logs drop to early boot.
95 // The drop count is cleared after it is sent to a sink, so sinks attached
96 // later will not receive drop counts from early boot.
97 if (sink_list().size() == 0) {
98 drop_count++;
99 return;
100 }
101
102 // If an encoding failure occurs or the constructed log entry is larger
103 // than the maximum allowed size, the log is dropped.
104 if (!is_entry_valid) {
105 drop_count++;
106 }
107
108 // Push entries to all attached sinks. This is a synchronous operation, so
109 // attached sinks should avoid blocking when processing entries. If the log
110 // entry is not valid, only the drop notification is sent to the sinks.
111 for (auto& sink : sink_list()) {
112 // The drop count is always provided before sending entries, to ensure the
113 // sink processes drops in-order.
114 if (drop_count > 0) {
115 sink.HandleDropped(drop_count);
116 }
117 if (is_entry_valid) {
118 sink.HandleEntry(log_entry);
119 }
120 }
121 // All sinks have been notified of any drops.
122 drop_count = 0;
123 }
124}
125
126void AddSink(Sink& sink) {
Ewout van Bekkumda2a62d2021-03-12 11:34:47 -0800127 const std::lock_guard lock(sink_list_lock());
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -0800128 sink_list().push_back(sink);
129}
130
131void RemoveSink(Sink& sink) {
Ewout van Bekkumda2a62d2021-03-12 11:34:47 -0800132 const std::lock_guard lock(sink_list_lock());
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -0800133 sink_list().remove(sink);
134}
135
136} // namespace pw::log_sink