/src/libwebsockets/lib/core/logs.c
Line | Count | Source |
1 | | /* |
2 | | * libwebsockets - small server side websockets and web server implementation |
3 | | * |
4 | | * Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com> |
5 | | * |
6 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
7 | | * of this software and associated documentation files (the "Software"), to |
8 | | * deal in the Software without restriction, including without limitation the |
9 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
10 | | * sell copies of the Software, and to permit persons to whom the Software is |
11 | | * furnished to do so, subject to the following conditions: |
12 | | * |
13 | | * The above copyright notice and this permission notice shall be included in |
14 | | * all copies or substantial portions of the Software. |
15 | | * |
16 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
19 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
20 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
21 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
22 | | * IN THE SOFTWARE. |
23 | | */ |
24 | | |
25 | | #include "private-lib-core.h" |
26 | | |
27 | | #ifdef LWS_HAVE_SYS_TYPES_H |
28 | | #include <sys/types.h> |
29 | | #endif |
30 | | |
31 | | #if defined(LWS_PLAT_OPTEE) |
32 | | void lwsl_emit_optee(int level, const char *line); |
33 | | #endif |
34 | | |
35 | | lws_log_cx_t log_cx = { |
36 | | #if !defined(LWS_PLAT_OPTEE) |
37 | | .u.emit = lwsl_emit_stderr, |
38 | | #else |
39 | | .u.emit = lwsl_emit_optee, |
40 | | #endif |
41 | | /* |
42 | | * Default for code that logs before or without any lws_context, |
43 | | * or that never calls lws_set_log_level(): USER is included so |
44 | | * a tool's own output (eg, a test's FAIL lines) is not silently |
45 | | * filtered |
46 | | */ |
47 | | .lll_flags = LLL_ERR | LLL_WARN | LLL_NOTICE | |
48 | | LLL_USER, |
49 | | }; |
50 | | |
51 | | #if !defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_NO_LOGS) |
52 | | static const char * log_level_names ="EWNIDPHXCLUT??"; |
53 | | #endif |
54 | | |
55 | | /* |
56 | | * Name an instance tag and attach to a group |
57 | | */ |
58 | | |
59 | | void |
60 | | __lws_lc_tag(struct lws_context *context, lws_lifecycle_group_t *grp, |
61 | | lws_lifecycle_t *lc, const char *format, ...) |
62 | 0 | { |
63 | 0 | va_list ap; |
64 | 0 | int n = 1; |
65 | |
|
66 | 0 | if (*lc->gutag == '[') { |
67 | | /* appending inside [] */ |
68 | |
|
69 | 0 | char *cp = (char *)strchr(lc->gutag, ']'); |
70 | 0 | char rend[96]; |
71 | 0 | size_t ll, k; |
72 | 0 | int n; |
73 | |
|
74 | 0 | if (!cp) |
75 | 0 | return; |
76 | | |
77 | | /* length of closing brace and anything else after it */ |
78 | 0 | k = strlen(cp); |
79 | | |
80 | | /* compute the remaining gutag unused */ |
81 | 0 | ll = sizeof(lc->gutag) - lws_ptr_diff_size_t(cp, lc->gutag) - k - 1; |
82 | 0 | if (ll > sizeof(rend) - 1) |
83 | 0 | ll = sizeof(rend) - 1; |
84 | 0 | va_start(ap, format); |
85 | 0 | n = vsnprintf(rend, ll, format, ap); |
86 | 0 | va_end(ap); |
87 | |
|
88 | 0 | if ((unsigned int)n > ll) |
89 | 0 | n = (int)ll; |
90 | | |
91 | | /* shove the trailer up by what we added */ |
92 | 0 | memmove(cp + n, cp, k); |
93 | 0 | assert(k + (unsigned int)n < sizeof(lc->gutag)); |
94 | 0 | cp[k + (unsigned int)n] = '\0'; |
95 | | /* copy what we added into place */ |
96 | 0 | memcpy(cp, rend, (unsigned int)n); |
97 | |
|
98 | 0 | return; |
99 | 0 | } |
100 | | |
101 | 0 | assert(grp); |
102 | 0 | assert(grp->tag_prefix); /* lc group must have a tag prefix string */ |
103 | |
|
104 | 0 | lc->gutag[0] = '['; |
105 | |
|
106 | | #if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) /* ie, will have getpid if set */ |
107 | | n += lws_snprintf(&lc->gutag[n], sizeof(lc->gutag) - |
108 | | (unsigned int)n - 1u, "%u|", getpid()); |
109 | | #endif |
110 | 0 | n += lws_snprintf(&lc->gutag[n], sizeof(lc->gutag) - |
111 | 0 | (unsigned int)n - 1u, "%s|%lx|", |
112 | 0 | grp->tag_prefix, |
113 | 0 | (unsigned long)grp->ordinal++); |
114 | |
|
115 | 0 | va_start(ap, format); |
116 | 0 | n += vsnprintf(&lc->gutag[n], sizeof(lc->gutag) - (unsigned int)n - |
117 | 0 | 1u, format, ap); |
118 | 0 | va_end(ap); |
119 | |
|
120 | 0 | if (n < (int)sizeof(lc->gutag) - 2) { |
121 | 0 | lc->gutag[n++] = ']'; |
122 | 0 | lc->gutag[n++] = '\0'; |
123 | 0 | } else { |
124 | 0 | lc->gutag[sizeof(lc->gutag) - 2] = ']'; |
125 | 0 | lc->gutag[sizeof(lc->gutag) - 1] = '\0'; |
126 | 0 | } |
127 | |
|
128 | 0 | lc->us_creation = (uint64_t)lws_now_usecs(); |
129 | 0 | lws_dll2_add_tail(&lc->list, &grp->owner); |
130 | |
|
131 | 0 | lwsl_refcount_cx(lc->log_cx, 1); |
132 | |
|
133 | 0 | #if defined(LWS_LOG_TAG_LIFECYCLE) |
134 | 0 | lwsl_cx_info(context, " ++ %s (%d)", lc->gutag, (int)lws_dll2_count(&grp->owner)); |
135 | 0 | #endif |
136 | 0 | } |
137 | | |
138 | | /* |
139 | | * Normally we want to set the tag one time at creation. But sometimes we |
140 | | * don't have enough information at that point to give it a meaningful tag, eg, |
141 | | * it's an accepted, served connection but we haven't read data from it yet |
142 | | * to find out what it wants to be. |
143 | | * |
144 | | * This allows you to append some extra info to the tag in those cases, the |
145 | | * initial tag remains the same on the lhs so it can be tracked correctly. |
146 | | */ |
147 | | |
148 | | void |
149 | | __lws_lc_tag_append(lws_lifecycle_t *lc, const char *app) |
150 | 0 | { |
151 | 0 | int n = (int)strlen(lc->gutag); |
152 | |
|
153 | 0 | if (n && lc->gutag[n - 1] == ']') |
154 | 0 | n--; |
155 | |
|
156 | 0 | if (!lc->recycle_len) |
157 | 0 | lc->recycle_len = (uint8_t)n; |
158 | 0 | else |
159 | 0 | n = lc->recycle_len; |
160 | |
|
161 | 0 | if ((unsigned int)n + 2u >= sizeof(lc->gutag)) { |
162 | | /* |
163 | | * No room to append anything... sizeof(gutag) - 2 - n would |
164 | | * underflow to a huge size_t and lws_snprintf() only rejects |
165 | | * size 0. Just make sure the tag is closed and terminated. |
166 | | */ |
167 | 0 | lc->gutag[sizeof(lc->gutag) - 2] = ']'; |
168 | 0 | lc->gutag[sizeof(lc->gutag) - 1] = '\0'; |
169 | |
|
170 | 0 | return; |
171 | 0 | } |
172 | | |
173 | 0 | n += lws_snprintf(&lc->gutag[n], sizeof(lc->gutag) - 2u - |
174 | 0 | (unsigned int)n, "|%s]", app); |
175 | |
|
176 | 0 | if ((unsigned int)n >= sizeof(lc->gutag) - 2u) { |
177 | 0 | lc->gutag[sizeof(lc->gutag) - 2] = ']'; |
178 | 0 | lc->gutag[sizeof(lc->gutag) - 1] = '\0'; |
179 | 0 | } |
180 | 0 | } |
181 | | |
182 | | /* |
183 | | * Remove instance from group |
184 | | */ |
185 | | |
186 | | void |
187 | | __lws_lc_untag(struct lws_context *context, lws_lifecycle_t *lc) |
188 | 0 | { |
189 | | //lws_lifecycle_group_t *grp; |
190 | 0 | char buf[24]; |
191 | |
|
192 | 0 | if (!lc->gutag[0]) { /* we never tagged this object... */ |
193 | 0 | lwsl_cx_err(context, "%s never tagged", lc->gutag); |
194 | 0 | assert(0); |
195 | 0 | return; |
196 | 0 | } |
197 | | |
198 | 0 | if (!lws_dll2_owner(&lc->list)) { /* we already untagged this object... */ |
199 | 0 | lwsl_cx_err(context, "%s untagged twice", lc->gutag); |
200 | 0 | assert(0); |
201 | 0 | return; |
202 | 0 | } |
203 | | |
204 | | //grp = lws_container_of(lc->list.owner, lws_lifecycle_group_t, owner); |
205 | | |
206 | 0 | #if defined(LWS_LOG_TAG_LIFECYCLE) |
207 | 0 | if (lws_humanize(buf, sizeof(buf), |
208 | 0 | (uint64_t)lws_now_usecs() - lc->us_creation, |
209 | 0 | humanize_schema_us) > 0) |
210 | | |
211 | 0 | lwsl_cx_info(context, " -- %s (%d) %s", lc->gutag, |
212 | 0 | (int)lws_dll2_count(lws_dll2_owner(&lc->list)) - 1, buf); |
213 | 0 | #endif |
214 | |
|
215 | 0 | lws_dll2_remove(&lc->list); |
216 | |
|
217 | 0 | lwsl_refcount_cx(lc->log_cx, -1); |
218 | 0 | } |
219 | | |
220 | | const char * |
221 | | lws_lc_tag(lws_lifecycle_t *lc) |
222 | 0 | { |
223 | 0 | return lc->gutag; |
224 | 0 | } |
225 | | |
226 | | |
227 | | int |
228 | | lwsl_timestamp(int level, char *p, size_t len) |
229 | 185 | { |
230 | 185 | #if !defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_NO_LOGS) |
231 | 185 | time_t o_now; |
232 | 185 | unsigned long long now; |
233 | 185 | struct timeval tv; |
234 | 185 | struct tm *ptm = NULL; |
235 | 185 | #if defined(LWS_HAVE_LOCALTIME_R) |
236 | 185 | struct tm tm; |
237 | 185 | #endif |
238 | 185 | int n; |
239 | | |
240 | 185 | gettimeofday(&tv, NULL); |
241 | 185 | o_now = tv.tv_sec; |
242 | 185 | now = ((unsigned long long)tv.tv_sec * 10000) + |
243 | 185 | (unsigned int)(tv.tv_usec / 100); |
244 | | |
245 | 185 | #if defined(LWS_HAVE_LOCALTIME_R) |
246 | 185 | ptm = localtime_r(&o_now, &tm); |
247 | | #else |
248 | | ptm = localtime(&o_now); |
249 | | #endif |
250 | 185 | p[0] = '\0'; |
251 | 185 | for (n = 0; n < LLL_COUNT; n++) { |
252 | 185 | if (level != (1 << n)) |
253 | 0 | continue; |
254 | | |
255 | 185 | if (ptm) |
256 | 185 | n = lws_snprintf(p, len, |
257 | 185 | "[%04d/%02d/%02d %02d:%02d:%02d:%04d] %c: ", |
258 | 185 | ptm->tm_year + 1900, |
259 | 185 | ptm->tm_mon + 1, |
260 | 185 | ptm->tm_mday, |
261 | 185 | ptm->tm_hour, |
262 | 185 | ptm->tm_min, |
263 | 185 | ptm->tm_sec, |
264 | 185 | (int)(now % 10000), log_level_names[n]); |
265 | 0 | else |
266 | 0 | n = lws_snprintf(p, len, "[%llu:%04d] %c: ", |
267 | 0 | (unsigned long long) now / 10000, |
268 | 0 | (int)(now % 10000), log_level_names[n]); |
269 | | |
270 | | #if defined(LWS_PLAT_FREERTOS) |
271 | | n += lws_snprintf(p + n, len - n, "%6u: ", |
272 | | #if defined(LWS_AMAZON_RTOS) |
273 | | (unsigned int)xPortGetFreeHeapSize()); |
274 | | #else |
275 | | (unsigned int)esp_get_free_heap_size()); |
276 | | #endif |
277 | | #endif |
278 | | |
279 | 185 | return n; |
280 | 185 | } |
281 | | #else |
282 | | p[0] = '\0'; |
283 | | #endif |
284 | | |
285 | 0 | return 0; |
286 | 185 | } |
287 | | |
288 | | uint32_t |
289 | | lws_log_ratelimit_check(lws_log_ratelimit_t *rl, int64_t interval_us) |
290 | 0 | { |
291 | 0 | lws_usec_t now = lws_now_usecs(); |
292 | |
|
293 | 0 | if (now >= rl->next_log_us) { |
294 | 0 | uint32_t r = rl->dropped + 1; |
295 | 0 | rl->next_log_us = now + interval_us; |
296 | 0 | rl->dropped = 0; |
297 | 0 | return r; |
298 | 0 | } |
299 | | |
300 | 0 | rl->dropped++; |
301 | |
|
302 | 0 | return 0; |
303 | 0 | } |
304 | | |
305 | | |
306 | | #ifndef LWS_PLAT_OPTEE |
307 | | static const char * const colours[] = { |
308 | | "[31;1m", /* LLL_ERR */ |
309 | | "[36;1m", /* LLL_WARN */ |
310 | | "[35;1m", /* LLL_NOTICE */ |
311 | | "[32;1m", /* LLL_INFO */ |
312 | | "[34;1m", /* LLL_DEBUG */ |
313 | | "[33;1m", /* LLL_PARSER */ |
314 | | "[33m", /* LLL_HEADER */ |
315 | | "[33m", /* LLL_EXT */ |
316 | | "[33m", /* LLL_CLIENT */ |
317 | | "[33;1m", /* LLL_LATENCY */ |
318 | | "[0;1m", /* LLL_USER */ |
319 | | "[31m", /* LLL_THREAD */ |
320 | | }; |
321 | | |
322 | | static char tty; |
323 | | |
324 | | static void |
325 | | _lwsl_emit_stderr(int level, const char *line) |
326 | 121 | { |
327 | 121 | int n, m = LWS_ARRAY_SIZE(colours) - 1; |
328 | | |
329 | 121 | if (!tty) |
330 | 1 | tty = (char)(isatty(2) | 2); |
331 | | |
332 | 121 | if (tty == 3) { |
333 | 0 | n = 1 << (LWS_ARRAY_SIZE(colours) - 1); |
334 | 0 | while (n) { |
335 | 0 | if (level & n) |
336 | 0 | break; |
337 | 0 | m--; |
338 | 0 | n >>= 1; |
339 | 0 | } |
340 | 0 | fprintf(stderr, "%c%s%s%c[0m", 27, colours[m], line, 27); |
341 | 0 | } else |
342 | 121 | fprintf(stderr, "%s", line); |
343 | 121 | } |
344 | | |
345 | | void |
346 | | lwsl_emit_stderr(int level, const char *line) |
347 | 121 | { |
348 | 121 | _lwsl_emit_stderr(level, line); |
349 | 121 | } |
350 | | |
351 | | void |
352 | | lwsl_emit_stderr_notimestamp(int level, const char *line) |
353 | 0 | { |
354 | 0 | _lwsl_emit_stderr(level, line); |
355 | 0 | } |
356 | | |
357 | | #if !defined(LWS_PLAT_FREERTOS) && !defined(LWS_PLAT_OPTEE) && !defined(LWS_PLAT_BAREMETAL) |
358 | | |
359 | | /* |
360 | | * Helper to emit to a file |
361 | | */ |
362 | | |
363 | | void |
364 | | lws_log_emit_cx_file(struct lws_log_cx *cx, int level, const char *line, |
365 | | size_t len) |
366 | 0 | { |
367 | 0 | int fd = (int)(intptr_t)cx->stg; |
368 | |
|
369 | 0 | if (fd >= 0) |
370 | 0 | if (write(fd, line, (unsigned int)len) != (ssize_t)len) |
371 | 0 | fprintf(stderr, "Unable to write log to file\n"); |
372 | 0 | } |
373 | | |
374 | | /* |
375 | | * Helper to use a .refcount_cb to store logs in a file |
376 | | */ |
377 | | |
378 | | void |
379 | | lws_log_use_cx_file(struct lws_log_cx *cx, int _new) |
380 | 0 | { |
381 | 0 | int fd; |
382 | |
|
383 | 0 | if (_new > 0 && cx->refcount == 1) { |
384 | 0 | fd = open((const char *)cx->opaque, |
385 | 0 | LWS_O_CREAT | LWS_O_TRUNC | LWS_O_WRONLY, 0600); |
386 | 0 | if (fd < 0) |
387 | 0 | fprintf(stderr, "Unable to open log %s: errno %d\n", |
388 | 0 | (const char *)cx->opaque, errno); |
389 | 0 | cx->stg = (void *)(intptr_t)fd; |
390 | |
|
391 | 0 | return; |
392 | 0 | } |
393 | | |
394 | 0 | fd = (int)(intptr_t)cx->stg; |
395 | |
|
396 | 0 | if (_new <= 0 && cx->refcount == 0 && fd >= 0) { |
397 | 0 | close(fd); |
398 | 0 | cx->stg = (void *)(intptr_t)-1; |
399 | 0 | } |
400 | 0 | } |
401 | | |
402 | | #endif |
403 | | |
404 | | #endif |
405 | | |
406 | | #if !(defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_NETWORK)) |
407 | | |
408 | | /* |
409 | | * More than one thread may log, not only with SMP or the threadpool: the |
410 | | * async queue's workers and the txpacer thread log from inside lws, and |
411 | | * applications log from their own threads. So wherever there are pthreads, |
412 | | * except on the small embedded platforms where lws runs no threads of its |
413 | | * own, we must assume it. |
414 | | */ |
415 | | |
416 | | #if defined(LWS_HAVE_PTHREAD_H) && \ |
417 | | (LWS_MAX_SMP > 1 || defined(LWS_WITH_THREADPOOL) || \ |
418 | | defined(LWS_WITH_ASYNC_QUEUE) || \ |
419 | | (!defined(LWS_PLAT_OPTEE) && !defined(LWS_PLAT_BAREMETAL) && \ |
420 | | !defined(LWS_PLAT_FREERTOS))) |
421 | | #define LWS_LOG_THREADED |
422 | | #endif |
423 | | |
424 | | #if defined(LWS_LOG_THREADED) |
425 | | #define LWS_LOG_LINE_MAX 1024 |
426 | | #else |
427 | | #define LWS_LOG_LINE_MAX 256 |
428 | | #endif |
429 | | |
430 | | /* |
431 | | * The dupe and spew state below is processwide. When there may be more than |
432 | | * one thread logging, it is guarded by a lock that is never held across a |
433 | | * call into the emit function, so an emit that itself logs cannot deadlock. |
434 | | */ |
435 | | |
436 | | #if defined(LWS_LOG_THREADED) |
437 | | static pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER; |
438 | 182 | #define log_lock_take() pthread_mutex_lock(&log_lock) |
439 | 182 | #define log_lock_release() pthread_mutex_unlock(&log_lock) |
440 | | #else |
441 | | #define log_lock_take() |
442 | | #define log_lock_release() |
443 | | #endif |
444 | | |
445 | | /* |
446 | | * Log spew handling |
447 | | * |
448 | | * A "spew" is a sustained rate of log emission that nothing downstream can |
449 | | * usefully absorb: a tight loop logging every service call, a POLLOUT storm, |
450 | | * a state machine ping-ponging. Left alone it fills the disk, or in a test |
451 | | * harness, the RAM the logs were being captured in, within seconds. |
452 | | * |
453 | | * Back-to-back identical lines are already collapsed by the dupe detection |
454 | | * in __lws_logv(). This catches the general case where the lines differ. |
455 | | * |
456 | | * We keep a small ring of the timestamps of the last LWS_LOG_SPEW_TS_RING |
457 | | * lines that got as far as emission. When the whole ring spans less than |
458 | | * LWS_LOG_SPEW_ENTER_US, ie, the sustained rate has exceeded |
459 | | * LWS_LOG_SPEW_TS_RING / LWS_LOG_SPEW_ENTER_US, we enter spew mode: we |
460 | | * allocate a small heap ringbuffer of LWS_LOG_SPEW_RING_SIZE bytes and divert |
461 | | * the formatted lines into that instead of emitting them. It retains only |
462 | | * the last LWS_LOG_SPEW_TAIL_LINES lines, fewer if they are long: enough to |
463 | | * see how the spew ended, since the spew itself is the problem, and this also |
464 | | * runs on devices with little heap. A heartbeat line goes out once a second |
465 | | * so the log shows the process is alive and how much was swallowed. |
466 | | * |
467 | | * Leaving spew mode is decided over a much shorter window than entering it, |
468 | | * since a spew typically never pauses at all: once the last |
469 | | * LWS_LOG_SPEW_EXIT_SAMPLES lines span more than the exit quiet, initially |
470 | | * LWS_LOG_SPEW_EXIT_US, we call the spew over, replay the retained tail in |
471 | | * order, free the ringbuffer and go back to emitting directly. The timestamp |
472 | | * ring is restarted, so a full LWS_LOG_SPEW_TS_RING lines at spew rate are |
473 | | * needed before we would enter again; that stops a bursty spew flapping in |
474 | | * and out of the mode and replaying its tail each time. |
475 | | * |
476 | | * When the next log arrives, a spew that stopped and a process that was |
477 | | * starved of cpu for a while, eg, on an overloaded box, look exactly the same. |
478 | | * What does tell them apart is the spew coming back: if we enter spew mode |
479 | | * again within LWS_LOG_SPEW_RESUME_US of calling it over, we were wrong, and |
480 | | * the quiet that fooled us is what a stall looks like on this box at the |
481 | | * moment. So the exit quiet grows to twice that, up to |
482 | | * LWS_LOG_SPEW_EXIT_MAX_US, and we say so. Conditions change, so each time a |
483 | | * spew starts afresh, the exit quiet halves back towards LWS_LOG_SPEW_EXIT_US. |
484 | | * |
485 | | * A legitimate surge of logs, eg, context creation at debug level, may be |
486 | | * fast enough to trip entry. The first LWS_LOG_SPEW_TS_RING - 1 lines of it |
487 | | * were emitted before we entered spew mode, and if it ends within the tail we |
488 | | * retain, it is replayed intact, so a surge of up to that many lines is |
489 | | * waved through losslessly. A longer one loses its middle, as a spew does. |
490 | | * |
491 | | * The exit check can only run when a log arrives. If a spew stops dead and |
492 | | * nothing logs afterwards, the retained tail is replayed by the next log |
493 | | * whenever it comes, or by lws_context_destroy(). If the process dies |
494 | | * during a spew, the retained tail dies with it; that is the price of not |
495 | | * having written it out. |
496 | | * |
497 | | * The tunables can be overridden from the compiler command line. How much of |
498 | | * the tail is retained can also be changed at runtime by |
499 | | * lws_log_spew_tail_lines(), eg from the --log-spew-tail commandline switch, |
500 | | * for when the part of interest is further back than the default tail. A log |
501 | | * context with LLLF_LOG_SPEW_OFF in its flags bypasses all of this. |
502 | | */ |
503 | | |
504 | | #if !defined(LWS_LOG_SPEW_TS_RING) |
505 | 584 | #define LWS_LOG_SPEW_TS_RING 64 |
506 | | #endif |
507 | | #if !defined(LWS_LOG_SPEW_ENTER_US) |
508 | 2 | #define LWS_LOG_SPEW_ENTER_US 20000 |
509 | | #endif |
510 | | #if !defined(LWS_LOG_SPEW_EXIT_SAMPLES) |
511 | 112 | #define LWS_LOG_SPEW_EXIT_SAMPLES 8 |
512 | | #endif |
513 | | #if !defined(LWS_LOG_SPEW_EXIT_US) |
514 | 2 | #define LWS_LOG_SPEW_EXIT_US 5000 |
515 | | #endif |
516 | | #if !defined(LWS_LOG_SPEW_EXIT_MAX_US) |
517 | 0 | #define LWS_LOG_SPEW_EXIT_MAX_US 1000000 |
518 | | #endif |
519 | | #if !defined(LWS_LOG_SPEW_RESUME_US) |
520 | 0 | #define LWS_LOG_SPEW_RESUME_US 1000000 |
521 | | #endif |
522 | | #if !defined(LWS_LOG_SPEW_TAIL_LINES) |
523 | 2 | #define LWS_LOG_SPEW_TAIL_LINES 10 |
524 | | #endif |
525 | | #if !defined(LWS_LOG_SPEW_RING_SIZE) |
526 | | /* a threaded build's lines are 1024 long, too big for the small ring */ |
527 | | #if (defined(LWS_PLAT_FREERTOS) || defined(LWS_PLAT_BAREMETAL)) && \ |
528 | | !defined(LWS_LOG_THREADED) |
529 | | #define LWS_LOG_SPEW_RING_SIZE 1024 |
530 | | #else |
531 | 1 | #define LWS_LOG_SPEW_RING_SIZE 2048 |
532 | | #endif |
533 | | #endif |
534 | | #if !defined(LWS_LOG_SPEW_HEARTBEAT_US) |
535 | 36 | #define LWS_LOG_SPEW_HEARTBEAT_US 1000000 |
536 | | #endif |
537 | | /* |
538 | | * Ring bytes budgeted per line when the tail is set at runtime: typical lines |
539 | | * fit, so the tail is usually the number of lines asked for, fewer if they |
540 | | * are long |
541 | | */ |
542 | | #if !defined(LWS_LOG_SPEW_LINE_BUDGET) |
543 | 0 | #define LWS_LOG_SPEW_LINE_BUDGET 256 |
544 | | #endif |
545 | | /* limit of a runtime tail, so a typo cannot ask for gigabytes of heap */ |
546 | | #if !defined(LWS_LOG_SPEW_TAIL_LINES_MAX) |
547 | 0 | #define LWS_LOG_SPEW_TAIL_LINES_MAX 65536 |
548 | | #endif |
549 | | |
550 | | /* each retained line is [len lo][len hi][level lo][level hi][len bytes] */ |
551 | 148 | #define SPEW_HDR 4 |
552 | | |
553 | | #if LWS_LOG_LINE_MAX + SPEW_HDR > LWS_LOG_SPEW_RING_SIZE |
554 | | #error "LWS_LOG_SPEW_RING_SIZE must hold at least one maximum-length log line" |
555 | | #endif |
556 | | |
557 | | typedef struct lws_log_spew_ring { |
558 | | uint8_t *buf; /* NULL: not in spew mode */ |
559 | | size_t size; /* of buf */ |
560 | | size_t head; /* next byte to write */ |
561 | | size_t tail; /* oldest byte retained */ |
562 | | size_t used; |
563 | | unsigned int lines; /* retained */ |
564 | | unsigned int max_lines; /* most lines to retain */ |
565 | | lws_usec_t entered; |
566 | | lws_usec_t exit_us; /* quiet needed to call this spew over */ |
567 | | lws_usec_t quiet; /* the quiet that called it over */ |
568 | | uint32_t swallowed; /* lines diverted into the ring */ |
569 | | uint32_t lost; /* of those, pushed out again */ |
570 | | } lws_log_spew_ring_t; |
571 | | |
572 | | typedef struct lws_log_spew { |
573 | | lws_usec_t ts[LWS_LOG_SPEW_TS_RING]; |
574 | | lws_log_spew_ring_t r; |
575 | | lws_usec_t last_heartbeat; |
576 | | lws_usec_t exit_us; /* quiet that calls a spew over */ |
577 | | lws_usec_t exited; /* when we last called one over */ |
578 | | lws_usec_t exited_quiet; /* ... on seeing this quiet */ |
579 | | unsigned int ts_head; /* next slot to write */ |
580 | | unsigned int ts_count; /* valid slots */ |
581 | | unsigned int tail_lines; /* runtime tail, 0 = default */ |
582 | | } lws_log_spew_t; |
583 | | |
584 | | static lws_log_spew_t spew; |
585 | | static char spew_entering; /* setting the ring up: no re-entry */ |
586 | | |
587 | | enum { |
588 | | SPEW_EMIT, /* not in spew mode, emit normally */ |
589 | | SPEW_ENTERED, /* this line entered spew mode and was retained */ |
590 | | SPEW_RESUMED, /* ... and we had called the spew over too early */ |
591 | | SPEW_SWALLOWED, /* retained, say nothing */ |
592 | | SPEW_HEARTBEAT, /* retained, but it is time to show signs of life */ |
593 | | SPEW_EXITED /* spew eased: replay the ring, then emit normally */ |
594 | | }; |
595 | | |
596 | | /* timestamp of the line k lines before the most recent one */ |
597 | | |
598 | | static lws_usec_t |
599 | | spew_ts_ago(unsigned int k) |
600 | 38 | { |
601 | 38 | return spew.ts[(spew.ts_head + LWS_LOG_SPEW_TS_RING - 1 - k) % |
602 | 38 | LWS_LOG_SPEW_TS_RING]; |
603 | 38 | } |
604 | | |
605 | | static void |
606 | | spew_ring_write(lws_log_spew_ring_t *r, const uint8_t *p, size_t len) |
607 | 74 | { |
608 | 74 | size_t n = r->size - r->head; |
609 | | |
610 | 74 | if (n > len) |
611 | 73 | n = len; |
612 | 74 | memcpy(r->buf + r->head, p, n); |
613 | 74 | if (len - n) |
614 | 1 | memcpy(r->buf, p + n, len - n); |
615 | 74 | r->head = (r->head + len) % r->size; |
616 | 74 | r->used += len; |
617 | 74 | } |
618 | | |
619 | | static void |
620 | | spew_ring_read(lws_log_spew_ring_t *r, uint8_t *p, size_t len) |
621 | 47 | { |
622 | 47 | size_t n = r->size - r->tail; |
623 | | |
624 | 47 | if (n > len) |
625 | 47 | n = len; |
626 | 47 | memcpy(p, r->buf + r->tail, n); |
627 | 47 | if (len - n) |
628 | 0 | memcpy(p + n, r->buf, len - n); |
629 | 47 | r->tail = (r->tail + len) % r->size; |
630 | 47 | r->used -= len; |
631 | 47 | } |
632 | | |
633 | | /* take the oldest retained line out of the ring, 0 if nothing retained */ |
634 | | |
635 | | static size_t |
636 | | spew_ring_pop(lws_log_spew_ring_t *r, char *line, size_t max, int *level) |
637 | 37 | { |
638 | 37 | uint8_t hdr[SPEW_HDR]; |
639 | 37 | size_t len; |
640 | | |
641 | 37 | *level = 0; |
642 | 37 | if (!r->lines) |
643 | 0 | return 0; |
644 | | |
645 | 37 | spew_ring_read(r, hdr, SPEW_HDR); |
646 | 37 | len = (size_t)hdr[0] | ((size_t)hdr[1] << 8); |
647 | 37 | *level = hdr[2] | (hdr[3] << 8); |
648 | 37 | r->lines--; |
649 | | |
650 | 37 | if (!line) { |
651 | | /* discard it */ |
652 | 27 | r->tail = (r->tail + len) % r->size; |
653 | 27 | r->used -= len; |
654 | | |
655 | 27 | return len; |
656 | 27 | } |
657 | | |
658 | 37 | assert(len < max); |
659 | 10 | spew_ring_read(r, (uint8_t *)line, len); |
660 | 10 | line[len] = '\0'; |
661 | | |
662 | 10 | return len; |
663 | 10 | } |
664 | | |
665 | | static void |
666 | | spew_ring_push(lws_log_spew_ring_t *r, int level, const char *line, size_t len) |
667 | 37 | { |
668 | 37 | uint8_t hdr[SPEW_HDR]; |
669 | 37 | int lv; |
670 | | |
671 | | /* |
672 | | * len is at most LWS_LOG_LINE_MAX, which the ring is checked at |
673 | | * compile time to hold along with its header, and is never sized |
674 | | * smaller than that at runtime. Still hold that against the ring we |
675 | | * actually have: a line it can't hold would never fit however many |
676 | | * old lines we forget below, and would overrun it if written. |
677 | | */ |
678 | | |
679 | 37 | if (len + SPEW_HDR > r->size) |
680 | 0 | return; |
681 | | |
682 | | /* make room by forgetting the oldest lines */ |
683 | | |
684 | 64 | while (r->lines >= r->max_lines || |
685 | 37 | r->size - r->used < len + SPEW_HDR) { |
686 | 27 | spew_ring_pop(r, NULL, 0, &lv); |
687 | 27 | r->lost++; |
688 | 27 | } |
689 | | |
690 | 37 | hdr[0] = (uint8_t)(len & 0xff); |
691 | 37 | hdr[1] = (uint8_t)(len >> 8); |
692 | 37 | hdr[2] = (uint8_t)(level & 0xff); |
693 | 37 | hdr[3] = (uint8_t)((level >> 8) & 0xff); |
694 | 37 | spew_ring_write(r, hdr, SPEW_HDR); |
695 | 37 | spew_ring_write(r, (const uint8_t *)line, len); |
696 | 37 | r->lines++; |
697 | 37 | r->swallowed++; |
698 | 37 | } |
699 | | |
700 | | /* |
701 | | * Account for a line that is about to be emitted and decide its fate. |
702 | | * |
703 | | * Called with the log lock held. If we leave spew mode, ownership of the ring |
704 | | * is handed out via *replay so the caller can replay and free it without the |
705 | | * lock, and the caller sees an empty ring here from then on. |
706 | | */ |
707 | | |
708 | | static int |
709 | | spew_track(lws_usec_t now, int level, const char *line, size_t len, |
710 | | lws_log_spew_ring_t *replay) |
711 | 145 | { |
712 | 145 | lws_usec_t quiet; |
713 | 145 | int act; |
714 | | |
715 | 145 | spew.ts[spew.ts_head] = now; |
716 | 145 | spew.ts_head = (spew.ts_head + 1) % LWS_LOG_SPEW_TS_RING; |
717 | 145 | if (spew.ts_count < LWS_LOG_SPEW_TS_RING) |
718 | 108 | spew.ts_count++; |
719 | | |
720 | 145 | if (!spew.r.buf) { |
721 | 108 | if (spew.ts_count < LWS_LOG_SPEW_TS_RING || |
722 | 1 | now - spew_ts_ago(LWS_LOG_SPEW_TS_RING - 1) >= |
723 | 1 | LWS_LOG_SPEW_ENTER_US) |
724 | 107 | return SPEW_EMIT; |
725 | | |
726 | | /* |
727 | | * The ring comes from the libc allocator, not lws_malloc(): |
728 | | * the lws allocator logs its allocations at debug level, and |
729 | | * that log re-entered here while the ring was being set up, |
730 | | * so an inner call allocated and filled one ring and the |
731 | | * outer then installed a different buffer under the inner's |
732 | | * counters (and with a non-recursive log lock it would have |
733 | | * deadlocked). The guard below refuses re-entry outright. |
734 | | */ |
735 | 1 | if (spew_entering) |
736 | 0 | return SPEW_EMIT; |
737 | 1 | spew_entering = 1; |
738 | 1 | memset(&spew.r, 0, sizeof(spew.r)); |
739 | 1 | spew.r.max_lines = LWS_LOG_SPEW_TAIL_LINES; |
740 | 1 | spew.r.size = LWS_LOG_SPEW_RING_SIZE; |
741 | 1 | if (spew.tail_lines) { |
742 | 0 | spew.r.max_lines = spew.tail_lines; |
743 | 0 | if (spew.r.size < (size_t)spew.tail_lines * |
744 | 0 | LWS_LOG_SPEW_LINE_BUDGET) |
745 | 0 | spew.r.size = (size_t)spew.tail_lines * |
746 | 0 | LWS_LOG_SPEW_LINE_BUDGET; |
747 | 0 | } |
748 | | /* zeroed, so no stale heap content can ever be read out of it */ |
749 | 1 | spew.r.buf = calloc(1, spew.r.size); |
750 | 1 | spew_entering = 0; |
751 | 1 | if (!spew.r.buf) |
752 | | /* no memory to retain anything: keep emitting */ |
753 | 0 | return SPEW_EMIT; |
754 | | |
755 | 1 | if (spew.exited && now - spew.exited < LWS_LOG_SPEW_RESUME_US) { |
756 | | /* |
757 | | * The spew never ended, we were only descheduled for |
758 | | * long enough to think so: don't be fooled by that |
759 | | * again |
760 | | */ |
761 | 0 | act = SPEW_RESUMED; |
762 | 0 | if (spew.exit_us < 2 * spew.exited_quiet) |
763 | 0 | spew.exit_us = 2 * spew.exited_quiet; |
764 | 0 | if (spew.exit_us > LWS_LOG_SPEW_EXIT_MAX_US) |
765 | 0 | spew.exit_us = LWS_LOG_SPEW_EXIT_MAX_US; |
766 | 0 | spew.r.quiet = spew.exited_quiet; |
767 | 1 | } else { |
768 | | /* a new spew: conditions may have improved since */ |
769 | 1 | act = SPEW_ENTERED; |
770 | 1 | spew.exit_us /= 2; |
771 | 1 | if (spew.exit_us < LWS_LOG_SPEW_EXIT_US) |
772 | 1 | spew.exit_us = LWS_LOG_SPEW_EXIT_US; |
773 | 1 | } |
774 | 1 | spew.exited = 0; |
775 | | |
776 | 1 | spew.r.entered = now; |
777 | 1 | spew.r.exit_us = spew.exit_us; |
778 | 1 | spew.last_heartbeat = now; |
779 | 1 | spew_ring_push(&spew.r, level, line, len); |
780 | 1 | *replay = spew.r; |
781 | | |
782 | 1 | return act; |
783 | 1 | } |
784 | | |
785 | 37 | quiet = now - spew_ts_ago(LWS_LOG_SPEW_EXIT_SAMPLES); |
786 | 37 | if (spew.ts_count > LWS_LOG_SPEW_EXIT_SAMPLES && |
787 | 37 | quiet > spew.r.exit_us) { |
788 | 1 | spew.r.quiet = quiet; |
789 | 1 | *replay = spew.r; |
790 | 1 | memset(&spew.r, 0, sizeof(spew.r)); |
791 | | /* a fresh run at spew rate is needed to enter again */ |
792 | 1 | spew.ts_head = 0; |
793 | 1 | spew.ts_count = 0; |
794 | | /* if it enters again right away, we were wrong about this */ |
795 | 1 | spew.exited = now; |
796 | 1 | spew.exited_quiet = quiet; |
797 | | |
798 | 1 | return SPEW_EXITED; |
799 | 1 | } |
800 | | |
801 | 36 | spew_ring_push(&spew.r, level, line, len); |
802 | | |
803 | 36 | if (now - spew.last_heartbeat >= LWS_LOG_SPEW_HEARTBEAT_US) { |
804 | 0 | spew.last_heartbeat = now; |
805 | 0 | *replay = spew.r; |
806 | |
|
807 | 0 | return SPEW_HEARTBEAT; |
808 | 0 | } |
809 | | |
810 | 36 | return SPEW_SWALLOWED; |
811 | 36 | } |
812 | | |
813 | | static void |
814 | | log_emit(lws_log_cx_t *cx, int level, const char *line, size_t len) |
815 | 121 | { |
816 | 121 | if (cx->lll_flags & LLLF_LOG_CONTEXT_AWARE) |
817 | 0 | cx->u.emit_cx(cx, level, line, len); |
818 | 121 | else |
819 | 121 | cx->u.emit(level, line); |
820 | 121 | } |
821 | | |
822 | | /* emit a line of our own about the spew, in the style of the cx */ |
823 | | |
824 | | static void LWS_FORMAT(3) |
825 | | spew_emit(lws_log_cx_t *cx, int level, const char *format, ...) |
826 | 3 | { |
827 | 3 | char b[160], *p = b, *end = b + sizeof(b) - 2; |
828 | 3 | va_list ap; |
829 | 3 | int n; |
830 | | |
831 | 3 | b[0] = '\0'; |
832 | | #if !defined(LWS_LOGS_TIMESTAMP) |
833 | | if (cx->lll_flags & LLLF_LOG_TIMESTAMP) |
834 | | #endif |
835 | 3 | { |
836 | 3 | lwsl_timestamp(level, b, sizeof(b)); |
837 | 3 | p += strlen(b); |
838 | 3 | } |
839 | | |
840 | 3 | va_start(ap, format); |
841 | 3 | n = vsnprintf(p, lws_ptr_diff_size_t(end, p), format, ap); |
842 | 3 | va_end(ap); |
843 | 3 | if (n < 0) |
844 | 0 | n = 0; |
845 | 3 | p += n; |
846 | 3 | if (p > end) |
847 | 0 | p = end; |
848 | 3 | *p++ = '\n'; |
849 | 3 | *p = '\0'; |
850 | | |
851 | 3 | log_emit(cx, level, b, lws_ptr_diff_size_t(p, b)); |
852 | 3 | } |
853 | | |
854 | | /* replay the retained tail of a spew in order, then free the ring */ |
855 | | |
856 | | static void |
857 | | spew_replay(lws_log_cx_t *cx, int level, lws_log_spew_ring_t *r, |
858 | | lws_usec_t now) |
859 | 1 | { |
860 | 1 | char line[LWS_LOG_LINE_MAX + 1]; |
861 | 1 | int lv = level; |
862 | 1 | size_t len; |
863 | | |
864 | 1 | spew_emit(cx, level, "lws: log spew eased: %ums quiet, swallowed %u logs" |
865 | 1 | " over %ums, %u oldest not retained, last %u" |
866 | 1 | " replayed:", |
867 | 1 | (unsigned int)(r->quiet / 1000), |
868 | 1 | (unsigned int)r->swallowed, |
869 | 1 | (unsigned int)((now - r->entered) / 1000), |
870 | 1 | (unsigned int)r->lost, r->lines); |
871 | | |
872 | 11 | while (r->lines) { |
873 | 10 | len = spew_ring_pop(r, line, sizeof(line), &lv); |
874 | 10 | log_emit(cx, lv, line, len); |
875 | 10 | } |
876 | | |
877 | 1 | spew_emit(cx, level, "lws: log spew: end of replay"); |
878 | | |
879 | 1 | free(r->buf); /* libc's: see spew_track() */ |
880 | 1 | r->buf = NULL; |
881 | 1 | } |
882 | | |
883 | | void |
884 | | lws_log_spew_tail_lines(unsigned int lines) |
885 | 0 | { |
886 | 0 | if (lines > LWS_LOG_SPEW_TAIL_LINES_MAX) |
887 | 0 | lines = LWS_LOG_SPEW_TAIL_LINES_MAX; |
888 | | |
889 | | /* a spew in progress keeps the ring it has, the next one uses this */ |
890 | 0 | log_lock_take(); |
891 | 0 | spew.tail_lines = lines; |
892 | 0 | log_lock_release(); |
893 | 0 | } |
894 | | |
895 | | /* |
896 | | * If a spew was in progress, replay and free what it retained now, rather than |
897 | | * waiting for a log that may never come. Called from lws_context_destroy(). |
898 | | */ |
899 | | |
900 | | void |
901 | | lws_log_spew_flush(lws_log_cx_t *cx) |
902 | 0 | { |
903 | 0 | lws_log_spew_ring_t r; |
904 | |
|
905 | 0 | if (!cx) |
906 | 0 | cx = &log_cx; |
907 | |
|
908 | 0 | log_lock_take(); |
909 | 0 | r = spew.r; |
910 | 0 | memset(&spew.r, 0, sizeof(spew.r)); |
911 | 0 | spew.ts_head = 0; |
912 | 0 | spew.ts_count = 0; |
913 | | /* not called over by a quiet: nothing to learn if it comes back */ |
914 | 0 | spew.exited = 0; |
915 | 0 | log_lock_release(); |
916 | |
|
917 | 0 | if (r.buf) |
918 | 0 | spew_replay(cx, LLL_NOTICE, &r, lws_now_usecs()); |
919 | 0 | } |
920 | | |
921 | | void |
922 | | __lws_logv(lws_log_cx_t *cx, lws_log_prepend_cx_t prep, void *obj, |
923 | | int filter, uint32_t dropped, const char *_fun, const char *format, va_list vl) |
924 | 11.8k | { |
925 | | #if !defined(LWS_LOG_THREADED) |
926 | | /* this is incompatible with multithreaded logging */ |
927 | | static char buf[LWS_LOG_LINE_MAX]; |
928 | | #else |
929 | 11.8k | char buf[LWS_LOG_LINE_MAX]; |
930 | 11.8k | #endif |
931 | 11.8k | static char prev_buf[LWS_LOG_LINE_MAX]; |
932 | 11.8k | static uint32_t log_dupes; |
933 | 11.8k | static lws_usec_t last_log_dupe_emit; |
934 | 11.8k | char *p = buf, *end = p + sizeof(buf) - 1, *body_start; |
935 | 11.8k | lws_log_spew_ring_t replay; |
936 | 11.8k | lws_log_cx_t *cxp; |
937 | 11.8k | int n, back = 0, act = SPEW_EMIT; |
938 | 11.8k | lws_usec_t now; |
939 | | |
940 | | /* |
941 | | * We need to handle NULL wsi etc at the wrappers as gracefully as |
942 | | * possible |
943 | | */ |
944 | | |
945 | 11.8k | if (!cx) { |
946 | 0 | lws_strncpy(p, "NULL log cx: ", sizeof(buf) - 1); |
947 | 0 | p += 13; |
948 | | /* use the processwide one for lack of anything better */ |
949 | 0 | cx = &log_cx; |
950 | 0 | } |
951 | | |
952 | 11.8k | cxp = cx; |
953 | | |
954 | 11.8k | if (!(cx->lll_flags & (uint32_t)filter)) |
955 | | /* |
956 | | * logs may be produced and built in to the code but disabled |
957 | | * at runtime |
958 | | */ |
959 | 11.6k | return; |
960 | | |
961 | | #if !defined(LWS_LOGS_TIMESTAMP) |
962 | | if (cx->lll_flags & LLLF_LOG_TIMESTAMP) |
963 | | #endif |
964 | 182 | { |
965 | 182 | buf[0] = '\0'; |
966 | 182 | lwsl_timestamp(filter, buf, sizeof(buf)); |
967 | 182 | p += strlen(buf); |
968 | 182 | } |
969 | | |
970 | 182 | body_start = p; |
971 | | |
972 | | /* |
973 | | * prepend parent log ctx content first |
974 | | * top level cx also gets an opportunity to prepend |
975 | | */ |
976 | | |
977 | 182 | while (cxp->parent) { |
978 | 0 | cxp = cxp->parent; |
979 | 0 | back++; |
980 | 0 | } |
981 | | |
982 | 182 | do { |
983 | 182 | int b = back; |
984 | | |
985 | 182 | cxp = cx; |
986 | 182 | while (b--) |
987 | 0 | cxp = cxp->parent; |
988 | 182 | if (cxp->prepend) |
989 | 0 | cxp->prepend(cxp, NULL, &p, end); |
990 | | |
991 | 182 | back--; |
992 | 182 | } while (back > 0); |
993 | | |
994 | 182 | if (prep) |
995 | 0 | prep(cxp, obj, &p, end); |
996 | | |
997 | 182 | if (_fun) |
998 | 0 | p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "%s: ", _fun); |
999 | | |
1000 | | /* |
1001 | | * The actual log content |
1002 | | */ |
1003 | | |
1004 | 182 | n = vsnprintf(p, lws_ptr_diff_size_t(end, p), format, vl); |
1005 | | |
1006 | | /* vnsprintf returns what it would have written, even if truncated */ |
1007 | 182 | if (p + n > end - 2) { |
1008 | 0 | p = end - 5; |
1009 | 0 | *p++ = '.'; |
1010 | 0 | *p++ = '.'; |
1011 | 0 | *p++ = '.'; |
1012 | 0 | *p++ = '\n'; |
1013 | 0 | *p++ = '\0'; |
1014 | 182 | } else { |
1015 | 182 | if (n > 0) { |
1016 | 182 | p += n; |
1017 | 182 | if (p[-1] == '\n') |
1018 | 182 | p--; |
1019 | 182 | if (dropped > 1) |
1020 | 0 | p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), " (dropped %u logs)", (unsigned int)(dropped - 1)); |
1021 | 182 | if (p < end - 1) { |
1022 | 182 | *p++ = '\n'; |
1023 | 182 | *p = '\0'; |
1024 | 182 | } |
1025 | 182 | } |
1026 | 182 | } |
1027 | | |
1028 | 182 | now = lws_now_usecs(); |
1029 | | |
1030 | 182 | log_lock_take(); |
1031 | | |
1032 | 182 | if (!strcmp(body_start, prev_buf)) { |
1033 | 37 | log_dupes++; |
1034 | 37 | if (now - last_log_dupe_emit < 1000000) { |
1035 | 37 | log_lock_release(); |
1036 | 37 | return; |
1037 | 37 | } |
1038 | | |
1039 | 0 | p = body_start + strlen(body_start); |
1040 | 0 | if (p > buf && p[-1] == '\n') |
1041 | 0 | p--; |
1042 | 0 | p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), |
1043 | 0 | " (swallowed %u dupes)\n", (unsigned int)log_dupes); |
1044 | 0 | log_dupes = 0; |
1045 | 0 | last_log_dupe_emit = now; |
1046 | 145 | } else { |
1047 | 145 | lws_strncpy(prev_buf, body_start, sizeof(prev_buf)); |
1048 | 145 | log_dupes = 0; |
1049 | 145 | last_log_dupe_emit = now; |
1050 | 145 | } |
1051 | | |
1052 | 145 | if (!(cx->lll_flags & LLLF_LOG_SPEW_OFF)) |
1053 | 145 | act = spew_track(now, filter, buf, lws_ptr_diff_size_t(p, buf), |
1054 | 145 | &replay); |
1055 | | |
1056 | 145 | log_lock_release(); |
1057 | | |
1058 | 145 | switch (act) { |
1059 | 1 | case SPEW_ENTERED: |
1060 | 1 | spew_emit(cx, filter, "lws: log spew: %u logs in %ums, retaining" |
1061 | 1 | " the last %u of it until %u logs" |
1062 | 1 | " span over %ums", |
1063 | 1 | LWS_LOG_SPEW_TS_RING, LWS_LOG_SPEW_ENTER_US / 1000, |
1064 | 1 | LWS_LOG_SPEW_TAIL_LINES, LWS_LOG_SPEW_EXIT_SAMPLES, |
1065 | 1 | (unsigned int)(replay.exit_us / 1000)); |
1066 | 1 | return; |
1067 | | |
1068 | 0 | case SPEW_RESUMED: |
1069 | 0 | spew_emit(cx, filter, "lws: log spew: resumed after %ums quiet," |
1070 | 0 | " it was not over: retaining again until" |
1071 | 0 | " %u logs span over %ums", |
1072 | 0 | (unsigned int)(replay.quiet / 1000), |
1073 | 0 | LWS_LOG_SPEW_EXIT_SAMPLES, |
1074 | 0 | (unsigned int)(replay.exit_us / 1000)); |
1075 | 0 | return; |
1076 | | |
1077 | 36 | case SPEW_SWALLOWED: |
1078 | 36 | return; |
1079 | | |
1080 | 0 | case SPEW_HEARTBEAT: |
1081 | 0 | spew_emit(cx, filter, "lws: log spew: still going, %u logs" |
1082 | 0 | " swallowed over %us", |
1083 | 0 | (unsigned int)replay.swallowed, |
1084 | 0 | (unsigned int)((now - replay.entered) / 1000000)); |
1085 | 0 | return; |
1086 | | |
1087 | 1 | case SPEW_EXITED: |
1088 | 1 | spew_replay(cx, filter, &replay, now); |
1089 | 1 | break; |
1090 | | |
1091 | 107 | default: |
1092 | 107 | break; |
1093 | 145 | } |
1094 | | |
1095 | | /* |
1096 | | * The actual emit |
1097 | | */ |
1098 | | |
1099 | 108 | log_emit(cx, filter, buf, lws_ptr_diff_size_t(p, buf)); |
1100 | 108 | } |
1101 | | |
1102 | | void _lws_logv(int filter, const char *format, va_list vl) |
1103 | 0 | { |
1104 | 0 | __lws_logv(&log_cx, NULL, NULL, filter, 0, NULL, format, vl); |
1105 | 0 | } |
1106 | | |
1107 | | void _lws_log(int filter, const char *format, ...) |
1108 | 11.8k | { |
1109 | 11.8k | va_list ap; |
1110 | | |
1111 | 11.8k | va_start(ap, format); |
1112 | 11.8k | __lws_logv(&log_cx, NULL, NULL, filter, 0, NULL, format, ap); |
1113 | 11.8k | va_end(ap); |
1114 | 11.8k | } |
1115 | | |
1116 | | void _lws_log_rl(int filter, uint32_t dropped, const char *format, ...) |
1117 | 0 | { |
1118 | 0 | va_list ap; |
1119 | |
|
1120 | 0 | va_start(ap, format); |
1121 | 0 | __lws_logv(&log_cx, NULL, NULL, filter, dropped, NULL, format, ap); |
1122 | 0 | va_end(ap); |
1123 | 0 | } |
1124 | | |
1125 | | void _lws_log_cx(lws_log_cx_t *cx, lws_log_prepend_cx_t prep, void *obj, |
1126 | | int filter, const char *_fun, const char *format, ...) |
1127 | 0 | { |
1128 | 0 | va_list ap; |
1129 | |
|
1130 | 0 | if (!cx) |
1131 | 0 | cx = &log_cx; |
1132 | |
|
1133 | 0 | va_start(ap, format); |
1134 | 0 | __lws_logv(cx, prep, obj, filter, 0, _fun, format, ap); |
1135 | 0 | va_end(ap); |
1136 | 0 | } |
1137 | | |
1138 | | void _lws_log_cx_rl(lws_log_cx_t *cx, lws_log_prepend_cx_t prep, void *obj, |
1139 | | int filter, uint32_t dropped, const char *_fun, const char *format, ...) |
1140 | 0 | { |
1141 | 0 | va_list ap; |
1142 | |
|
1143 | 0 | if (!cx) |
1144 | 0 | cx = &log_cx; |
1145 | |
|
1146 | 0 | va_start(ap, format); |
1147 | 0 | __lws_logv(cx, prep, obj, filter, dropped, _fun, format, ap); |
1148 | 0 | va_end(ap); |
1149 | 0 | } |
1150 | | #endif |
1151 | | |
1152 | | void |
1153 | | lws_set_log_level(int flags, lws_log_emit_t func) |
1154 | 0 | { |
1155 | 0 | log_cx.lll_flags = (uint32_t)(flags & (~LLLF_LOG_CONTEXT_AWARE)); |
1156 | |
|
1157 | 0 | if (func) |
1158 | 0 | log_cx.u.emit = func; |
1159 | 0 | } |
1160 | | |
1161 | | int lwsl_visible(int level) |
1162 | 0 | { |
1163 | 0 | return !!(log_cx.lll_flags & (uint32_t)level); |
1164 | 0 | } |
1165 | | |
1166 | | int lwsl_visible_cx(lws_log_cx_t *cx, int level) |
1167 | 0 | { |
1168 | 0 | return !!(cx->lll_flags & (uint32_t)level); |
1169 | 0 | } |
1170 | | |
1171 | | void |
1172 | | lwsl_refcount_cx(lws_log_cx_t *cx, int _new) |
1173 | 0 | { |
1174 | | #if LWS_MAX_SMP > 1 |
1175 | | volatile lws_log_cx_t *vcx = (volatile lws_log_cx_t *)cx; |
1176 | | #endif |
1177 | |
|
1178 | 0 | if (!cx) |
1179 | 0 | return; |
1180 | | |
1181 | | #if LWS_MAX_SMP > 1 |
1182 | | if (!vcx->inited) { |
1183 | | vcx->inited = 1; |
1184 | | lws_pthread_mutex_init(&cx->refcount_lock); |
1185 | | vcx->inited = 2; |
1186 | | } |
1187 | | while (vcx->inited != 2) |
1188 | | ; |
1189 | | lws_pthread_mutex_lock(&cx->refcount_lock); |
1190 | | #endif |
1191 | | |
1192 | 0 | if (_new > 0) |
1193 | 0 | cx->refcount++; |
1194 | 0 | else { |
1195 | 0 | assert(cx->refcount); |
1196 | 0 | cx->refcount--; |
1197 | 0 | } |
1198 | |
|
1199 | 0 | if (cx->refcount_cb) |
1200 | 0 | cx->refcount_cb(cx, _new); |
1201 | |
|
1202 | | #if LWS_MAX_SMP > 1 |
1203 | | lws_pthread_mutex_unlock(&cx->refcount_lock); |
1204 | | #endif |
1205 | 0 | } |
1206 | | |
1207 | | void |
1208 | | lwsl_hexdump_level_cx(lws_log_cx_t *cx, lws_log_prepend_cx_t prep, void *obj, |
1209 | | int hexdump_level, const void *vbuf, size_t len) |
1210 | 0 | { |
1211 | 0 | unsigned char *buf = (unsigned char *)vbuf; |
1212 | 0 | unsigned int n; |
1213 | |
|
1214 | 0 | if (!lwsl_visible_cx(cx, hexdump_level)) |
1215 | 0 | return; |
1216 | | |
1217 | 0 | if (!len) { |
1218 | 0 | _lws_log_cx(cx, prep, obj, hexdump_level, NULL, |
1219 | 0 | "(hexdump: zero length)\n"); |
1220 | 0 | return; |
1221 | 0 | } |
1222 | | |
1223 | 0 | if (!vbuf) { |
1224 | 0 | _lws_log_cx(cx, prep, obj, hexdump_level, NULL, |
1225 | 0 | "(hexdump: NULL ptr)\n"); |
1226 | 0 | return; |
1227 | 0 | } |
1228 | | |
1229 | 0 | _lws_log_cx(cx, prep, obj, hexdump_level, NULL, "\n"); |
1230 | |
|
1231 | 0 | for (n = 0; n < len;) { |
1232 | 0 | unsigned int start = n, m; |
1233 | 0 | char line[80], *p = line; |
1234 | |
|
1235 | 0 | p += lws_snprintf(p, 10, "%04X: ", start); |
1236 | |
|
1237 | 0 | for (m = 0; m < 16 && n < len; m++) |
1238 | 0 | p += lws_snprintf(p, 5, "%02X ", buf[n++]); |
1239 | 0 | while (m++ < 16) |
1240 | 0 | p += lws_snprintf(p, 5, " "); |
1241 | |
|
1242 | 0 | p += lws_snprintf(p, 6, " "); |
1243 | |
|
1244 | 0 | for (m = 0; m < 16 && (start + m) < len; m++) { |
1245 | 0 | if (buf[start + m] >= ' ' && buf[start + m] < 127) |
1246 | 0 | *p++ = (char)buf[start + m]; |
1247 | 0 | else |
1248 | 0 | *p++ = '.'; |
1249 | 0 | } |
1250 | 0 | while (m++ < 16) |
1251 | 0 | *p++ = ' '; |
1252 | |
|
1253 | 0 | *p++ = '\n'; |
1254 | 0 | *p = '\0'; |
1255 | 0 | _lws_log_cx(cx, prep, obj, hexdump_level, NULL, "%s", line); |
1256 | 0 | (void)line; |
1257 | 0 | } |
1258 | |
|
1259 | 0 | _lws_log_cx(cx, prep, obj, hexdump_level, NULL, "\n"); |
1260 | 0 | } |
1261 | | |
1262 | | void |
1263 | | lwsl_hexdump_level(int hexdump_level, const void *vbuf, size_t len) |
1264 | 0 | { |
1265 | 0 | lwsl_hexdump_level_cx(&log_cx, NULL, NULL, hexdump_level, vbuf, len); |
1266 | 0 | } |
1267 | | |
1268 | | void |
1269 | | lwsl_hexdump(const void *vbuf, size_t len) |
1270 | 0 | { |
1271 | 0 | #if defined(_DEBUG) |
1272 | 0 | lwsl_hexdump_level(LLL_DEBUG, vbuf, len); |
1273 | 0 | #endif |
1274 | 0 | } |
1275 | | |
1276 | | /* |
1277 | | * A context's log lines: its log context, and its name to prefix them with |
1278 | | */ |
1279 | | |
1280 | | void |
1281 | | lws_log_prepend_context(struct lws_log_cx *cx, void *obj, char **p, char *e) |
1282 | 0 | { |
1283 | 0 | struct lws_context *lcx = (struct lws_context *)obj; |
1284 | |
|
1285 | 0 | if (lcx->name) |
1286 | 0 | *p += lws_snprintf(*p, lws_ptr_diff_size_t(e, (*p)), "%s: ", |
1287 | 0 | lcx->name); |
1288 | 0 | } |
1289 | | |
1290 | | struct lws_log_cx * |
1291 | | lwsl_context_get_cx(struct lws_context *cx) |
1292 | 0 | { |
1293 | 0 | if (!cx) |
1294 | 0 | return NULL; |
1295 | | |
1296 | 0 | return cx->log_cx; |
1297 | 0 | } |