1 // Copyright 2024 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 <fidl/fuchsia.logger/cpp/fidl.h>
16 #include <lib/component/incoming/cpp/protocol.h>
17 #include <lib/syslog/structured_backend/cpp/fuchsia_syslog.h>
18 #include <zircon/process.h>
19
20 #include <cstdarg>
21 #include <cstdio>
22 #include <string_view>
23
24 #include "pw_assert/check.h"
25 #include "pw_log/levels.h"
26 #include "pw_log_fuchsia/log_backend.h"
27 #include "pw_string/string_builder.h"
28
29 namespace {
30
31 // This is an arbitrary size limit of logs.
32 constexpr size_t kBufferSize = 400;
33
34 // Returns the part of a path following the final '/', or the whole path if
35 // there is no '/'.
BaseName(const char * path)36 constexpr const char* BaseName(const char* path) {
37 for (const char* c = path; c && (*c != '\0'); c++) {
38 if (*c == '/') {
39 path = c + 1;
40 }
41 }
42 return path;
43 }
44
LogLevelToString(int severity)45 const char* LogLevelToString(int severity) {
46 switch (severity) {
47 case PW_LOG_LEVEL_ERROR:
48 return "ERROR";
49 case PW_LOG_LEVEL_WARN:
50 return "WARN";
51 case PW_LOG_LEVEL_INFO:
52 return "INFO";
53 case PW_LOG_LEVEL_DEBUG:
54 return "DEBUG";
55 default:
56 return "UNKNOWN";
57 }
58 }
59
FuchsiaLogSeverityFromFidl(fuchsia_diagnostics::Severity severity)60 FuchsiaLogSeverity FuchsiaLogSeverityFromFidl(
61 fuchsia_diagnostics::Severity severity) {
62 switch (severity) {
63 case fuchsia_diagnostics::Severity::kFatal:
64 return FUCHSIA_LOG_FATAL;
65 case fuchsia_diagnostics::Severity::kError:
66 return FUCHSIA_LOG_ERROR;
67 case fuchsia_diagnostics::Severity::kWarn:
68 return FUCHSIA_LOG_WARNING;
69 case fuchsia_diagnostics::Severity::kInfo:
70 return FUCHSIA_LOG_INFO;
71 case fuchsia_diagnostics::Severity::kDebug:
72 return FUCHSIA_LOG_DEBUG;
73 case fuchsia_diagnostics::Severity::kTrace:
74 return FUCHSIA_LOG_TRACE;
75 }
76 }
77
PigweedLevelToFuchsiaSeverity(int pw_level)78 FuchsiaLogSeverity PigweedLevelToFuchsiaSeverity(int pw_level) {
79 switch (pw_level) {
80 case PW_LOG_LEVEL_ERROR:
81 return FUCHSIA_LOG_ERROR;
82 case PW_LOG_LEVEL_WARN:
83 return FUCHSIA_LOG_WARNING;
84 case PW_LOG_LEVEL_INFO:
85 return FUCHSIA_LOG_INFO;
86 case PW_LOG_LEVEL_DEBUG:
87 return FUCHSIA_LOG_DEBUG;
88 default:
89 return FUCHSIA_LOG_ERROR;
90 }
91 }
92
93 class LogState {
94 public:
Initialize(async_dispatcher_t * dispatcher)95 void Initialize(async_dispatcher_t* dispatcher) {
96 dispatcher_ = dispatcher;
97
98 auto client_end = ::component::Connect<fuchsia_logger::LogSink>();
99 PW_CHECK(client_end.is_ok());
100 log_sink_.Bind(std::move(*client_end), dispatcher_);
101
102 zx::socket local, remote;
103 zx::socket::create(ZX_SOCKET_DATAGRAM, &local, &remote);
104 ::fidl::OneWayStatus result =
105 log_sink_->ConnectStructured(std::move(remote));
106 PW_CHECK(result.ok());
107
108 // Get interest level synchronously to avoid dropping DEBUG logs during
109 // initialization (before an async interest response would be received).
110 ::fidl::WireResult<::fuchsia_logger::LogSink::WaitForInterestChange>
111 interest_result = log_sink_.sync()->WaitForInterestChange();
112 PW_CHECK(interest_result.ok());
113 HandleInterest(interest_result->value()->data);
114
115 socket_ = std::move(local);
116
117 WaitForInterestChanged();
118 }
119
HandleInterest(fuchsia_diagnostics::wire::Interest & interest)120 void HandleInterest(fuchsia_diagnostics::wire::Interest& interest) {
121 if (!interest.has_min_severity()) {
122 severity_ = FUCHSIA_LOG_INFO;
123 } else {
124 severity_ = FuchsiaLogSeverityFromFidl(interest.min_severity());
125 }
126 }
127
WaitForInterestChanged()128 void WaitForInterestChanged() {
129 log_sink_->WaitForInterestChange().Then(
130 [this](fidl::WireUnownedResult<
131 fuchsia_logger::LogSink::WaitForInterestChange>&
132 interest_result) {
133 if (!interest_result.ok()) {
134 auto error = interest_result.error();
135 PW_CHECK(error.is_dispatcher_shutdown(),
136 "%s",
137 error.FormatDescription().c_str());
138 return;
139 }
140 HandleInterest(interest_result.value()->data);
141 WaitForInterestChanged();
142 });
143 }
144
socket()145 zx::socket& socket() { return socket_; }
severity() const146 FuchsiaLogSeverity severity() const { return severity_; }
147
148 private:
149 fidl::WireClient<::fuchsia_logger::LogSink> log_sink_;
150 async_dispatcher_t* dispatcher_;
151 zx::socket socket_;
152 FuchsiaLogSeverity severity_ = FUCHSIA_LOG_INFO;
153 };
154
155 LogState log_state;
156
GetKoid(zx_handle_t handle)157 zx_koid_t GetKoid(zx_handle_t handle) {
158 zx_info_handle_basic_t info;
159 zx_status_t status = zx_object_get_info(
160 handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
161 return status == ZX_OK ? info.koid : ZX_KOID_INVALID;
162 }
163
164 thread_local const zx_koid_t thread_koid = GetKoid(zx_thread_self());
165 zx_koid_t const process_koid = GetKoid(zx_process_self());
166
167 } // namespace
168
169 namespace pw::log_fuchsia {
170
InitializeLogging(async_dispatcher_t * dispatcher)171 void InitializeLogging(async_dispatcher_t* dispatcher) {
172 log_state.Initialize(dispatcher);
173 }
174
175 } // namespace pw::log_fuchsia
176
pw_Log(int level,const char * module_name,unsigned int flags,const char * file_name,int line_number,const char * message,...)177 extern "C" void pw_Log(int level,
178 const char* module_name,
179 unsigned int flags,
180 const char* file_name,
181 int line_number,
182 const char* message,
183 ...) {
184 if (flags & PW_LOG_FLAG_IGNORE) {
185 return;
186 }
187
188 pw::StringBuffer<kBufferSize> formatted;
189
190 va_list args;
191 va_start(args, message);
192 formatted.FormatVaList(message, args);
193 va_end(args);
194
195 if (flags & PW_LOG_FLAG_USE_PRINTF) {
196 printf("%s: [%s:%s:%d] %s\n",
197 LogLevelToString(level),
198 module_name,
199 BaseName(file_name),
200 line_number,
201 formatted.c_str());
202 return;
203 }
204
205 FuchsiaLogSeverity fuchsia_severity = PigweedLevelToFuchsiaSeverity(level);
206 if (log_state.severity() > fuchsia_severity) {
207 return;
208 }
209
210 ::fuchsia_syslog::LogBuffer buffer;
211 buffer.BeginRecord(fuchsia_severity,
212 std::string_view(file_name),
213 line_number,
214 std::string_view(formatted.c_str()),
215 log_state.socket().borrow(),
216 /*dropped_count=*/0,
217 process_koid,
218 thread_koid);
219 buffer.WriteKeyValue("tag", module_name);
220 buffer.FlushRecord();
221 }
222