GCC Code Coverage Report


Directory: ./
File: log.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 348 369 94.3%
Functions: 34 34 100.0%
Branches: 282 430 65.6%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the LGPL v2.1 as described in the
10 * included LICENSE.LGPL file.
11 */
12
13 #include "fpp-pch.h"
14 #include <cstring>
15
16 #include <algorithm>
17 #include <atomic>
18 #include <fcntl.h>
19 #include <sys/stat.h>
20 #include <sys/syscall.h>
21 #include <sys/time.h>
22 #include <sys/utsname.h>
23 #include <pwd.h>
24 #include <sys/types.h>
25 #include <chrono>
26 #include <mutex>
27 #include <sstream>
28 #include <stdarg.h>
29 #include <stdbool.h>
30 #include <time.h>
31 #include <unistd.h>
32
33 #include "fppversion.h"
34 #include "log.h"
35
36 // int logLevel = LOG_INFO;
37 // int logMask = VB_MOST;
38
39 // Create Global Instance
40 FPPLogger FPPLogger::INSTANCE = FPPLogger();
41
42 char logFileName[1024] = "";
43 bool logToStdOut = true;
44
45 // Whether a line already written to the log file should be kept off stdout.
46 // Opt-in, and only fppd opts in: it is the one FPP binary whose stdout is a
47 // firehose into journald (it runs as `fppd -f` under systemd), so its stdout
48 // copy is pure duplication of what fppd.log already has. The small services --
49 // fppoled and friends -- log a handful of lines that users and support look for
50 // with `journalctl -u fppoled`, so they stay on both. See _LogWrite().
51 static bool suppressDuplicateStdOut = false;
52
53 // ---------------------------------------------------------------------------
54 // Crash-time log ring
55 //
56 // The most useful line for diagnosing a crash is routinely one nobody kept: the
57 // trigger is logged at LogDebug, production runs at info, so it is filtered out
58 // before it is ever formatted. This ring formats and retains those lines in
59 // memory without writing them anywhere, and the crash handler dumps it into the
60 // report -- debug-level detail at info-level cost on disk.
61 //
62 // Constraints that shaped it:
63 // * No locking. The normal log path takes logFileLock, and a crash handler
64 // that blocks on a mutex the faulting thread already holds hangs the
65 // process -- exactly the case (heap corruption inside free()) that most
66 // needs the trace. A slot may be torn between a writer and the dump; a
67 // smeared line in a crash report is a fine price for never deadlocking.
68 // * No allocation, for the same reason: a fixed array of fixed slots, so the
69 // dump is a bounded series of write() calls and is async-signal-safe.
70 // * LOG_EXCESSIVE is excluded. It fires per frame in the output path, and
71 // formatting it unconditionally would be a real cost on a single-core BBB;
72 // LOG_DEBUG and coarser covers the state changes worth reconstructing.
73 // ---------------------------------------------------------------------------
74 static constexpr size_t kCrashRingSlots = 256;
75 static constexpr size_t kCrashRingSlotSize = 240;
76 static char s_crashRing[kCrashRingSlots][kCrashRingSlotSize];
77 static std::atomic<uint64_t> s_crashRingSeq{ 0 };
78
79 // Set once the crash handler starts, so its own output -- the gdb stack, the
80 // "Crash handler called" line -- stops evicting the pre-crash history that is
81 // the entire reason the ring exists. That output is already captured in the
82 // stack file; duplicating it here would only cost us the lines we want.
83 static std::atomic<bool> s_crashRingFrozen{ false };
84
85 17 void CrashLogRingFreeze() {
86 17 s_crashRingFrozen.store(true, std::memory_order_relaxed);
87 17 }
88
89 // Zero-init statics, so this is safe from any point in startup with no
90 // dependency on static initialization order.
91 2357045 static void CrashLogRingPush(const char* line, size_t len) {
92
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 2356999 times.
2357045 if (s_crashRingFrozen.load(std::memory_order_relaxed)) {
93 46 return;
94 }
95 2356999 uint64_t seq = s_crashRingSeq.fetch_add(1, std::memory_order_relaxed);
96 2356999 char* slot = s_crashRing[seq % kCrashRingSlots];
97
2/2
✓ Branch 0 taken 21604 times.
✓ Branch 1 taken 2335395 times.
2356999 if (len >= kCrashRingSlotSize) {
98 21604 len = kCrashRingSlotSize - 1;
99 }
100 2356999 memcpy(slot, line, len);
101 2356999 slot[len] = '\0';
102 }
103
104 // Two independent filters, both needed:
105 // * facility opt-out excludes the per-frame data facilities outright (see
106 // FPPLoggerInstance::crashRingCapture -- HexDump emits at LogInfo, so a
107 // level test alone would not have caught them);
108 // * LOG_EXCESSIVE is dropped everywhere, since it is the per-frame level by
109 // convention in every facility that has one.
110 7931093 bool CrashLogRingWillCapture(int level, FPPLoggerInstance& facility) {
111
4/4
✓ Branch 0 taken 6882642 times.
✓ Branch 1 taken 1048451 times.
✓ Branch 2 taken 2357045 times.
✓ Branch 3 taken 4525597 times.
7931093 return facility.crashRingCapture && level <= LOG_DEBUG;
112 }
113
114 // Async-signal-safe: only write(), no allocation, no locks.
115 18 void CrashLogRingDump(int fd) {
116 18 uint64_t seq = s_crashRingSeq.load(std::memory_order_relaxed);
117
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 uint64_t count = seq < kCrashRingSlots ? seq : kCrashRingSlots;
118 static const char hdr[] = "=== recent log lines (ring buffer, oldest first) ===\n";
119
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 (void)!write(fd, hdr, sizeof(hdr) - 1);
120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 if (count == 0) {
121 static const char none[] = "(empty)\n";
122 ✗ (void)!write(fd, none, sizeof(none) - 1);
123 ✗ return;
124 }
125
2/2
✓ Branch 0 taken 2034 times.
✓ Branch 1 taken 18 times.
2052 for (uint64_t i = seq - count; i < seq; i++) {
126 2034 const char* slot = s_crashRing[i % kCrashRingSlots];
127 2034 size_t len = strnlen(slot, kCrashRingSlotSize);
128
1/2
✓ Branch 0 taken 2034 times.
✗ Branch 1 not taken.
2034 if (len) {
129
1/2
✓ Branch 0 taken 2034 times.
✗ Branch 1 not taken.
2034 (void)!write(fd, slot, len);
130 }
131 }
132 }
133
134 // True when stdout is an interactive terminal (a person ran `fppd -f` in a
135 // shell) rather than a pipe to journald (how systemd runs `fppd -f`). Used to
136 // decide whether the stdout copy of a line already written to the log file is
137 // a useful on-screen echo or a pure journald duplicate. Cached: fppd's stdout
138 // is not reassigned after startup, and isatty() is a syscall per log line.
139 374969 static bool stdoutIsInteractive() {
140
3/4
✓ Branch 0 taken 1882 times.
✓ Branch 1 taken 373087 times.
✓ Branch 2 taken 1882 times.
✗ Branch 3 not taken.
374969 static const bool interactive = isatty(fileno(stdout)) != 0;
141 374969 return interactive;
142 }
143
144 // Program name for the syslog-style program field. fppd.log is a merged,
145 // sequential record -- fppd itself, the fppd_start/stop/restart scripts, and
146 // operation breadcrumbs all append to it -- so every line has to say who wrote
147 // it. The [facility] field cannot carry this: it is a subsystem *within* fppd
148 // (General/ChannelOut/Playlist/...), not a program.
149 2388107 static const char* logProgramName() {
150 #ifdef PLATFORM_OSX
151 return getprogname();
152 #else
153 2388107 return program_invocation_short_name;
154 #endif
155 }
156 // char logLevelStr[16];
157 // char logMaskStr[1024];
158
159 // Persistent handle for the log file so each log line is a single write()
160 // instead of a full fopen/write/fclose cycle. On slow SD cards the per-line
161 // open/close metadata traffic was enough that a card-internal stall could
162 // block the (real-time) channel output thread mid-log and drop frames.
163 //
164 // logrotate rotates fppd.log by rename (no copytruncate and no signal to
165 // fppd), so we periodically stat() the path and reopen when the inode under
166 // our name changes or the file disappears.
167 //
168 // A recursive_timed_mutex guards the handle: recursive because the crash
169 // handler calls LogErr and may re-enter on the same thread; timed so that if
170 // another thread is wedged inside a write (e.g. stalled storage) a crashing
171 // thread gives up and falls back to stderr instead of hanging the process.
172 // Owner the log file should end up with, resolved once. Looked up rather than
173 // hardcoded to a uid, and returns false where there is no such user (macOS runs
174 // as the developer's own account, not fpp) so the chown is simply skipped there.
175 1871 static bool logFileOwner(uid_t& uid, gid_t& gid) {
176 static bool resolved = false;
177 static bool haveOwner = false;
178 static uid_t cachedUid = 0;
179 static gid_t cachedGid = 0;
180
2/2
✓ Branch 0 taken 1870 times.
✓ Branch 1 taken 1 times.
1871 if (!resolved) {
181 1870 resolved = true;
182 1870 struct passwd* pw = getpwnam("fpp");
183
2/2
✓ Branch 0 taken 160 times.
✓ Branch 1 taken 1710 times.
1870 if (pw) {
184 160 cachedUid = pw->pw_uid;
185 160 cachedGid = pw->pw_gid;
186 160 haveOwner = true;
187 }
188 }
189 1871 uid = cachedUid;
190 1871 gid = cachedGid;
191 1871 return haveOwner;
192 }
193
194 static std::recursive_timed_mutex logFileLock;
195 static FILE* persistentLogFile = nullptr;
196 static ino_t persistentLogIno = 0;
197 static dev_t persistentLogDev = 0;
198 static time_t lastRotationCheck = 0;
199
200 // Must be called with logFileLock held.
201 1958 static void closePersistentLogFile() {
202
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1957 times.
1958 if (persistentLogFile) {
203 1 fclose(persistentLogFile);
204 1 persistentLogFile = nullptr;
205 1 persistentLogIno = 0;
206 1 persistentLogDev = 0;
207 1 lastRotationCheck = 0;
208 }
209 1958 }
210
211 // Must be called with logFileLock held. Returns the persistent handle for
212 // logFileName, (re)opening it as needed, or nullptr if it cannot be opened.
213 375281 static FILE* getPersistentLogFile(time_t now) {
214
4/4
✓ Branch 0 taken 373287 times.
✓ Branch 1 taken 1994 times.
✓ Branch 2 taken 4759 times.
✓ Branch 3 taken 368528 times.
375281 if (persistentLogFile && now != lastRotationCheck) {
215 // At most once a second, check whether the file we have open is still
216 // the one at logFileName (logrotate renames it, a user may delete it).
217 4759 lastRotationCheck = now;
218 struct stat st;
219 4759 if ((stat(logFileName, &st) != 0) ||
220
6/8
✓ Branch 0 taken 4758 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4758 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4758 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 4758 times.
4759 (st.st_ino != persistentLogIno) || (st.st_dev != persistentLogDev)) {
221
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 closePersistentLogFile();
222 }
223 }
224
2/2
✓ Branch 0 taken 1995 times.
✓ Branch 1 taken 373286 times.
375281 if (!persistentLogFile) {
225 1995 bool existed = (access(logFileName, F_OK) == 0);
226 1995 persistentLogFile = fopen(logFileName, "a");
227
2/2
✓ Branch 0 taken 1890 times.
✓ Branch 1 taken 105 times.
1995 if (persistentLogFile) {
228 // Line buffered: each log line still hits the kernel immediately
229 // (matching the old open/write/close behavior) but as one write().
230 1890 setvbuf(persistentLogFile, nullptr, _IOLBF, 8192);
231
2/2
✓ Branch 0 taken 1871 times.
✓ Branch 1 taken 19 times.
1890 if (!existed) {
232 // We just created the log, and fppd runs as root (fppd.service
233 // sets no User=), so it would be root:root 0644 in an fpp-owned
234 // directory -- locking out every writer that is not root. That
235 // matters because fppd is no longer the only one: the
236 // fppd_start/stop/restart scripts append here, and so do the PHP
237 // breadcrumbs (backup, mp3gain) which run as Apache/fpp. Their
238 // writes are best-effort and error-suppressed, so they would fail
239 // SILENTLY.
240 //
241 // logrotate's `create 0664 fpp fpp` normally gets here first, but
242 // it cannot cover every case: resetConfig.php deletes logs/*, and
243 // a fresh install writes here before FPPINIT's setFileOwnership
244 // runs. Whoever creates the file has to set the ownership, so do
245 // it here too. Best-effort: as non-root the chown simply fails,
246 // which is the case where we are already the right owner.
247 uid_t uid;
248 gid_t gid;
249
6/8
✓ Branch 0 taken 1871 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
✓ Branch 3 taken 1711 times.
✓ Branch 4 taken 160 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 160 times.
✓ Branch 7 taken 1711 times.
1871 if (logFileOwner(uid, gid) && fchown(fileno(persistentLogFile), uid, gid) == 0) {
250 160 fchmod(fileno(persistentLogFile), 0664);
251 }
252 }
253 struct stat st;
254
1/2
✓ Branch 0 taken 1890 times.
✗ Branch 1 not taken.
1890 if (fstat(fileno(persistentLogFile), &st) == 0) {
255 1890 persistentLogIno = st.st_ino;
256 1890 persistentLogDev = st.st_dev;
257 }
258 1890 lastRotationCheck = now;
259 }
260 }
261 375281 return persistentLogFile;
262 }
263
264 4298 void FPPLogger::Init() {
265
2/2
✓ Branch 0 taken 1889 times.
✓ Branch 1 taken 2409 times.
4298 if (all.empty()) {
266
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::General));
267
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::ChannelOut));
268
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::ChannelData));
269
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::Command));
270
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::E131Bridge));
271
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::Effect));
272
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::MediaOut));
273
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::Playlist));
274
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::Schedule));
275
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::Sequence));
276
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::Settings));
277
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::Control));
278
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::Sync));
279
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::Plugin));
280
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::GPIO));
281
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 all.push_back(&(FPPLogger::HTTP));
282 }
283 4298 }
284
285 471 bool FPPLogger::SetLevel(const char* name, const char* level) {
286 471 LogLevel newLevel = LOG_ERR;
287 471 bool found = false;
288
3/4
✓ Branch 0 taken 469 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 469 times.
471 if (level == nullptr || name == nullptr) {
289 2 LogErr(VB_SETTING, "Log level (%s) and name (%s) must be specified\n", level, name);
290 2 return false;
291 }
292
293
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 469 times.
469 if (strcmp(level, "error") == 0) {
294 ✗ newLevel = LOG_ERR;
295 ✗ found = true;
296
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 468 times.
469 } else if (strcmp(level, "warn") == 0) {
297 1 newLevel = LOG_WARN;
298 1 found = true;
299
2/2
✓ Branch 0 taken 230 times.
✓ Branch 1 taken 238 times.
468 } else if (strcmp(level, "info") == 0) {
300 230 newLevel = LOG_INFO;
301 230 found = true;
302
2/2
✓ Branch 0 taken 182 times.
✓ Branch 1 taken 56 times.
238 } else if (strcmp(level, "debug") == 0) {
303 182 newLevel = LOG_DEBUG;
304 182 found = true;
305
2/2
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 3 times.
56 } else if (strcmp(level, "excess") == 0) {
306 53 newLevel = LOG_EXCESSIVE;
307 53 found = true;
308 }
309
310
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 466 times.
469 if (!found) {
311 3 LogErr(VB_SETTING, "Invalid name for LogLevel: %s\n", level);
312 3 return false;
313 } else {
314
2/4
✓ Branch 0 taken 466 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 466 times.
✗ Branch 3 not taken.
932 return SetLevel(std::string(name), newLevel);
315 }
316 }
317
318 466 bool FPPLogger::SetLevel(std::string name, LogLevel level) {
319 466 bool madeChange = false;
320
1/2
✓ Branch 0 taken 466 times.
✗ Branch 1 not taken.
466 std::string prefix("LogLevel_");
321
3/4
✓ Branch 0 taken 466 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 306 times.
✓ Branch 3 taken 160 times.
466 if (name.compare(0, prefix.size(), prefix) == 0) {
322
1/2
✓ Branch 0 taken 306 times.
✗ Branch 1 not taken.
306 name = name.substr(prefix.size());
323 }
324
325 466 std::vector<FPPLoggerInstance*>::iterator it;
326
1/2
✓ Branch 0 taken 466 times.
✗ Branch 1 not taken.
466 Init(); /* Just incase */
327
328 // "All" sets every logger (fpp --log-level LEVEL).
329
2/2
✓ Branch 0 taken 7456 times.
✓ Branch 1 taken 466 times.
7922 for (it = all.begin(); it != all.end(); it++) {
330 7456 FPPLoggerInstance* ptr = *it;
331
7/8
✓ Branch 0 taken 6992 times.
✓ Branch 1 taken 464 times.
✓ Branch 2 taken 6992 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 6976 times.
✓ Branch 6 taken 480 times.
✓ Branch 7 taken 6976 times.
7456 if (name == ptr->name || name == "All") {
332
1/2
✓ Branch 0 taken 480 times.
✗ Branch 1 not taken.
480 SetFacilityLevel(*ptr, level);
333 480 madeChange = true;
334 }
335 }
336
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 465 times.
466 if (!madeChange) {
337
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogWarn(VB_SETTING, "Invalid name for LoggerInstance: %s\n", name.c_str());
338 }
339
340 466 return madeChange;
341 466 }
342
343 1926 int FPPLogger::MinimumLogLevel() {
344 1926 int rc = LOG_ERR;
345
1/2
✓ Branch 0 taken 1926 times.
✗ Branch 1 not taken.
1926 Init(); /* Just incase */
346 1926 std::vector<FPPLoggerInstance*>::iterator it;
347
348
2/2
✓ Branch 0 taken 30816 times.
✓ Branch 1 taken 1926 times.
32742 for (it = all.begin(); it != all.end(); it++) {
349
1/2
✓ Branch 0 taken 30816 times.
✗ Branch 1 not taken.
30816 int level = FacilityLevel(**it);
350
2/2
✓ Branch 0 taken 1980 times.
✓ Branch 1 taken 28836 times.
30816 if (level > rc) {
351 1980 rc = level;
352 }
353 }
354
355 1926 return rc;
356 }
357
358 1876 std::string FPPLogger::GetLogLevelString() {
359
1/2
✓ Branch 0 taken 1876 times.
✗ Branch 1 not taken.
1876 std::stringstream rc;
360 1876 LogLevel currentLevel = LOG_ERR;
361 1876 bool firstLevel = true;
362 1876 bool firstLogger = true;
363 1876 std::vector<FPPLoggerInstance*>::iterator it;
364
365
2/2
✓ Branch 0 taken 30016 times.
✓ Branch 1 taken 1876 times.
31892 for (it = all.begin(); it != all.end(); it++) {
366
1/2
✓ Branch 0 taken 30016 times.
✗ Branch 1 not taken.
30016 LogLevel level = FacilityLevel(**it);
367
3/4
✓ Branch 0 taken 28043 times.
✓ Branch 1 taken 1973 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 28043 times.
30016 if ((currentLevel != level) || firstLevel) {
368
2/2
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 1876 times.
1973 if (!firstLevel) {
369
1/2
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
97 rc << ";";
370 }
371 1973 firstLevel = false;
372 1973 firstLogger = true;
373
3/6
✓ Branch 0 taken 1973 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1973 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1973 times.
✗ Branch 5 not taken.
1973 rc << LogLevelToString(level) << ":";
374 1973 currentLevel = level;
375 }
376
2/2
✓ Branch 0 taken 1973 times.
✓ Branch 1 taken 28043 times.
30016 if (firstLogger) {
377 1973 firstLogger = false;
378 } else {
379
1/2
✓ Branch 0 taken 28043 times.
✗ Branch 1 not taken.
28043 rc << ",";
380 }
381
1/2
✓ Branch 0 taken 30016 times.
✗ Branch 1 not taken.
30016 rc << (*it)->name;
382 }
383
1/2
✓ Branch 0 taken 1876 times.
✗ Branch 1 not taken.
3752 return rc.str();
384 1876 }
385
386 18 void FPPLogger::SetAllLevel(LogLevel level) {
387 18 std::vector<FPPLoggerInstance*>::iterator it;
388
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 Init(); /* Just incase */
389
390
2/2
✓ Branch 0 taken 288 times.
✓ Branch 1 taken 18 times.
306 for (it = all.begin(); it != all.end(); it++) {
391
1/2
✓ Branch 0 taken 288 times.
✗ Branch 1 not taken.
288 SetFacilityLevel(**it, level);
392 }
393 18 }
394
395 1263322 std::string TruncateForLog(const std::string& s, size_t maxLen) {
396 // Escape embedded newlines first (on the ORIGINAL string, not just the
397 // truncated slice) - formatLogLines() below re-prefixes every physical
398 // line it's given (deliberately, for attribution in this merged log
399 // file), so an unescaped \n in otherwise-unbounded data (a Variable's
400 // value, raw command output, etc.) turns one logical log entry into
401 // several prefixed-but-uncorrelated-looking lines. A short multi-line
402 // value is just as confusing as a long one, so this isn't gated behind
403 // the length check below.
404 // Only the part that survives truncation is escaped, so logging a large
405 // body costs maxLen rather than a copy of the whole thing.
406 1263322 std::string escaped;
407
1/2
✓ Branch 0 taken 1263322 times.
✗ Branch 1 not taken.
1263322 escaped.reserve(std::min(s.size(), maxLen + 1));
408
2/2
✓ Branch 0 taken 46454011 times.
✓ Branch 1 taken 1251838 times.
47705849 for (char c : s) {
409
2/2
✓ Branch 0 taken 11484 times.
✓ Branch 1 taken 46442527 times.
46454011 if (escaped.size() > maxLen) {
410 11484 break;
411 }
412
2/2
✓ Branch 0 taken 80693 times.
✓ Branch 1 taken 46361834 times.
46442527 if (c == '\n') {
413
1/2
✓ Branch 0 taken 80693 times.
✗ Branch 1 not taken.
80693 escaped += "\\n";
414
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46361834 times.
46361834 } else if (c == '\r') {
415 ✗ escaped += "\\r";
416 } else {
417
1/2
✓ Branch 0 taken 46361834 times.
✗ Branch 1 not taken.
46361834 escaped += c;
418 }
419 }
420
2/2
✓ Branch 0 taken 1251838 times.
✓ Branch 1 taken 11484 times.
1263322 if (escaped.size() <= maxLen) {
421 1251838 return escaped;
422 }
423
5/10
✓ Branch 0 taken 11484 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11484 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11484 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11484 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 11354 times.
✗ Branch 9 not taken.
11484 return escaped.substr(0, maxLen) + "...(" + std::to_string(s.size()) + " bytes total)";
424 1263322 }
425
426 8515254 bool WillLog(int level, FPPLoggerInstance& facility) {
427 // Don't log if we're not concerned about anything at this level
428
2/2
✓ Branch 0 taken 8128301 times.
✓ Branch 1 taken 386953 times.
8515254 if (FacilityLevel(facility) < level)
429 8128301 return false;
430
431 386953 return true;
432 }
433
434 // atomic_ref rather than an atomic member: FPPLoggerInstance's layout is
435 // compiled into plugins (see log.h).
436 8576646 LogLevel FacilityLevel(FPPLoggerInstance& facility) {
437 8576646 return std::atomic_ref<LogLevel>(facility.level).load(std::memory_order_relaxed);
438 }
439 768 void SetFacilityLevel(FPPLoggerInstance& facility, LogLevel level) {
440 768 std::atomic_ref<LogLevel>(facility.level).store(level, std::memory_order_relaxed);
441 768 }
442
443 85 void _LogWrite(const char* file, int line, int level, FPPLoggerInstance& facility, const std::string& str, ...) {
444 // Same gate as the char* overload -- a ring-only line still has to reach it.
445
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 85 times.
85 if (!WillLog(level, facility) && !CrashLogRingWillCapture(level, facility))
446 ✗ return;
447 // The string is the message, not a format: callers build it from
448 // sequence names, config values and exception text, and a '%' there
449 // used to be read as a conversion with no argument behind it.
450 85 _LogWrite(file, line, level, facility, "%s", str.c_str());
451 }
452
453 // Append msg to out, applying prefix to EVERY physical line.
454 //
455 // The prefix used to be snprintf'd together with the caller's format string and
456 // handed to vfprintf, which applied it once per LogX() call rather than per
457 // line: any message containing \n produced several physical lines with only the
458 // first one identifying itself. That was survivable while fppd owned this file
459 // outright, but fppd.log is now a merged, sequential record -- fppd, the
460 // fppd_start/stop/restart scripts and the operation breadcrumbs all append to it
461 // -- and an unlabelled line there is an unattributable line.
462 //
463 // A message that does not end in \n gets one; previously it left the line open
464 // and the next log call concatenated onto it.
465 2388107 static void formatLogLines(std::string& out, const char* prefix, const char* msg) {
466
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2388107 times.
2388107 if (!*msg) {
467 ✗ out.append(prefix);
468 ✗ out.append("\n");
469 ✗ return;
470 }
471 2388107 const char* p = msg;
472
2/2
✓ Branch 0 taken 2399897 times.
✓ Branch 1 taken 2382543 times.
4782440 while (*p) {
473 2399897 const char* nl = strchr(p, '\n');
474
2/2
✓ Branch 0 taken 2394333 times.
✓ Branch 1 taken 5564 times.
2399897 size_t len = nl ? (size_t)(nl - p) : strlen(p);
475 2399897 out.append(prefix);
476 2399897 out.append(p, len);
477 2399897 out.append("\n");
478
2/2
✓ Branch 0 taken 5564 times.
✓ Branch 1 taken 2394333 times.
2399897 if (!nl) {
479 5564 break;
480 }
481 2394333 p = nl + 1;
482 }
483 }
484
485 // __FILE__ is whatever path the compiler was handed. A local make passes
486 // paths relative to src/ ("non-gpl/BBShiftString/BBShiftString.cpp"), but a
487 // distributed compile (nocc) passes an absolute path rooted in the remote
488 // cache, e.g. "/var/cache/nocc/cpp/clients/<id>/opt/fpp/src/...", which buries
489 // the part that matters. Trim everything up to and including "/fpp/src/" so
490 // both builds log the same name. Paths without it (plugins) are untouched.
491 2388107 static const char* logSourceFile(const char* file) {
492 static constexpr char marker[] = "/fpp/src/";
493 2388107 const char* s = strstr(file, marker);
494
2/2
✓ Branch 0 taken 21328 times.
✓ Branch 1 taken 2366779 times.
2388107 return s ? s + sizeof(marker) - 1 : file;
495 }
496
497 7931093 void _LogWrite(const char* file, int line, int level, FPPLoggerInstance& facility, const char* format, ...) {
498 // A line is formatted if it is either being logged normally OR retained in
499 // the crash ring -- the ring is the whole point of formatting a line the
500 // configured level would discard.
501
1/2
✓ Branch 0 taken 7931093 times.
✗ Branch 1 not taken.
7931093 const bool willLog = WillLog(level, facility);
502
1/2
✓ Branch 0 taken 7931093 times.
✗ Branch 1 not taken.
7931093 const bool willRing = CrashLogRingWillCapture(level, facility);
503
4/4
✓ Branch 0 taken 7547746 times.
✓ Branch 1 taken 383347 times.
✓ Branch 2 taken 5542986 times.
✓ Branch 3 taken 2004760 times.
7931093 if (!willLog && !willRing)
504 7547851 return;
505
506 va_list arg;
507
508 struct timeval tv;
509 2388107 gettimeofday(&tv, nullptr);
510 2388107 int ms = tv.tv_usec / 1000;
511
512 uint64_t tid;
513 #ifdef PLATFORM_OSX
514 pthread_threadid_np(NULL, &tid);
515 #else
516 2388107 tid = gettid();
517 #endif
518
519 // The "YYYY-MM-DD HH:MM:SS" field changes once per second, but building it
520 // cost a localtime_r plus seven width-qualified integer conversions on
521 // EVERY line -- together the largest single component of a log call. That
522 // is invisible on a desktop and not on the small ARM boards: measured on an
523 // AM335x, localtime_r ~5us and the full prefix snprintf ~12us, against
524 // ~1.7us to render the caller's actual message.
525 //
526 // So cache it, keyed on the second it describes. thread_local rather than
527 // shared: this is on every log call from every thread, and a lock or atomic
528 // here would cost more than it saves, while a torn shared buffer would
529 // corrupt timestamps. Per-thread cost is one time_t and 20 bytes.
530 //
531 // A timezone change (DST) is picked up on the next second boundary, since
532 // the key is the second itself.
533 thread_local time_t cachedSec = (time_t)-1;
534 thread_local char cachedDateTime[20]; // "YYYY-MM-DD HH:MM:SS"
535
2/2
✓ Branch 0 taken 32554 times.
✓ Branch 1 taken 2355553 times.
2388107 if (tv.tv_sec != cachedSec) {
536 struct tm tm;
537 32554 localtime_r(&tv.tv_sec, &tm);
538 32554 snprintf(cachedDateTime, sizeof(cachedDateTime),
539 "%4d-%.2d-%.2d %.2d:%.2d:%.2d",
540 32554 1900 + tm.tm_year, tm.tm_mon + 1, tm.tm_mday,
541 tm.tm_hour, tm.tm_min, tm.tm_sec);
542 32554 cachedSec = tv.tv_sec;
543 }
544
545 // The remaining numeric fields are emitted directly rather than through
546 // snprintf; integer conversion is the expensive part of a format string,
547 // and milliseconds are always exactly three digits here.
548 char prefix[512];
549 2388107 char* p = prefix;
550 2388107 memcpy(p, cachedDateTime, 19);
551 2388107 p += 19;
552 2388107 *p++ = '.';
553 2388107 *p++ = (char)('0' + (ms / 100) % 10);
554 2388107 *p++ = (char)('0' + (ms / 10) % 10);
555 2388107 *p++ = (char)('0' + ms % 10);
556 2388107 *p++ = ' ';
557 2388107 snprintf(p, sizeof(prefix) - (p - prefix), "%s(%llu) [%s] %s:%d: ",
558 logProgramName(), tid, facility.name.c_str(), logSourceFile(file), line);
559
560 // Render the caller's message first so it can be split on newlines. The
561 // stack buffer covers essentially every real log line; the heap path exists
562 // so a long one (a dumped config blob) is not silently truncated.
563 char stackBuf[1024];
564 2388107 va_start(arg, format);
565 2388107 int msgLen = vsnprintf(stackBuf, sizeof(stackBuf), format, arg);
566 2388107 va_end(arg);
567
568 2388107 const char* msg = stackBuf;
569 2388107 std::string heapBuf;
570
2/2
✓ Branch 0 taken 405 times.
✓ Branch 1 taken 2387702 times.
2388107 if (msgLen >= (int)sizeof(stackBuf)) {
571
1/2
✓ Branch 0 taken 405 times.
✗ Branch 1 not taken.
405 heapBuf.resize(msgLen + 1);
572 405 va_start(arg, format);
573 405 vsnprintf(&heapBuf[0], msgLen + 1, format, arg);
574 405 va_end(arg);
575 405 msg = heapBuf.c_str();
576 }
577
578 2388107 std::string out;
579
1/2
✓ Branch 0 taken 2388107 times.
✗ Branch 1 not taken.
2388107 out.reserve((msgLen > 0 ? (size_t)msgLen : 0) + 128);
580
1/2
✓ Branch 0 taken 2388107 times.
✗ Branch 1 not taken.
2388107 formatLogLines(out, prefix, msg);
581
582
2/2
✓ Branch 0 taken 2357045 times.
✓ Branch 1 taken 31062 times.
2388107 if (willRing) {
583 2357045 CrashLogRingPush(out.data(), out.size());
584 }
585
2/2
✓ Branch 0 taken 2004760 times.
✓ Branch 1 taken 383347 times.
2388107 if (!willLog) {
586 // Retained for the crash report only; the configured level says this
587 // line does not belong in the log file or on stdout.
588 2004760 return;
589 }
590
591 // One fwrite per log call: it keeps the whole (possibly multi-line) message
592 // contiguous, which matters now that shell writers append to this same file.
593 383347 bool wroteLogFile = false;
594
2/2
✓ Branch 0 taken 381414 times.
✓ Branch 1 taken 1933 times.
383347 if (logFileName[0]) {
595
2/2
✓ Branch 0 taken 6133 times.
✓ Branch 1 taken 375281 times.
381414 if (!strcmp(logFileName, "stderr")) {
596
1/2
✓ Branch 0 taken 6133 times.
✗ Branch 1 not taken.
6133 fwrite(out.data(), 1, out.size(), stderr);
597
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 375281 times.
375281 } else if (!strcmp(logFileName, "stdout")) {
598 ✗ fwrite(out.data(), 1, out.size(), stdout);
599 } else {
600 375281 std::unique_lock<std::recursive_timed_mutex> lock(logFileLock, std::defer_lock);
601
2/4
✓ Branch 0 taken 375281 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 375281 times.
✗ Branch 3 not taken.
375281 if (lock.try_lock_for(std::chrono::seconds(2))) {
602
1/2
✓ Branch 0 taken 375281 times.
✗ Branch 1 not taken.
375281 FILE* logFile = getPersistentLogFile(tv.tv_sec);
603
2/2
✓ Branch 0 taken 375176 times.
✓ Branch 1 taken 105 times.
375281 if (logFile) {
604
1/2
✓ Branch 0 taken 375176 times.
✗ Branch 1 not taken.
375176 fwrite(out.data(), 1, out.size(), logFile);
605 375176 wroteLogFile = true;
606 } else {
607
1/2
✓ Branch 0 taken 105 times.
✗ Branch 1 not taken.
105 fprintf(stderr, "Error: Unable to open log file for writing!\n");
608
1/2
✓ Branch 0 taken 105 times.
✗ Branch 1 not taken.
105 fwrite(out.data(), 1, out.size(), stderr);
609 105 return;
610 }
611 } else {
612 // Could not get the log file lock (holder likely wedged on
613 // stalled storage); don't hang here - emit to stderr instead.
614 ✗ fwrite(out.data(), 1, out.size(), stderr);
615 ✗ return;
616 }
617
2/2
✓ Branch 0 taken 375176 times.
✓ Branch 1 taken 105 times.
375281 }
618 }
619 // For fppd, once the line is in a real log file a second copy on stdout is a
620 // pure duplicate: it runs as `fppd -f` under systemd, so logToStdOut stays
621 // set and journald captured every line a second time -- doubling the volume
622 // for a copy that does not outlive the boot, while fppd.log is what the
623 // Support Zip ships and what users are asked for. Suppress it at the source,
624 // here, for the callers that ask (SetSuppressDuplicateStdOut).
625 //
626 // Do NOT instead mute the stream in fppd.service: fppd's stdout is inherited
627 // by every child it forks (cape detection, plugin callbacks), so muting it
628 // there discards their output too. That was tried, and reverted.
629 //
630 // Every other binary that links the logger keeps writing both places. They
631 // are low volume, and `journalctl -u fppoled` / `systemctl status` is where
632 // a service's output is looked for -- that is what suppressing this
633 // globally, rather than per-caller, silently took away.
634 //
635 // `fppd -l stdout` still logs to stdout, and nothing suppresses stdout when
636 // no real log file is in use (early startup, before SetLogFile).
637 //
638 // The duplicate-suppression only applies when stdout is a pipe to journald.
639 // When a person runs `fppd -f` in a terminal, stdout is a TTY and the copy
640 // is a genuine on-screen echo of the log file, not a journald duplicate, so
641 // keep echoing every line -- that is the interactive foreground behavior.
642
5/6
✓ Branch 0 taken 383242 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 376910 times.
✓ Branch 3 taken 6332 times.
✓ Branch 4 taken 1971 times.
✓ Branch 5 taken 381271 times.
760152 if (strcmp(logFileName, "stdout") && logToStdOut &&
643
6/6
✓ Branch 0 taken 374977 times.
✓ Branch 1 taken 1933 times.
✓ Branch 2 taken 374969 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 30 times.
✓ Branch 5 taken 374939 times.
376910 (!wroteLogFile || !suppressDuplicateStdOut || stdoutIsInteractive())) {
644
1/2
✓ Branch 0 taken 1971 times.
✗ Branch 1 not taken.
1971 fwrite(out.data(), 1, out.size(), stdout);
645 }
646
4/4
✓ Branch 0 taken 383242 times.
✓ Branch 1 taken 2004865 times.
✓ Branch 2 taken 383242 times.
✓ Branch 3 taken 2004865 times.
4392972 }
647
648 1888 void SetSuppressDuplicateStdOut(bool suppress) {
649 1888 suppressDuplicateStdOut = suppress;
650 1888 }
651
652 1957 void SetLogFile(const char* filename, bool toStdOut) {
653
1/2
✓ Branch 0 taken 1957 times.
✗ Branch 1 not taken.
1957 std::unique_lock<std::recursive_timed_mutex> lock(logFileLock);
654
1/2
✓ Branch 0 taken 1957 times.
✗ Branch 1 not taken.
1957 closePersistentLogFile();
655 1957 snprintf(logFileName, sizeof(logFileName), "%s", filename);
656 1957 logToStdOut = toStdOut;
657 1957 }
658
659 2533 std::string LogLevelToString(LogLevel level) {
660
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2530 times.
2533 if (level == LOG_WARN) {
661
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
6 return "warn";
662
2/2
✓ Branch 0 taken 263 times.
✓ Branch 1 taken 2267 times.
2530 } else if (level == LOG_DEBUG) {
663
1/2
✓ Branch 0 taken 263 times.
✗ Branch 1 not taken.
526 return "debug";
664
2/2
✓ Branch 0 taken 2202 times.
✓ Branch 1 taken 65 times.
2267 } else if (level == LOG_INFO) {
665
1/2
✓ Branch 0 taken 2202 times.
✗ Branch 1 not taken.
4404 return "info";
666
1/2
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
65 } else if (level == LOG_EXCESSIVE) {
667
1/2
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
130 return "excess";
668 ✗ } else if (level == LOG_ERR) {
669 ✗ return "error";
670 } else {
671 ✗ return "unknown";
672 }
673 }
674
675 /*
676 * Parse a string like debug:schedule,player;excess:mqtt for log level
677 */
678 217 bool SetLogLevelComplex(std::string& input) {
679
1/2
✓ Branch 0 taken 217 times.
✗ Branch 1 not taken.
217 LogDebug(VB_SETTING, "Attempting to parse Log Levels from %s\n", input.c_str());
680
681
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 217 times.
217 if (input.length() == 0) {
682 ✗ LogWarn(VB_SETTING, "Ignoring Empty String sent to SetLogLevelComplex");
683 ✗ return false;
684 }
685 // Check if simple method
686
2/2
✓ Branch 0 taken 205 times.
✓ Branch 1 taken 12 times.
217 if (input.find(':') == std::string::npos) {
687
1/2
✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
205 LogDebug(VB_SETTING, "Using simple loging for input \"%s\" as no colon found.\n", input.c_str());
688
1/2
✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
205 return SetLogLevel(input.c_str());
689 }
690
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (input.find(';') == std::string::npos) {
691
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 input = input + ';';
692 }
693
694 12 bool madeChange = false;
695
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 std::istringstream ss(input);
696 12 std::string token1;
697
698
4/6
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 12 times.
24 while (std::getline(ss, token1, ';')) {
699 // token1 should be debug:scheduler,player
700
701
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 LogDebug(VB_SETTING, "Outer Prase is %s\n", token1.c_str());
702 12 size_t pos = token1.find(':');
703
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (pos != std::string::npos) {
704
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 std::string level = token1.substr(0, pos);
705
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 std::string loggers = token1.substr(pos + 1);
706
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 std::istringstream st(loggers);
707 12 std::string logger;
708
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
12 if (loggers.find(',') == std::string::npos) {
709
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 loggers = loggers + ",";
710 }
711
4/6
✓ Branch 0 taken 159 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 159 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 147 times.
✓ Branch 5 taken 12 times.
159 while (std::getline(st, logger, ',')) {
712
1/2
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
147 LogDebug(VB_SETTING, "Attempting to set %s to %s \n", logger.c_str(), level.c_str());
713
4/6
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 146 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
147 madeChange = FPPLogger::INSTANCE.SetLevel(logger.c_str(), level.c_str()) || madeChange;
714 }
715 12 } else {
716 ✗ LogErr(VB_SETTING, "Invalid Log Level Token: %s\n", token1.c_str());
717 }
718 }
719
720 12 return madeChange;
721 12 }
722 9 bool SetLogLevelComplex(const char* input) {
723
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 std::string s = input;
724
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
18 return SetLogLevelComplex(s);
725 9 }
726
727 206 bool SetLogLevel(const char* newLevel) {
728 206 LogErr(VB_SETTING, "Using Legacy Global Logging to set all to same Log Level of %s\n", newLevel);
729
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 206 times.
206 if (!strcmp(newLevel, "warn")) {
730 ✗ FPPLogger::INSTANCE.SetAllLevel(LOG_WARN);
731
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 197 times.
206 } else if (!strcmp(newLevel, "debug")) {
732 9 FPPLogger::INSTANCE.SetAllLevel(LOG_DEBUG);
733
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 191 times.
197 } else if (!strcmp(newLevel, "info")) {
734 6 FPPLogger::INSTANCE.SetAllLevel(LOG_INFO);
735
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 188 times.
191 } else if (!strcmp(newLevel, "excess")) {
736 3 FPPLogger::INSTANCE.SetAllLevel(LOG_EXCESSIVE);
737
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 188 times.
188 } else if (!strcmp(newLevel, "error")) {
738 ✗ FPPLogger::INSTANCE.SetAllLevel(LOG_ERR);
739 } else {
740 188 LogErr(VB_SETTING, "Unknown Log Level: %s\n", newLevel);
741 188 return false;
742 }
743
744 18 return true;
745 }
746
747 1888 int loggingToFile(void) {
748
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 if ((logFileName[0]) &&
749
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 (strcmp(logFileName, "stderr")) &&
750
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 (strcmp(logFileName, "stdout")))
751 1888 return 1;
752
753 ✗ return 0;
754 }
755
756 1888 void logVersionInfo(void) {
757
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 LogErr(VB_ALL, "=========================================\n");
758
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
1888 LogErr(VB_ALL, "FPP %s\n", getFPPVersion());
759
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
1888 LogErr(VB_ALL, "Branch: %s\n", getFPPBranch());
760
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 int o = open("/proc/device-tree/model", O_RDONLY);
761
2/2
✓ Branch 0 taken 160 times.
✓ Branch 1 taken 1728 times.
1888 if (o >= 0) {
762 // A short read continues at buf + c; reading back into buf replaced
763 // the start of the model with its tail.
764 char buf[256];
765 160 int c = 0;
766
1/2
✓ Branch 0 taken 320 times.
✗ Branch 1 not taken.
320 while (c < 255) {
767
1/2
✓ Branch 0 taken 320 times.
✗ Branch 1 not taken.
320 ssize_t i = read(o, buf + c, 255 - c);
768
2/2
✓ Branch 0 taken 160 times.
✓ Branch 1 taken 160 times.
320 if (i <= 0) {
769 160 break;
770 }
771 160 c += i;
772 }
773
1/2
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
160 if (c > 0) {
774 160 buf[c] = 0;
775
1/2
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
160 LogErr(VB_ALL, "Model: %s\n", buf);
776 }
777
1/2
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
160 close(o);
778 }
779 // Kernel matters for triage far more often than it looks: the Pi's V4L2
780 // decoder wedge (issues #2695/#2727) and several DRM/KMS bugs are fixed or
781 // broken per kernel, and asking a user for `uname -a` days after the fact
782 // is unreliable -- the box may have been updated since. Log it with the
783 // rest of the version banner so every fppd.log answers it on its own.
784 struct utsname un;
785
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 if (uname(&un) == 0) {
786
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 LogErr(VB_ALL, "Kernel: %s %s (%s)\n", un.sysname, un.release, un.machine);
787 }
788
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 LogErr(VB_ALL, "=========================================\n");
789 1888 }
790