1 // 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"
26 #include "pw_sync/interrupt_spin_lock.h"
27 
28 namespace pw::log_sink {
29 namespace {
30 // TODO: Make buffer sizes configurable.
31 constexpr size_t kMaxMessageStringSize = 32;
32 constexpr size_t kEncodeBufferSize = 128;
33 
34 size_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.
sink_list()39 IntrusiveList<Sink>& sink_list() {
40   static IntrusiveList<Sink> sink_list;
41   return sink_list;
42 }
43 
sink_list_lock()44 pw::sync::InterruptSpinLock& sink_list_lock() {
45   // 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.
47   static pw::sync::InterruptSpinLock sink_list_lock;
48   return sink_list_lock;
49 }
50 
51 }  // namespace
52 
53 // This is a fully loaded, inefficient-at-the-callsite, log implementation.
pw_LogSink_Log(int level,unsigned int flags,const char *,const char *,int line_number,const char *,const char * message,...)54 extern "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   {
92     const std::lock_guard<pw::sync::InterruptSpinLock> lock(sink_list_lock());
93 
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 
AddSink(Sink & sink)127 void AddSink(Sink& sink) {
128   const std::lock_guard lock(sink_list_lock());
129   sink_list().push_back(sink);
130 }
131 
RemoveSink(Sink & sink)132 void RemoveSink(Sink& sink) {
133   const std::lock_guard lock(sink_list_lock());
134   sink_list().remove(sink);
135 }
136 
137 }  // namespace pw::log_sink
138