blob: 774c390935f9566b39fd6dd037f35ea793d09d3d [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"
22#include "pw_log_proto/log.pwpb.h"
23#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) |
69 ((line_number << PW_LOG_LEVEL_BITWIDTH) & ~PW_LOG_LEVEL_BITMASK));
70 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);
82 encoder.WriteMessageString(buffer.c_str());
83 encoder.WriteThreadString("");
84
85 ConstByteSpan log_entry;
86 Status status = nested_encoder.Encode(&log_entry);
87 bool is_entry_valid = buffer.status().ok() && status.ok();
88
89 // TODO(pwbug/305): Consider using a shared buffer between users. For now,
90 // only lock after completing the encoding.
91 {
Ewout van Bekkumda2a62d2021-03-12 11:34:47 -080092 const std::lock_guard<pw::sync::InterruptSpinLock> lock(sink_list_lock());
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -080093
94 // If no sinks are configured, ignore the message. When sinks are attached,
95 // they will receive this drop count to indicate logs drop to early boot.
96 // The drop count is cleared after it is sent to a sink, so sinks attached
97 // later will not receive drop counts from early boot.
98 if (sink_list().size() == 0) {
99 drop_count++;
100 return;
101 }
102
103 // If an encoding failure occurs or the constructed log entry is larger
104 // than the maximum allowed size, the log is dropped.
105 if (!is_entry_valid) {
106 drop_count++;
107 }
108
109 // Push entries to all attached sinks. This is a synchronous operation, so
110 // attached sinks should avoid blocking when processing entries. If the log
111 // entry is not valid, only the drop notification is sent to the sinks.
112 for (auto& sink : sink_list()) {
113 // The drop count is always provided before sending entries, to ensure the
114 // sink processes drops in-order.
115 if (drop_count > 0) {
116 sink.HandleDropped(drop_count);
117 }
118 if (is_entry_valid) {
119 sink.HandleEntry(log_entry);
120 }
121 }
122 // All sinks have been notified of any drops.
123 drop_count = 0;
124 }
125}
126
127void AddSink(Sink& sink) {
Ewout van Bekkumda2a62d2021-03-12 11:34:47 -0800128 const std::lock_guard lock(sink_list_lock());
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -0800129 sink_list().push_back(sink);
130}
131
132void RemoveSink(Sink& sink) {
Ewout van Bekkumda2a62d2021-03-12 11:34:47 -0800133 const std::lock_guard lock(sink_list_lock());
Prashanth Swaminathan49a4a822021-01-12 18:41:52 -0800134 sink_list().remove(sink);
135}
136
137} // namespace pw::log_sink