From beb0fb75663091c20765a8508162711ba828f456 Mon Sep 17 00:00:00 2001 From: Philippe Proulx Date: Mon, 8 May 2017 13:04:03 -0400 Subject: [PATCH] Add logging API (internal to log, public to set the current log level) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit See doc/logging-guide.adoc which explains the whole logging API, how to set and initialize the log levels, how to write logging statements, etc. Signed-off-by: Philippe Proulx Signed-off-by: Jérémie Galarneau --- Makefile.am | 3 +- configure.ac | 12 + doc/Makefile.am | 7 +- doc/api/Doxyfile.in | 1 + doc/logging-guide.adoc | 685 ++++++++++ include/Makefile.am | 69 +- include/babeltrace/lib-logging-internal.h | 35 + include/babeltrace/logging-internal.h | 960 ++++++++++++++ include/babeltrace/logging.h | 152 +++ lib/Makefile.am | 3 +- lib/logging.c | 71 ++ logging/LICENSE | 21 + logging/Makefile.am | 5 + logging/log.c | 1376 +++++++++++++++++++++ 14 files changed, 3364 insertions(+), 36 deletions(-) create mode 100644 doc/logging-guide.adoc create mode 100644 include/babeltrace/lib-logging-internal.h create mode 100644 include/babeltrace/logging-internal.h create mode 100644 include/babeltrace/logging.h create mode 100644 lib/logging.c create mode 100644 logging/LICENSE create mode 100644 logging/Makefile.am create mode 100644 logging/log.c diff --git a/Makefile.am b/Makefile.am index 6e90e481..63613f88 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,7 +5,8 @@ ACLOCAL_AMFLAGS = -I m4 SUBDIRS = \ include \ common \ - compat + compat \ + logging if WITH_PYTHON_PLUGINS SUBDIRS += python-plugin-provider diff --git a/configure.ac b/configure.ac index b12fe3d2..b31f7acc 100644 --- a/configure.ac +++ b/configure.ac @@ -400,6 +400,14 @@ AM_CONDITIONAL([BUILT_IN_PYTHON_PLUGIN_SUPPORT], [test "x$built_in_python_plugin PKG_CHECK_MODULES(GMODULE, [gmodule-2.0 >= 2.0.0]) +# Logging +AC_ARG_VAR([BABELTRACE_MINIMAL_LOG_LEVEL], [Minimal log level for Babeltrace program, library, and plugins (VERBOSE, DEBUG, INFO, WARN, ERROR (default), FATAL, or NONE)]) +AS_IF([test "x$BABELTRACE_MINIMAL_LOG_LEVEL" = "x"], [BABELTRACE_MINIMAL_LOG_LEVEL=DEBUG]) +AS_IF([test "$BABELTRACE_MINIMAL_LOG_LEVEL" != "VERBOSE" && test "$BABELTRACE_MINIMAL_LOG_LEVEL" != "DEBUG" && test "$BABELTRACE_MINIMAL_LOG_LEVEL" != "INFO" && test "$BABELTRACE_MINIMAL_LOG_LEVEL" != "WARN" && test "$BABELTRACE_MINIMAL_LOG_LEVEL" != "ERROR" && test "$BABELTRACE_MINIMAL_LOG_LEVEL" != "FATAL" && test "$BABELTRACE_MINIMAL_LOG_LEVEL" != "NONE"], [ + AC_MSG_ERROR([Invalid BABELTRACE_MINIMAL_LOG_LEVEL value ($BABELTRACE_MINIMAL_LOG_LEVEL): use VERBOSE, DEBUG, INFO, WARN, ERROR, FATAL, or NONE.]) +]) +AC_DEFINE_UNQUOTED([BT_LOG_LEVEL], [BT_LOG_$BABELTRACE_MINIMAL_LOG_LEVEL], [Minimal log level]) + LIBS="$LIBS $GMODULE_LIBS" PACKAGE_CFLAGS="$GMODULE_CFLAGS -Wall -Wformat" AC_SUBST(PACKAGE_CFLAGS) @@ -473,6 +481,7 @@ AC_CONFIG_FILES([ lib/ctf-ir/Makefile lib/ctf-writer/Makefile include/Makefile + logging/Makefile bindings/Makefile bindings/python/Makefile bindings/python/babeltrace/Makefile @@ -566,6 +575,9 @@ done ] PPRINT_PROP_STRING([Target architecture], $target_arch) +# Minimal log level +PPRINT_PROP_STRING([Minimal log level], $BABELTRACE_MINIMAL_LOG_LEVEL) + # API doc test "x$enable_api_doc" = "xyes" && value=1 || value=0 PPRINT_PROP_BOOL([HTML API documentation], $value) diff --git a/doc/Makefile.am b/doc/Makefile.am index 6ee9c233..274f9b8b 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -6,6 +6,11 @@ endif dist_man_MANS = babeltrace.1 babeltrace-log.1 -dist_doc_DATA = API.txt lttng-live.txt ref-counting.md +dist_doc_DATA = API.txt lttng-live.txt ref-counting.md logging-guide.adoc EXTRA_DIST = development.txt + +logging-guide.html: logging-guide.adoc + asciidoc --verbose -a source-highlighter=pygments logging-guide.adoc + +CLEANFILES = logging-guide.html diff --git a/doc/api/Doxyfile.in b/doc/api/Doxyfile.in index 7cff9c79..adb2a564 100644 --- a/doc/api/Doxyfile.in +++ b/doc/api/Doxyfile.in @@ -146,6 +146,7 @@ INPUT = "@top_srcdir@/include/babeltrace/ctf-ir" \ "@top_srcdir@/include/babeltrace/plugin" \ "@top_srcdir@/include/babeltrace/ref.h" \ "@top_srcdir@/include/babeltrace/values.h" \ + "@top_srcdir@/include/babeltrace/logging.h" \ "@srcdir@/dox/main-page.dox" \ "@srcdir@/dox/includes-build.dox" \ "@srcdir@/dox/write-plugin.dox" \ diff --git a/doc/logging-guide.adoc b/doc/logging-guide.adoc new file mode 100644 index 00000000..921188e6 --- /dev/null +++ b/doc/logging-guide.adoc @@ -0,0 +1,685 @@ += Babeltrace logging guide +Philippe Proulx +7 May 2017 +:toc: +:toclevels: 5 + +This guide explains to the Babeltrace developer how to insert logging +statements in Babeltrace's CLI, library, and plugins. + + +== Motive + +Logging is a great instrument for a developer to be able to collect +information about a running software. + +Babeltrace is a complex software with many layers. When a Babeltrace +graph fails to run, what caused the failure? It could be caused by any +component, any notification iterator, and any deeply nested validation +of a CTR IR object, for example. With the appropriate logging statements +manually placed in the source code, we can find the cause of a bug +faster. + +While <> when placing _INFO_ to _FATAL_ +logging statements, you should deliberately instrument your Babeltrace +module with _DEBUG_ and _VERBOSE_ logging statements to help future +you and other developers understand what's happening at run-time. + + +== API + +The Babeltrace logging API is internal: it is not exposed to the users +of the library, only to their developers. The only thing that a library +user can control is the current log level of the library with +`bt_logging_set_global_level()` and the initial library's log level with +the `BABELTRACE_LOGGING_GLOBAL_LEVEL` environment variable. + +This API is based on https://github.com/wonder-mice/zf_log[zf_log], a +lightweight, yet featureful, MIT-licensed core logging library for C and +$$C++$$. The zf_log source files were modified to have the `BT_` and +`bt_` prefixes, and other small changes. + +The logging functions are implemented in the logging convenience +library (`logging` directory). + + +=== Headers + +The logging API headers are: + +``:: + Public header which a library user can use to control and read + libbabeltrace's current log level. + +``:: + Internal, generic logging API which you can use in any Babeltrace + subproject. This is the translation of `zf_log.h`. + +``:: + Specific internal header to use within the library. This header + defines `BT_LOG_OUTPUT_LEVEL` to a custom, library-wide hidden + symbol which is the library's current log level before including + ``. + +Do not include `` or +`` in a header which contains logging +statements: this header could be included in source files which define a +different <>, for example. + + +=== Log levels === + +The API offers the following log levels: + +* _VERBOSE_ +* _DEBUG_ +* _INFO_ +* _WARN_ +* _ERROR_ +* _FATAL_ + +See <> below. + +There are two important log level variables: + +[[build-time-log-level]]Build-time, minimal log level:: + The minimal log level, or build-time log level, is set at build + time and determines the minimal log level which can be executed. + This applies to all the subprojects and modules (CLI, library, + plugins, etc.). ++ +All the logging statements with a level below this level are **not built +at all**. All the logging statements with a level equal to or greater +than this level _can_ be executed, depending on the run-time log level +(see below). ++ +You can set this level at configuration time with the +`BABELTRACE_MINIMAL_LOG_LEVEL` environment variable, for example: ++ +-- +---- +$ BABELTRACE_MINIMAL_LOG_LEVEL=WARN ./configure +---- +-- ++ +The default build-time log level is `DEBUG`. For optimal performance, +set it to `NONE`, which effectively disables all logging in all the +Babeltrace subprojects. ++ +The library's public API provides `bt_logging_get_minimal_level()` to +get the configured minimal log level. + +[[run-time-log-level]]Run-time, dynamic log level:: + The dynamic log level is set at run-time and determines the current, + active log level. All the logging statements with a level below this + level are not executed, but they evaluate the condition. All the + logging statements with a level equal to or greater than this level + are executed, provided that their level is also enabled at build + time (see above). ++ +In `zf_log`, there is a concept of a global run-time log level which +uses the `_bt_log_global_output_lvl` symbol. In practice, we never use +this symbol, and always make sure that `BT_LOG_OUTPUT_LEVEL` is defined +to a module-wise or subproject-wise hidden symbol before including +``. In the library, +`` does this job: just include +this header which defines `BT_LOG_OUTPUT_LEVEL` to the appropriate +symbol before it includes ``. In plugins, +for example, there is one log level per component class, which makes +log filtering easier during execution. ++ +In libbabeltrace, the user can set the current run-time log level with +the `bt_logging_set_global_level()` function, for example: ++ +-- +[source,c] +---- +bt_logging_set_global_level(BT_LOGGING_LEVEL_INFO); +---- +-- ++ +The library's initial run-time log level is defined by the +`BABELTRACE_LOGGING_GLOBAL_LEVEL` environment variable (`VERBOSE`, `DEBUG`, +`INFO`, `WARN`, `ERROR`, `FATAL`, or `NONE`), or set to _NONE_ if this +environment variable is undefined. ++ +Other subprojects have their own way of setting their run-time log +level. For example, the CLI uses the `BABELTRACE_CLI_LOG_LEVEL` +environment variable, and the `text.pretty` sink component class +initializes its log level thanks to the +`BABELTRACE_PLUGIN_TEXT_PRETTY_SINK_LOG_LEVEL` environment variable +(also _NONE_ by default). ++ +Make sure that there is a documented way to initialize or modify the +log level of your subproject or module, and that it's set to _NONE_ +by default. + + +=== Logging statement macros + +The Babeltrace logging statement macros work just like `printf()` and +contain their log level in their name: + +`BT_LOGV("format string", ...)`:: + Standard verbose logging statement. + +`BT_LOGD("format string", ...)`:: + Standard debug logging statement. + +`BT_LOGI("format string", ...)`:: + Standard info logging statement. + +`BT_LOGW("format string", ...)`:: + Standard warning logging statement. + +`BT_LOGE("format string", ...)`:: + Standard error logging statement. + +`BT_LOGF("format string", ...)`:: + Standard fatal logging statement. + +`BT_LOGV_MEM(data_ptr, data_size, "format string", ...)`:: + Memory verbose logging statement. + +`BT_LOGD_MEM(data_ptr, data_size, "format string", ...)`:: + Memory debug logging statement. + +`BT_LOGI_MEM(data_ptr, data_size, "format string", ...)`:: + Memory info logging statement. + +`BT_LOGW_MEM(data_ptr, data_size, "format string", ...)`:: + Memory warning logging statement. + +`BT_LOGE_MEM(data_ptr, data_size, "format string", ...)`:: + Memory error logging statement. + +`BT_LOGF_MEM(data_ptr, data_size, "format string", ...)`:: + Memory fatal logging statement. + +`BT_LOGV_STR("preformatted string")`:: + Preformatted string verbose logging statement. + +`BT_LOGD_STR("preformatted string")`:: + Preformatted string debug logging statement. + +`BT_LOGI_STR("preformatted string")`:: + Preformatted string info logging statement. + +`BT_LOGW_STR("preformatted string")`:: + Preformatted string warning logging statement. + +`BT_LOGE_STR("preformatted string")`:: + Preformatted string error logging statement. + +`BT_LOGF_STR("preformatted string")`:: + Preformatted string fatal logging statement. + + +=== Conditional logging + +`BT_LOG_IF(cond, statement)`:: + Execute `statement` only if `cond` is true. ++ +Example: ++ +-- +[source,c] +---- +BT_LOG_IF(i < count / 2, BT_LOGD("Log this: i=%d", i)); +---- +-- + +To check the <>: + +[source,c] +---- +#if BT_LOG_ENABLED_DEBUG +... +#endif +---- + +This tests if the _DEBUG_ level was enabled at build-time. This +means that the current, dynamic log level _could_ be _DEBUG_, but it +could also be higher. The rule of thumb is to use only logging +statements at the same level in a `BT_LOG_ENABLED_*` conditional block. + +The available definitions for build-time conditions are: + +* `BT_LOG_ENABLED_VERBOSE` +* `BT_LOG_ENABLED_DEBUG` +* `BT_LOG_ENABLED_INFO` +* `BT_LOG_ENABLED_WARN` +* `BT_LOG_ENABLED_ERROR` +* `BT_LOG_ENABLED_FATAL` + +To check the current, <>: + +[source,c] +---- +if (BT_LOG_ON_DEBUG) { + ... +} +---- + +This tests if the _DEBUG_ log level is dynamically turned on +(implies that it's also enabled at build-time). This check could have a +noticeable impact on performance. + +The available definitions for run-time conditions are: + +* `BT_LOG_ON_VERBOSE` +* `BT_LOG_ON_DEBUG` +* `BT_LOG_ON_INFO` +* `BT_LOG_ON_WARN` +* `BT_LOG_ON_ERROR` +* `BT_LOG_ON_FATAL` + +Those macros check the subproject-specific or module-specific log level +symbol (defined by `BT_LOG_OUTPUT_LEVEL`). + +Never, ever write code which would be executed only to compute the +fields of a logging statement outside a conditional logging scope, +for example: + +[source,c] +---- +int number = get_number_of_event_classes_with_property_x(...); +BT_LOGD("Bla bla: number=%d", number); +---- + +Do this instead: + +[source,c] +---- +if (BT_LOG_ON_DEBUG) { + int number = get_number_of_event_classes_with_property_x(...); + BT_LOGD("Bla bla: number=%d", number); +} +---- + +Or even this: + +[source,c] +---- +BT_LOGD("Bla bla: number=%d", get_number_of_event_classes_with_property_x(...)); +---- + + +[[tag]] +== Tag + +Before including `` (or +``) in your C source file, define +`BT_LOG_TAG` to a name which represents your module. The tag name _must_ +be only uppercase letters/digits and the hyphen (`-`) character. + +For example: + +[source,c] +---- +#define BT_LOG_TAG "EVENT-CLASS" +#include +---- + +A tag is conceptually similar to a logger name. + + +=== Babeltrace tags + +==== CTF IR (library) + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Attributes |`ATTRS` +|Clock class and values |`CLOCK-CLASS` +|Event class |`EVENT-CLASS` +|Event |`EVENT` +|Field path |`FIELD-PATH` +|Field types |`FIELD-TYPES` +|Fields |`FIELDS` +|Packet |`PACKET` +|Resolver |`RESOLVE` +|Stream class |`STREAM-CLASS` +|Stream |`STREAM` +|Trace |`TRACE` +|Validation |`VALIDATION` +|Visitor |`VISITOR` +|=== + + +==== CTF writer (library) + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Clock |`CTF-WRITER-CLOCK` +|CTF writer |`CTF-WRITER` +|Serialization |`CTF-WRITER-SER` +|=== + + +==== Graph (library) + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Clock class priority map |`CC-PRIO-MAP` +|Component (common) |`COMP` +|Component class |`COMP-CLASS` +|Connection |`CONNECTION` +|Filter component |`COMP-FILTER` +|Graph |`GRAPH` +|Notification iterator |`NOTIF-ITER` +|Port |`PORT` +|Sink component |`COMP-SINK` +|Source component |`COMP-SOURCE` +|=== + +==== Notifications (library) + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Event notification |`NOTIF-EVENT` +|Inacitivity notification |`NOTIF-INACTIVITY` +|Notification |`NOTIF` +|Packet notification |`NOTIF-PACKET` +|Stream notification |`NOTIF-STREAM` +|=== + + +==== Plugin (library) + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Plugin |`PLUGIN` +|Python plugin provider |`PLUGIN-PY` +|Shared object plugin provider |`PLUGIN-SO` +|=== + + +==== Values (library) + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Values |`VALUES` +|=== + + +==== Reference counting (library) + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Reference counting |`REF` +|=== + + +==== Common (library) + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Common |`COMMON` +|=== + + +==== CLI + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|CLI (main) |`CLI` +|CLI configuration (common) |`CLI-CFG` +|CLI configuration from CLI arguments |`CLI-CFG-ARGS` +|CLI connection configuration from CLI arguments |`CLI-CFG-ARGS-CONNECT` +|=== + + +==== libctfcopytrace (plugin convenience library) + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Clock fields |`LIBCTFCOPYTRACE-CLOCK-FIELDS` +|libctfcopytrace |`LIBCTFCOPYTRACE` +|=== + + +==== `ctf` plugin + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Plugin (main) |`PLUGIN-CTF` +|Common: BTR |`PLUGIN-CTF-BTR` +|Common: CTF IR generation metadata visitor |`PLUGIN-CTF-METADATA-IR-VISITOR` +|Common: Metadata decoder |`PLUGIN-CTF-METADATA-DECODER` +|Common: Metadata lexer |`PLUGIN-CTF-METADATA-LEXER` +|Common: Metadata parser |`PLUGIN-CTF-METADATA-PARSER` +|Common: Notification iterator |`PLUGIN-CTF-NOTIF-ITER` +|`fs` sink (main) |`PLUGIN-CTF-FS-SINK` +|`fs` sink: write |`PLUGIN-CTF-FS-SINK-WRITE` +|`fs` source (main) |`PLUGIN-CTF-FS-SRC` +|`fs` source: data stream |`PLUGIN-CTF-FS-SRC-DS` +|`fs` source: file |`PLUGIN-CTF-FS-SRC-FILE` +|`fs` source: metadata |`PLUGIN-CTF-FS-SRC-METADATA` +|`lttng-live` source (main) |`PLUGIN-CTF-LTTNG-LIVE` +|`lttng-live` source: data stream |`PLUGIN-CTF-LTTNG-LIVE-DS` +|`lttng-live` source: metadata |`PLUGIN-CTF-LTTNG-LIVE-METADATA` +|`lttng-live` source: viewer connection |`PLUGIN-CTF-LTTNG-LIVE-VIEWER` +|=== + + +==== `lttng-utils` plugin + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Plugin (main) |`PLUGIN-LTTNG-UTILS` +|`debug-info` filter (main) |`PLUGIN-LTTNG-UTILS-DBG-INFO` +|`debug-info` filter: binary info |`PLUGIN-LTTNG-UTILS-DBG-INFO-BIN-INFO` +|`debug-info` filter: copy |`PLUGIN-LTTNG-UTILS-DBG-INFO-COPY` +|`debug-info` filter: CRC32 |`PLUGIN-LTTNG-UTILS-DBG-INFO-CRC32` +|`debug-info` filter: DWARF |`PLUGIN-LTTNG-UTILS-DBG-INFO-DWARF` +|=== + + +==== `text` plugin + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Plugin (main) |`PLUGIN-TEXT` +|`pretty` filter (main) |`PLUGIN-TEXT-PRETTY` +|`pretty` filter: print |`PLUGIN-TEXT-PRETTY-PRINT` +|=== + + +==== `utils` plugin + +[options="header,autowidth"] +|=== +|Subsystem/object |Tag name + +|Plugin (main) |`PLUGIN-UTILS` +|`dummy` sink (main) |`PLUGIN-UTILS-DUMMY` +|`muxer` filter (main) |`PLUGIN-UTILS-MUXER` +|`trimmer` filter (main) |`PLUGIN-UTILS-TRIMMER` +|`trimmer` filter: copy |`PLUGIN-UTILS-TRIMMER-COPY` +|`trimmer` filter: iterator |`PLUGIN-UTILS-TRIMMER-ITER` +|=== + + +[[level]] +== Log level + +Choosing the appropriate level for your logging statement is very +important. + +[options="header,autowidth",cols="default,default,asciidoc,default"] +|=== +|Log level |Description |Use cases |Impact on performance + +|_FATAL_ +|The program, library, or plugin cannot continue to work in this +condition: it must be terminated immediately. + +An assertion is usually an indicator of where you should put a +_FATAL_-level logging statement. In Babeltrace, however, memory +allocation errors are usually propagated back to the caller, so they +belong to the _ERROR_ log level. +| +* Unexpected return values from system calls. +|Almost none: should be executed in production. + +|_ERROR_ +|An important error which is somewhat not fatal, that is, the program, +library, or plugin can continue to work after this, but you judge that +it should be reported to the user. + +Usually, the program cannot recover from such an error, but it can at +least exit cleanly. +| +* Memory allocation errors. +* Failed to perform an operation which should work considering the + implementation and the satisfied preconditions. For example, the + failure to create an empty object (no parameters): most probably + failed internally because of an allocation error. +|Almost none: should be executed in production. + +|_WARN_ +|A logic error which still allows the execution to continue. +| +* Unexpected values for function parameters. +* Other user-induced errors (the user does not honor a function's + preconditions). ++ +For example, the caller tries to set a property of a frozen stream +class. +|Almost none: can be executed in production. + +|_INFO_ +|Any useful information which a non-developer user would understand. +| +* Successful loading of a plugin (with name, version, etc.). +* Successful connection to or disconnection from another system. +* An optional subsystem cannot be loaded. +|Very little: can be executed in production if +_INFO_ level information is desired. + +|_DEBUG_ +|Something that only Babeltrace developers would be interested into. +| +* High-level function entry/exit. +* Object creation, destruction, copying, and freezing. +* The result of some computation/validation. +|Noticeable, but not as much as the _VERBOSE_ level: not executed in +production. + +|_VERBOSE_ +|Low-level debugging context information. More appropriate for tracing +in general. +| +* Reference count change. +* Status of each iteration of a loop. +* State machine's state change. +* Data structure lookup/modification. +* List of ELF sections found in a plugin. +* Get or set an object's property. +* Object comparison's intermediate results. +|Huge: not executed in production. +|=== + + +== Message + +Follow those rules when you write a logging statement's message: + +* Use an english sentence which starts with a capital letter. Start the + sentence with the appropriate verb tense depending on the context. For + example: ++ +-- +** _Creating ..._ +** _Created ..._ or _Successfully created ..._ +-- ++ +For warning and error messages, you can start the message with _Cannot_ +followed by a verb if it's appropriate. + +* Do not include the log level in the message itself. For example, + do not start the message with _Error while_ or _Warning:_. + +* Do not put newlines, tabs, or other special characters in the + message, unless you want to log a string with such characters. Note + that multiline log messages can be hard to parse, analyze, and filter, + however. + +* **If there are fields that your logging statement must record**, + follow the message with `:` followed by a space, then with the list of + fields (more about this below). If there are no fields, end the + sentence with a period. + +The statement's fields _must_ be a comma-separated list of ++__name__=__value__+ tokens. Keep +__name__+ as simple as possible +(lowercase if possible). If +__value__+ is a string, put it between +double quotes. + +Example: + + "Cannot add event class to stream class: stream-class-addr=%p, " + "stream-class-name=\"%s\", stream-class-id=%" PRId64 + "event-class-addr=%p, event-class-name=\"%s\", event-class-id=%" PRId64 + +By following a standard format for the statement fields, it is easier +to use tools like https://www.elastic.co/products/logstash[Logstash] +to split fields and analyze logs. + +Prefer the following suffixes in field names: + +[options="header,autowidth"] +|=== +|Field name suffix |Description |Format specifier + +|`-addr` |Memory address |`%p` +|`-fd` |File descriptor |`%d` +|`-fp` |File stream (`FILE *`) |`%p` +|`-id` |Object's ID |`%" PRId64 "` or `%" PRIu64 "` +|`-name` |Object's name |`\"%s\"` +|`-ref-cnt` |Object's reference count |`%u` +|=== + + +== Output + +The log is printed to the standard error stream. A log line contains the +time, the process and thread IDs, the log level, the tag, the source's +function name, file name and line number, and the message. + +Example: + + 05-11 00:58:03.691 23402 23402 D VALUES bt_value_destroy@values.c:498 Destroying value: addr=0xb9c3eb0 + +You can easily filter the log with `grep` or `ag`. For example, to +keep only the _WARN_-level log messages that the `VALUES` module +generates: + + $ export BABELTRACE_LOGGING_GLOBAL_LEVEL=VERBOSE + $ ./test_ctf_writer_complete 2>&1 | ag 'W VALUES' diff --git a/include/Makefile.am b/include/Makefile.am index ad1817ce..58147820 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1,7 +1,8 @@ babeltraceinclude_HEADERS = \ babeltrace/babeltrace.h \ babeltrace/values.h \ - babeltrace/ref.h + babeltrace/ref.h \ + babeltrace/logging.h babeltracectfinclude_HEADERS = \ babeltrace/ctf/events.h @@ -35,7 +36,6 @@ babeltraceplugininclude_HEADERS = \ babeltracegraphinclude_HEADERS = \ babeltrace/graph/clock-class-priority-map.h \ - babeltrace/graph/clock-class-priority-map-internal.h \ babeltrace/graph/component-class-filter.h \ babeltrace/graph/component-class-sink.h \ babeltrace/graph/component-class-source.h \ @@ -69,57 +69,60 @@ noinst_HEADERS = \ babeltrace/babeltrace-internal.h \ babeltrace/bitfield-internal.h \ babeltrace/common-internal.h \ + babeltrace/compat/dirent-internal.h \ + babeltrace/compat/fcntl-internal.h \ + babeltrace/compat/glib-internal.h \ + babeltrace/compat/limits-internal.h \ + babeltrace/compat/memstream-internal.h \ + babeltrace/compat/mman-internal.h \ + babeltrace/compat/send-internal.h \ + babeltrace/compat/stdio-internal.h \ + babeltrace/compat/stdlib-internal.h \ + babeltrace/compat/string-internal.h \ + babeltrace/compat/utc-internal.h \ + babeltrace/compat/uuid-internal.h \ babeltrace/compiler-internal.h \ - babeltrace/prio-heap-internal.h \ - babeltrace/ref-internal.h \ - babeltrace/object-internal.h \ - babeltrace/ctf-writer/writer-internal.h \ - babeltrace/ctf-writer/serialize-internal.h \ babeltrace/ctf-ir/attributes-internal.h \ - babeltrace/ctf-ir/field-types-internal.h \ - babeltrace/ctf-ir/fields-internal.h \ - babeltrace/ctf-ir/event-internal.h \ + babeltrace/ctf-ir/clock-class-internal.h \ babeltrace/ctf-ir/event-class-internal.h \ + babeltrace/ctf-ir/event-internal.h \ babeltrace/ctf-ir/field-path-internal.h \ - babeltrace/ctf-ir/clock-class-internal.h \ + babeltrace/ctf-ir/field-types-internal.h \ + babeltrace/ctf-ir/fields-internal.h \ + babeltrace/ctf-ir/packet-internal.h \ babeltrace/ctf-ir/resolve-internal.h \ babeltrace/ctf-ir/stream-class-internal.h \ babeltrace/ctf-ir/stream-internal.h \ - babeltrace/ctf-ir/packet-internal.h \ babeltrace/ctf-ir/trace-internal.h \ babeltrace/ctf-ir/validation-internal.h \ babeltrace/ctf-ir/visitor-internal.h \ babeltrace/ctf-writer/clock-internal.h \ babeltrace/ctf-writer/functor-internal.h \ - babeltrace/compat/uuid-internal.h \ - babeltrace/compat/memstream-internal.h \ - babeltrace/compat/string-internal.h \ - babeltrace/compat/utc-internal.h \ - babeltrace/compat/limits-internal.h \ - babeltrace/compat/glib-internal.h \ - babeltrace/compat/send-internal.h \ - babeltrace/compat/fcntl-internal.h \ - babeltrace/compat/stdlib-internal.h \ - babeltrace/compat/dirent-internal.h \ - babeltrace/compat/stdio-internal.h \ - babeltrace/compat/mman-internal.h \ + babeltrace/ctf-writer/serialize-internal.h \ + babeltrace/ctf-writer/writer-internal.h \ babeltrace/endian-internal.h \ - babeltrace/mmap-align-internal.h \ - babeltrace/plugin/plugin-internal.h \ - babeltrace/plugin/plugin-so-internal.h \ + babeltrace/graph/clock-class-priority-map-internal.h \ babeltrace/graph/component-class-internal.h \ - babeltrace/graph/connection-internal.h \ - babeltrace/graph/port-internal.h \ - babeltrace/graph/component-internal.h \ - babeltrace/graph/graph-internal.h \ babeltrace/graph/component-filter-internal.h \ + babeltrace/graph/component-internal.h \ babeltrace/graph/component-sink-internal.h \ babeltrace/graph/component-source-internal.h \ + babeltrace/graph/connection-internal.h \ + babeltrace/graph/graph-internal.h \ babeltrace/graph/notification-eot-internal.h \ babeltrace/graph/notification-event-internal.h \ + babeltrace/graph/notification-heap-internal.h \ babeltrace/graph/notification-inactivity-internal.h \ - babeltrace/graph/notification-iterator-internal.h \ babeltrace/graph/notification-internal.h \ + babeltrace/graph/notification-iterator-internal.h \ babeltrace/graph/notification-packet-internal.h \ babeltrace/graph/notification-stream-internal.h \ - babeltrace/graph/notification-heap-internal.h + babeltrace/graph/port-internal.h \ + babeltrace/lib-logging-internal.h \ + babeltrace/logging-internal.h \ + babeltrace/mmap-align-internal.h \ + babeltrace/object-internal.h \ + babeltrace/plugin/plugin-internal.h \ + babeltrace/plugin/plugin-so-internal.h \ + babeltrace/prio-heap-internal.h \ + babeltrace/ref-internal.h diff --git a/include/babeltrace/lib-logging-internal.h b/include/babeltrace/lib-logging-internal.h new file mode 100644 index 00000000..a692cd55 --- /dev/null +++ b/include/babeltrace/lib-logging-internal.h @@ -0,0 +1,35 @@ +#ifndef BABELTRACE_LIB_LOGGING_INTERNAL_H +#define BABELTRACE_LIB_LOGGING_INTERNAL_H + +/* + * Copyright 2017 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include + +#define BT_LOG_OUTPUT_LEVEL bt_lib_log_level + +#include + +BT_HIDDEN +int bt_lib_log_level; + +#endif /* BABELTRACE_LIB_LOGGING_INTERNAL_H */ diff --git a/include/babeltrace/logging-internal.h b/include/babeltrace/logging-internal.h new file mode 100644 index 00000000..5cad0c32 --- /dev/null +++ b/include/babeltrace/logging-internal.h @@ -0,0 +1,960 @@ +/* + * This is zf_log.h, modified with Babeltrace prefixes. + * See . + * See logging/LICENSE in the Babeltrace source tree. + */ + +#pragma once + +#ifndef BABELTRACE_LOGGING_INTERNAL_H +#define BABELTRACE_LOGGING_INTERNAL_H + +#include + +/* To detect incompatible changes you can define BT_LOG_VERSION_REQUIRED to be + * the current value of BT_LOG_VERSION before including this file (or via + * compiler command line): + * + * #define BT_LOG_VERSION_REQUIRED 4 + * #include + * + * Compilation will fail when included file has different version. + */ +#define BT_LOG_VERSION 4 +#if defined(BT_LOG_VERSION_REQUIRED) + #if BT_LOG_VERSION_REQUIRED != BT_LOG_VERSION + #error different bt_log version required + #endif +#endif + +/* Log level guideline: + * - BT_LOG_FATAL - happened something impossible and absolutely unexpected. + * Process can't continue and must be terminated. + * Example: division by zero, unexpected modifications from other thread. + * - BT_LOG_ERROR - happened something possible, but highly unexpected. The + * process is able to recover and continue execution. + * Example: out of memory (could also be FATAL if not handled properly). + * - BT_LOG_WARN - happened something that *usually* should not happen and + * significantly changes application behavior for some period of time. + * Example: configuration file not found, auth error. + * - BT_LOG_INFO - happened significant life cycle event or major state + * transition. + * Example: app started, user logged in. + * - BT_LOG_DEBUG - minimal set of events that could help to reconstruct the + * execution path. Usually disabled in release builds. + * - BT_LOG_VERBOSE - all other events. Usually disabled in release builds. + * + * *Ideally*, log file of debugged, well tested, production ready application + * should be empty or very small. Choosing a right log level is as important as + * providing short and self descriptive log message. + */ +#define BT_LOG_VERBOSE BT_LOGGING_LEVEL_VERBOSE +#define BT_LOG_DEBUG BT_LOGGING_LEVEL_DEBUG +#define BT_LOG_INFO BT_LOGGING_LEVEL_INFO +#define BT_LOG_WARN BT_LOGGING_LEVEL_WARN +#define BT_LOG_ERROR BT_LOGGING_LEVEL_ERROR +#define BT_LOG_FATAL BT_LOGGING_LEVEL_FATAL +#define BT_LOG_NONE BT_LOGGING_LEVEL_NONE + +/* "Current" log level is a compile time check and has no runtime overhead. Log + * level that is below current log level it said to be "disabled". Otherwise, + * it's "enabled". Log messages that are disabled has no runtime overhead - they + * are converted to no-op by preprocessor and then eliminated by compiler. + * Current log level is configured per compilation module (.c/.cpp/.m file) by + * defining BT_LOG_DEF_LEVEL or BT_LOG_LEVEL. BT_LOG_LEVEL has higer priority + * and when defined overrides value provided by BT_LOG_DEF_LEVEL. + * + * Common practice is to define default current log level with BT_LOG_DEF_LEVEL + * in build script (e.g. Makefile, CMakeLists.txt, gyp, etc.) for the entire + * project or target: + * + * CC_ARGS := -DBT_LOG_DEF_LEVEL=BT_LOG_INFO + * + * And when necessary to override it with BT_LOG_LEVEL in .c/.cpp/.m files + * before including bt_log.h: + * + * #define BT_LOG_LEVEL BT_LOG_VERBOSE + * #include + * + * If both BT_LOG_DEF_LEVEL and BT_LOG_LEVEL are undefined, then BT_LOG_INFO + * will be used for release builds (NDEBUG is defined) and BT_LOG_DEBUG + * otherwise (NDEBUG is not defined). + */ +#if defined(BT_LOG_LEVEL) + #define _BT_LOG_LEVEL BT_LOG_LEVEL +#elif defined(BT_LOG_DEF_LEVEL) + #define _BT_LOG_LEVEL BT_LOG_DEF_LEVEL +#else + #ifdef NDEBUG + #define _BT_LOG_LEVEL BT_LOG_INFO + #else + #define _BT_LOG_LEVEL BT_LOG_DEBUG + #endif +#endif + +/* "Output" log level is a runtime check. When log level is below output log + * level it said to be "turned off" (or just "off" for short). Otherwise it's + * "turned on" (or just "on"). Log levels that were "disabled" (see + * BT_LOG_LEVEL and BT_LOG_DEF_LEVEL) can't be "turned on", but "enabled" log + * levels could be "turned off". Only messages with log level which is + * "turned on" will reach output facility. All other messages will be ignored + * (and their arguments will not be evaluated). Output log level is a global + * property and configured per process using bt_log_set_output_level() function + * which can be called at any time. + * + * Though in some cases it could be useful to configure output log level per + * compilation module or per library. There are two ways to achieve that: + * - Define BT_LOG_OUTPUT_LEVEL to expresion that evaluates to desired output + * log level. + * - Copy bt_log.h and bt_log.c files into your library and build it with + * BT_LOG_LIBRARY_PREFIX defined to library specific prefix. See + * BT_LOG_LIBRARY_PREFIX for more details. + * + * When defined, BT_LOG_OUTPUT_LEVEL must evaluate to integral value that + * corresponds to desired output log level. Use it only when compilation module + * is required to have output log level which is different from global output + * log level set by bt_log_set_output_level() function. For other cases, + * consider defining BT_LOG_LEVEL or using bt_log_set_output_level() function. + * + * Example: + * + * #define BT_LOG_OUTPUT_LEVEL g_module_log_level + * #include + * static int g_module_log_level = BT_LOG_INFO; + * static void foo() { + * BT_LOGI("Will check g_module_log_level for output log level"); + * } + * void debug_log(bool on) { + * g_module_log_level = on? BT_LOG_DEBUG: BT_LOG_INFO; + * } + * + * Note on performance. This expression will be evaluated each time message is + * logged (except when message log level is "disabled" - see BT_LOG_LEVEL for + * details). Keep this expression as simple as possible, otherwise it will not + * only add runtime overhead, but also will increase size of call site (which + * will result in larger executable). The prefered way is to use integer + * variable (as in example above). If structure must be used, log_level field + * must be the first field in this structure: + * + * #define BT_LOG_OUTPUT_LEVEL (g_config.log_level) + * #include + * struct config { + * int log_level; + * unsigned other_field; + * [...] + * }; + * static config g_config = {BT_LOG_INFO, 0, ...}; + * + * This allows compiler to generate more compact load instruction (no need to + * specify offset since it's zero). Calling a function to get output log level + * is generaly a bad idea, since it will increase call site size and runtime + * overhead even further. + */ +#if defined(BT_LOG_OUTPUT_LEVEL) + #define _BT_LOG_OUTPUT_LEVEL BT_LOG_OUTPUT_LEVEL +#else + #define _BT_LOG_OUTPUT_LEVEL _bt_log_global_output_lvl +#endif + +/* "Tag" is a compound string that could be associated with a log message. It + * consists of tag prefix and tag (both are optional). + * + * Tag prefix is a global property and configured per process using + * bt_log_set_tag_prefix() function. Tag prefix identifies context in which + * component or module is running (e.g. process name). For example, the same + * library could be used in both client and server processes that work on the + * same machine. Tag prefix could be used to easily distinguish between them. + * For more details about tag prefix see bt_log_set_tag_prefix() function. Tag + * prefix + * + * Tag identifies component or module. It is configured per compilation module + * (.c/.cpp/.m file) by defining BT_LOG_TAG or BT_LOG_DEF_TAG. BT_LOG_TAG has + * higer priority and when defined overrides value provided by BT_LOG_DEF_TAG. + * When defined, value must evaluate to (const char *), so for strings double + * quotes must be used. + * + * Default tag could be defined with BT_LOG_DEF_TAG in build script (e.g. + * Makefile, CMakeLists.txt, gyp, etc.) for the entire project or target: + * + * CC_ARGS := -DBT_LOG_DEF_TAG=\"MISC\" + * + * And when necessary could be overriden with BT_LOG_TAG in .c/.cpp/.m files + * before including bt_log.h: + * + * #define BT_LOG_TAG "MAIN" + * #include + * + * If both BT_LOG_DEF_TAG and BT_LOG_TAG are undefined no tag will be added to + * the log message (tag prefix still could be added though). + * + * Output example: + * + * 04-29 22:43:20.244 40059 1299 I hello.MAIN Number of arguments: 1 + * | | + * | +- tag (e.g. module) + * +- tag prefix (e.g. process name) + */ +#if defined(BT_LOG_TAG) + #define _BT_LOG_TAG BT_LOG_TAG +#elif defined(BT_LOG_DEF_TAG) + #define _BT_LOG_TAG BT_LOG_DEF_TAG +#else + #define _BT_LOG_TAG 0 +#endif + +/* Source location is part of a log line that describes location (function or + * method name, file name and line number, e.g. "runloop@main.cpp:68") of a + * log statement that produced it. + * Source location formats are: + * - BT_LOG_SRCLOC_NONE - don't add source location to log line. + * - BT_LOG_SRCLOC_SHORT - add source location in short form (file and line + * number, e.g. "@main.cpp:68"). + * - BT_LOG_SRCLOC_LONG - add source location in long form (function or method + * name, file and line number, e.g. "runloop@main.cpp:68"). + */ +#define BT_LOG_SRCLOC_NONE 0 +#define BT_LOG_SRCLOC_SHORT 1 +#define BT_LOG_SRCLOC_LONG 2 + +/* Source location format is configured per compilation module (.c/.cpp/.m + * file) by defining BT_LOG_DEF_SRCLOC or BT_LOG_SRCLOC. BT_LOG_SRCLOC has + * higer priority and when defined overrides value provided by + * BT_LOG_DEF_SRCLOC. + * + * Common practice is to define default format with BT_LOG_DEF_SRCLOC in + * build script (e.g. Makefile, CMakeLists.txt, gyp, etc.) for the entire + * project or target: + * + * CC_ARGS := -DBT_LOG_DEF_SRCLOC=BT_LOG_SRCLOC_LONG + * + * And when necessary to override it with BT_LOG_SRCLOC in .c/.cpp/.m files + * before including bt_log.h: + * + * #define BT_LOG_SRCLOC BT_LOG_SRCLOC_NONE + * #include + * + * If both BT_LOG_DEF_SRCLOC and BT_LOG_SRCLOC are undefined, then + * BT_LOG_SRCLOC_NONE will be used for release builds (NDEBUG is defined) and + * BT_LOG_SRCLOC_LONG otherwise (NDEBUG is not defined). + */ +#if defined(BT_LOG_SRCLOC) + #define _BT_LOG_SRCLOC BT_LOG_SRCLOC +#elif defined(BT_LOG_DEF_SRCLOC) + #define _BT_LOG_SRCLOC BT_LOG_DEF_SRCLOC +#else + #ifdef NDEBUG + #define _BT_LOG_SRCLOC BT_LOG_SRCLOC_NONE + #else + #define _BT_LOG_SRCLOC BT_LOG_SRCLOC_LONG + #endif +#endif +#if BT_LOG_SRCLOC_LONG == _BT_LOG_SRCLOC + #define _BT_LOG_SRCLOC_FUNCTION _BT_LOG_FUNCTION +#else + #define _BT_LOG_SRCLOC_FUNCTION 0 +#endif + +/* Censoring provides conditional logging of secret information, also known as + * Personally Identifiable Information (PII) or Sensitive Personal Information + * (SPI). Censoring can be either enabled (BT_LOG_CENSORED) or disabled + * (BT_LOG_UNCENSORED). When censoring is enabled, log statements marked as + * "secrets" will be ignored and will have zero overhead (arguments also will + * not be evaluated). + */ +#define BT_LOG_CENSORED 1 +#define BT_LOG_UNCENSORED 0 + +/* Censoring is configured per compilation module (.c/.cpp/.m file) by defining + * BT_LOG_DEF_CENSORING or BT_LOG_CENSORING. BT_LOG_CENSORING has higer priority + * and when defined overrides value provided by BT_LOG_DEF_CENSORING. + * + * Common practice is to define default censoring with BT_LOG_DEF_CENSORING in + * build script (e.g. Makefile, CMakeLists.txt, gyp, etc.) for the entire + * project or target: + * + * CC_ARGS := -DBT_LOG_DEF_CENSORING=BT_LOG_CENSORED + * + * And when necessary to override it with BT_LOG_CENSORING in .c/.cpp/.m files + * before including bt_log.h (consider doing it only for debug purposes and be + * very careful not to push such temporary changes to source control): + * + * #define BT_LOG_CENSORING BT_LOG_UNCENSORED + * #include + * + * If both BT_LOG_DEF_CENSORING and BT_LOG_CENSORING are undefined, then + * BT_LOG_CENSORED will be used for release builds (NDEBUG is defined) and + * BT_LOG_UNCENSORED otherwise (NDEBUG is not defined). + */ +#if defined(BT_LOG_CENSORING) + #define _BT_LOG_CENSORING BT_LOG_CENSORING +#elif defined(BT_LOG_DEF_CENSORING) + #define _BT_LOG_CENSORING BT_LOG_DEF_CENSORING +#else + #ifdef NDEBUG + #define _BT_LOG_CENSORING BT_LOG_CENSORED + #else + #define _BT_LOG_CENSORING BT_LOG_UNCENSORED + #endif +#endif + +/* Check censoring at compile time. Evaluates to true when censoring is disabled + * (i.e. when secrets will be logged). For example: + * + * #if BT_LOG_SECRETS + * char ssn[16]; + * getSocialSecurityNumber(ssn); + * BT_LOGI("Customer ssn: %s", ssn); + * #endif + * + * See BT_LOG_SECRET() macro for a more convenient way of guarding single log + * statement. + */ +#define BT_LOG_SECRETS (BT_LOG_UNCENSORED == _BT_LOG_CENSORING) + +/* Static (compile-time) initialization support allows to configure logging + * before entering main() function. This mostly useful in C++ where functions + * and methods could be called during initialization of global objects. Those + * functions and methods could record log messages too and for that reason + * static initialization of logging configuration is customizable. + * + * Macros below allow to specify values to use for initial configuration: + * - BT_LOG_EXTERN_TAG_PREFIX - tag prefix (default: none) + * - BT_LOG_EXTERN_GLOBAL_FORMAT - global format options (default: see + * BT_LOG_MEM_WIDTH in bt_log.c) + * - BT_LOG_EXTERN_GLOBAL_OUTPUT - global output facility (default: stderr or + * platform specific, see BT_LOG_USE_XXX macros in bt_log.c) + * - BT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL - global output log level (default: 0 - + * all levals are "turned on") + * + * For example, in log_config.c: + * + * #include + * BT_LOG_DEFINE_TAG_PREFIX = "MyApp"; + * BT_LOG_DEFINE_GLOBAL_FORMAT = {CUSTOM_MEM_WIDTH}; + * BT_LOG_DEFINE_GLOBAL_OUTPUT = {BT_LOG_PUT_STD, custom_output_callback, 0}; + * BT_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL = BT_LOG_INFO; + * + * However, to use any of those macros bt_log library must be compiled with + * following macros defined: + * - to use BT_LOG_DEFINE_TAG_PREFIX define BT_LOG_EXTERN_TAG_PREFIX + * - to use BT_LOG_DEFINE_GLOBAL_FORMAT define BT_LOG_EXTERN_GLOBAL_FORMAT + * - to use BT_LOG_DEFINE_GLOBAL_OUTPUT define BT_LOG_EXTERN_GLOBAL_OUTPUT + * - to use BT_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL define + * BT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL + * + * When bt_log library compiled with one of BT_LOG_EXTERN_XXX macros defined, + * corresponding BT_LOG_DEFINE_XXX macro MUST be used exactly once somewhere. + * Otherwise build will fail with link error (undefined symbol). + */ +#define BT_LOG_DEFINE_TAG_PREFIX const char *_bt_log_tag_prefix +#define BT_LOG_DEFINE_GLOBAL_FORMAT bt_log_format _bt_log_global_format +#define BT_LOG_DEFINE_GLOBAL_OUTPUT bt_log_output _bt_log_global_output +#define BT_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL int _bt_log_global_output_lvl + +/* Pointer to global format options. Direct modification is not allowed. Use + * bt_log_set_mem_width() instead. Could be used to initialize bt_log_spec + * structure: + * + * const bt_log_output g_output = {BT_LOG_PUT_STD, output_callback, 0}; + * const bt_log_spec g_spec = {BT_LOG_GLOBAL_FORMAT, &g_output}; + * BT_LOGI_AUX(&g_spec, "Hello"); + */ +#define BT_LOG_GLOBAL_FORMAT ((const bt_log_format *)&_bt_log_global_format) + +/* Pointer to global output variable. Direct modification is not allowed. Use + * bt_log_set_output_v() or bt_log_set_output_p() instead. Could be used to + * initialize bt_log_spec structure: + * + * const bt_log_format g_format = {40}; + * const bt_log_spec g_spec = {g_format, BT_LOG_GLOBAL_OUTPUT}; + * BT_LOGI_AUX(&g_spec, "Hello"); + */ +#define BT_LOG_GLOBAL_OUTPUT ((const bt_log_output *)&_bt_log_global_output) + +/* When defined, all library symbols produced by linker will be prefixed with + * provided value. That allows to use bt_log library privately in another + * libraries without exposing bt_log symbols in their original form (to avoid + * possible conflicts with other libraries / components that also could use + * bt_log for logging). Value must be without quotes, for example: + * + * CC_ARGS := -DBT_LOG_LIBRARY_PREFIX=my_lib_ + * + * Note, that in this mode BT_LOG_LIBRARY_PREFIX must be defined when building + * bt_log library AND it also must be defined to the same value when building + * a library that uses it. For example, consider fictional KittyHttp library + * that wants to use bt_log for logging. First approach that could be taken is + * to add bt_log.h and bt_log.c to the KittyHttp's source code tree directly. + * In that case it will be enough just to define BT_LOG_LIBRARY_PREFIX in + * KittyHttp's build script: + * + * // KittyHttp/CMakeLists.txt + * target_compile_definitions(KittyHttp PRIVATE + * "BT_LOG_LIBRARY_PREFIX=KittyHttp_") + * + * If KittyHttp doesn't want to include bt_log source code in its source tree + * and wants to build bt_log as a separate library than bt_log library must be + * built with BT_LOG_LIBRARY_PREFIX defined to KittyHttp_ AND KittyHttp library + * itself also needs to define BT_LOG_LIBRARY_PREFIX to KittyHttp_. It can do + * so either in its build script, as in example above, or by providing a + * wrapper header that KittyHttp library will need to use instead of bt_log.h: + * + * // KittyHttpLogging.h + * #define BT_LOG_LIBRARY_PREFIX KittyHttp_ + * #include + * + * Regardless of the method chosen, the end result is that bt_log symbols will + * be prefixed with "KittyHttp_", so if a user of KittyHttp (say DogeBrowser) + * also uses bt_log for logging, they will not interferer with each other. Both + * will have their own log level, output facility, format options etc. + */ +#ifdef BT_LOG_LIBRARY_PREFIX + #define _BT_LOG_DECOR__(prefix, name) prefix ## name + #define _BT_LOG_DECOR_(prefix, name) _BT_LOG_DECOR__(prefix, name) + #define _BT_LOG_DECOR(name) _BT_LOG_DECOR_(BT_LOG_LIBRARY_PREFIX, name) + + #define bt_log_set_tag_prefix _BT_LOG_DECOR(bt_log_set_tag_prefix) + #define bt_log_set_mem_width _BT_LOG_DECOR(bt_log_set_mem_width) + #define bt_log_set_output_level _BT_LOG_DECOR(bt_log_set_output_level) + #define bt_log_set_output_v _BT_LOG_DECOR(bt_log_set_output_v) + #define bt_log_set_output_p _BT_LOG_DECOR(bt_log_set_output_p) + #define bt_log_out_stderr_callback _BT_LOG_DECOR(bt_log_out_stderr_callback) + #define _bt_log_tag_prefix _BT_LOG_DECOR(_bt_log_tag_prefix) + #define _bt_log_global_format _BT_LOG_DECOR(_bt_log_global_format) + #define _bt_log_global_output _BT_LOG_DECOR(_bt_log_global_output) + #define _bt_log_global_output_lvl _BT_LOG_DECOR(_bt_log_global_output_lvl) + #define _bt_log_write_d _BT_LOG_DECOR(_bt_log_write_d) + #define _bt_log_write_aux_d _BT_LOG_DECOR(_bt_log_write_aux_d) + #define _bt_log_write _BT_LOG_DECOR(_bt_log_write) + #define _bt_log_write_aux _BT_LOG_DECOR(_bt_log_write_aux) + #define _bt_log_write_mem_d _BT_LOG_DECOR(_bt_log_write_mem_d) + #define _bt_log_write_mem_aux_d _BT_LOG_DECOR(_bt_log_write_mem_aux_d) + #define _bt_log_write_mem _BT_LOG_DECOR(_bt_log_write_mem) + #define _bt_log_write_mem_aux _BT_LOG_DECOR(_bt_log_write_mem_aux) + #define _bt_log_stderr_spec _BT_LOG_DECOR(_bt_log_stderr_spec) +#endif + +#if defined(__printflike) + #define _BT_LOG_PRINTFLIKE(a, b) __printflike(a, b) +#else + #define _BT_LOG_PRINTFLIKE(a, b) +#endif + +#if (defined(_WIN32) || defined(_WIN64)) && !defined(__GNUC__) + #define _BT_LOG_FUNCTION __FUNCTION__ +#else + #define _BT_LOG_FUNCTION __func__ +#endif + +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) + #define _BT_LOG_INLINE __inline + #define _BT_LOG_IF(cond) \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) \ + if(cond) \ + __pragma(warning(pop)) + #define _BT_LOG_WHILE(cond) \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) \ + while(cond) \ + __pragma(warning(pop)) +#else + #define _BT_LOG_INLINE inline + #define _BT_LOG_IF(cond) if(cond) + #define _BT_LOG_WHILE(cond) while(cond) +#endif +#define _BT_LOG_NEVER _BT_LOG_IF(0) +#define _BT_LOG_ONCE _BT_LOG_WHILE(0) + +#ifdef __cplusplus +extern "C" { +#endif + +/* Set tag prefix. Prefix will be separated from the tag with dot ('.'). + * Use 0 or empty string to disable (default). Common use is to set it to + * the process (or build target) name (e.g. to separate client and server + * processes). Function will NOT copy provided prefix string, but will store the + * pointer. Hence specified prefix string must remain valid. See + * BT_LOG_DEFINE_TAG_PREFIX for a way to set it before entering main() function. + * See BT_LOG_TAG for more information about tag and tag prefix. + */ +void bt_log_set_tag_prefix(const char *const prefix); + +/* Set number of bytes per log line in memory (ASCII-HEX) output. Example: + * + * I hello.MAIN 4c6f72656d20697073756d20646f6c6f Lorem ipsum dolo + * |<- w bytes ->| |<- w chars ->| + * + * See BT_LOGF_MEM and BT_LOGF_MEM_AUX for more details. + */ +void bt_log_set_mem_width(const unsigned w); + +/* Set "output" log level. See BT_LOG_LEVEL and BT_LOG_OUTPUT_LEVEL for more + * info about log levels. + */ +void bt_log_set_output_level(const int lvl); + +/* Put mask is a set of flags that define what fields will be added to each + * log message. Default value is BT_LOG_PUT_STD and other flags could be used to + * alter its behavior. See bt_log_set_output_v() for more details. + * + * Note about BT_LOG_PUT_SRC: it will be added only in debug builds (NDEBUG is + * not defined). + */ +enum +{ + BT_LOG_PUT_CTX = 1 << 0, /* context (time, pid, tid, log level) */ + BT_LOG_PUT_TAG = 1 << 1, /* tag (including tag prefix) */ + BT_LOG_PUT_SRC = 1 << 2, /* source location (file, line, function) */ + BT_LOG_PUT_MSG = 1 << 3, /* message text (formatted string) */ + BT_LOG_PUT_STD = 0xffff, /* everything (default) */ +}; + +typedef struct bt_log_message +{ + int lvl; /* Log level of the message */ + const char *tag; /* Associated tag (without tag prefix) */ + char *buf; /* Buffer start */ + char *e; /* Buffer end (last position where EOL with 0 could be written) */ + char *p; /* Buffer content end (append position) */ + char *tag_b; /* Prefixed tag start */ + char *tag_e; /* Prefixed tag end (if != tag_b, points to msg separator) */ + char *msg_b; /* Message start (expanded format string) */ +} +bt_log_message; + +/* Type of output callback function. It will be called for each log line allowed + * by both "current" and "output" log levels ("enabled" and "turned on"). + * Callback function is allowed to modify content of the buffers pointed by the + * msg, but it's not allowed to modify any of msg fields. Buffer pointed by msg + * is UTF-8 encoded (no BOM mark). + */ +typedef void (*bt_log_output_cb)(const bt_log_message *msg, void *arg); + +/* Format options. For more details see bt_log_set_mem_width(). + */ +typedef struct bt_log_format +{ + unsigned mem_width; /* Bytes per line in memory (ASCII-HEX) dump */ +} +bt_log_format; + +/* Output facility. + */ +typedef struct bt_log_output +{ + unsigned mask; /* What to put into log line buffer (see BT_LOG_PUT_XXX) */ + void *arg; /* User provided output callback argument */ + bt_log_output_cb callback; /* Output callback function */ +} +bt_log_output; + +/* Set output callback function. + * + * Mask allows to control what information will be added to the log line buffer + * before callback function is invoked. Default mask value is BT_LOG_PUT_STD. + */ +void bt_log_set_output_v(const unsigned mask, void *const arg, + const bt_log_output_cb callback); +static _BT_LOG_INLINE void bt_log_set_output_p(const bt_log_output *const output) +{ + bt_log_set_output_v(output->mask, output->arg, output->callback); +} + +/* Used with _AUX macros and allows to override global format and output + * facility. Use BT_LOG_GLOBAL_FORMAT and BT_LOG_GLOBAL_OUTPUT for values from + * global configuration. Example: + * + * static const bt_log_output module_output = { + * BT_LOG_PUT_STD, 0, custom_output_callback + * }; + * static const bt_log_spec module_spec = { + * BT_LOG_GLOBAL_FORMAT, &module_output + * }; + * BT_LOGI_AUX(&module_spec, "Position: %ix%i", x, y); + * + * See BT_LOGF_AUX and BT_LOGF_MEM_AUX for details. + */ +typedef struct bt_log_spec +{ + const bt_log_format *format; + const bt_log_output *output; +} +bt_log_spec; + +#ifdef __cplusplus +} +#endif + +/* Execute log statement if condition is true. Example: + * + * BT_LOG_IF(1 < 2, BT_LOGI("Log this")); + * BT_LOG_IF(1 > 2, BT_LOGI("Don't log this")); + * + * Keep in mind though, that if condition can't be evaluated at compile time, + * then it will be evaluated at run time. This will increase exectuable size + * and can have noticeable performance overhead. Try to limit conditions to + * expressions that can be evaluated at compile time. + */ +#define BT_LOG_IF(cond, f) do { _BT_LOG_IF((cond)) { f; } } _BT_LOG_ONCE + +/* Mark log statement as "secret". Log statements that are marked as secrets + * will NOT be executed when censoring is enabled (see BT_LOG_CENSORED). + * Example: + * + * BT_LOG_SECRET(BT_LOGI("Credit card: %s", credit_card)); + * BT_LOG_SECRET(BT_LOGD_MEM(cipher, cipher_sz, "Cipher bytes:")); + */ +#define BT_LOG_SECRET(f) BT_LOG_IF(BT_LOG_SECRETS, f) + +/* Check "current" log level at compile time (ignoring "output" log level). + * Evaluates to true when specified log level is enabled. For example: + * + * #if BT_LOG_ENABLED_DEBUG + * const char *const g_enum_strings[] = { + * "enum_value_0", "enum_value_1", "enum_value_2" + * }; + * #endif + * // ... + * #if BT_LOG_ENABLED_DEBUG + * BT_LOGD("enum value: %s", g_enum_strings[v]); + * #endif + * + * See BT_LOG_LEVEL for details. + */ +#define BT_LOG_ENABLED(lvl) ((lvl) >= _BT_LOG_LEVEL) +#define BT_LOG_ENABLED_VERBOSE BT_LOG_ENABLED(BT_LOG_VERBOSE) +#define BT_LOG_ENABLED_DEBUG BT_LOG_ENABLED(BT_LOG_DEBUG) +#define BT_LOG_ENABLED_INFO BT_LOG_ENABLED(BT_LOG_INFO) +#define BT_LOG_ENABLED_WARN BT_LOG_ENABLED(BT_LOG_WARN) +#define BT_LOG_ENABLED_ERROR BT_LOG_ENABLED(BT_LOG_ERROR) +#define BT_LOG_ENABLED_FATAL BT_LOG_ENABLED(BT_LOG_FATAL) + +/* Check "output" log level at run time (taking into account "current" log + * level as well). Evaluates to true when specified log level is turned on AND + * enabled. For example: + * + * if (BT_LOG_ON_DEBUG) + * { + * char hash[65]; + * sha256(data_ptr, data_sz, hash); + * BT_LOGD("data: len=%u, sha256=%s", data_sz, hash); + * } + * + * See BT_LOG_OUTPUT_LEVEL for details. + */ +#define BT_LOG_ON(lvl) \ + (BT_LOG_ENABLED((lvl)) && (lvl) >= _BT_LOG_OUTPUT_LEVEL) +#define BT_LOG_ON_VERBOSE BT_LOG_ON(BT_LOG_VERBOSE) +#define BT_LOG_ON_DEBUG BT_LOG_ON(BT_LOG_DEBUG) +#define BT_LOG_ON_INFO BT_LOG_ON(BT_LOG_INFO) +#define BT_LOG_ON_WARN BT_LOG_ON(BT_LOG_WARN) +#define BT_LOG_ON_ERROR BT_LOG_ON(BT_LOG_ERROR) +#define BT_LOG_ON_FATAL BT_LOG_ON(BT_LOG_FATAL) + +#ifdef __cplusplus +extern "C" { +#endif + +extern const char *_bt_log_tag_prefix; +extern bt_log_format _bt_log_global_format; +extern bt_log_output _bt_log_global_output; +extern int _bt_log_global_output_lvl; +extern const bt_log_spec _bt_log_stderr_spec; + +BT_HIDDEN +void _bt_log_write_d( + const char *const func, const char *const file, const unsigned line, + const int lvl, const char *const tag, + const char *const fmt, ...) _BT_LOG_PRINTFLIKE(6, 7); + +BT_HIDDEN +void _bt_log_write_aux_d( + const char *const func, const char *const file, const unsigned line, + const bt_log_spec *const log, const int lvl, const char *const tag, + const char *const fmt, ...) _BT_LOG_PRINTFLIKE(7, 8); + +BT_HIDDEN +void _bt_log_write( + const int lvl, const char *const tag, + const char *const fmt, ...) _BT_LOG_PRINTFLIKE(3, 4); + +BT_HIDDEN +void _bt_log_write_aux( + const bt_log_spec *const log, const int lvl, const char *const tag, + const char *const fmt, ...) _BT_LOG_PRINTFLIKE(4, 5); + +BT_HIDDEN +void _bt_log_write_mem_d( + const char *const func, const char *const file, const unsigned line, + const int lvl, const char *const tag, + const void *const d, const unsigned d_sz, + const char *const fmt, ...) _BT_LOG_PRINTFLIKE(8, 9); + +BT_HIDDEN +void _bt_log_write_mem_aux_d( + const char *const func, const char *const file, const unsigned line, + const bt_log_spec *const log, const int lvl, const char *const tag, + const void *const d, const unsigned d_sz, + const char *const fmt, ...) _BT_LOG_PRINTFLIKE(9, 10); + +BT_HIDDEN +void _bt_log_write_mem( + const int lvl, const char *const tag, + const void *const d, const unsigned d_sz, + const char *const fmt, ...) _BT_LOG_PRINTFLIKE(5, 6); + +BT_HIDDEN +void _bt_log_write_mem_aux( + const bt_log_spec *const log, const int lvl, const char *const tag, + const void *const d, const unsigned d_sz, + const char *const fmt, ...) _BT_LOG_PRINTFLIKE(6, 7); + +#ifdef __cplusplus +} +#endif + +/* Message logging macros: + * - BT_LOGV("format string", args, ...) + * - BT_LOGD("format string", args, ...) + * - BT_LOGI("format string", args, ...) + * - BT_LOGW("format string", args, ...) + * - BT_LOGE("format string", args, ...) + * - BT_LOGF("format string", args, ...) + * + * Memory logging macros: + * - BT_LOGV_MEM(data_ptr, data_sz, "format string", args, ...) + * - BT_LOGD_MEM(data_ptr, data_sz, "format string", args, ...) + * - BT_LOGI_MEM(data_ptr, data_sz, "format string", args, ...) + * - BT_LOGW_MEM(data_ptr, data_sz, "format string", args, ...) + * - BT_LOGE_MEM(data_ptr, data_sz, "format string", args, ...) + * - BT_LOGF_MEM(data_ptr, data_sz, "format string", args, ...) + * + * Auxiliary logging macros: + * - BT_LOGV_AUX(&log_instance, "format string", args, ...) + * - BT_LOGD_AUX(&log_instance, "format string", args, ...) + * - BT_LOGI_AUX(&log_instance, "format string", args, ...) + * - BT_LOGW_AUX(&log_instance, "format string", args, ...) + * - BT_LOGE_AUX(&log_instance, "format string", args, ...) + * - BT_LOGF_AUX(&log_instance, "format string", args, ...) + * + * Auxiliary memory logging macros: + * - BT_LOGV_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...) + * - BT_LOGD_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...) + * - BT_LOGI_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...) + * - BT_LOGW_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...) + * - BT_LOGE_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...) + * - BT_LOGF_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...) + * + * Preformatted string logging macros: + * - BT_LOGV_STR("preformatted string"); + * - BT_LOGD_STR("preformatted string"); + * - BT_LOGI_STR("preformatted string"); + * - BT_LOGW_STR("preformatted string"); + * - BT_LOGE_STR("preformatted string"); + * - BT_LOGF_STR("preformatted string"); + * + * Explicit log level and tag macros: + * - BT_LOG_WRITE(level, tag, "format string", args, ...) + * - BT_LOG_WRITE_MEM(level, tag, data_ptr, data_sz, "format string", args, ...) + * - BT_LOG_WRITE_AUX(&log_instance, level, tag, "format string", args, ...) + * - BT_LOG_WRITE_MEM_AUX(&log_instance, level, tag, data_ptr, data_sz, + * "format string", args, ...) + * + * Format string follows printf() conventions. Both data_ptr and data_sz could + * be 0. Tag can be 0 as well. Most compilers will verify that type of arguments + * match format specifiers in format string. + * + * Library assuming UTF-8 encoding for all strings (char *), including format + * string itself. + */ +#if BT_LOG_SRCLOC_NONE == _BT_LOG_SRCLOC + #define BT_LOG_WRITE(lvl, tag, ...) \ + do { \ + if (BT_LOG_ON(lvl)) \ + _bt_log_write(lvl, tag, __VA_ARGS__); \ + } _BT_LOG_ONCE + #define BT_LOG_WRITE_MEM(lvl, tag, d, d_sz, ...) \ + do { \ + if (BT_LOG_ON(lvl)) \ + _bt_log_write_mem(lvl, tag, d, d_sz, __VA_ARGS__); \ + } _BT_LOG_ONCE + #define BT_LOG_WRITE_AUX(log, lvl, tag, ...) \ + do { \ + if (BT_LOG_ON(lvl)) \ + _bt_log_write_aux(log, lvl, tag, __VA_ARGS__); \ + } _BT_LOG_ONCE + #define BT_LOG_WRITE_MEM_AUX(log, lvl, tag, d, d_sz, ...) \ + do { \ + if (BT_LOG_ON(lvl)) \ + _bt_log_write_mem_aux(log, lvl, tag, d, d_sz, __VA_ARGS__); \ + } _BT_LOG_ONCE +#else + #define BT_LOG_WRITE(lvl, tag, ...) \ + do { \ + if (BT_LOG_ON(lvl)) \ + _bt_log_write_d(_BT_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \ + lvl, tag, __VA_ARGS__); \ + } _BT_LOG_ONCE + #define BT_LOG_WRITE_MEM(lvl, tag, d, d_sz, ...) \ + do { \ + if (BT_LOG_ON(lvl)) \ + _bt_log_write_mem_d(_BT_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \ + lvl, tag, d, d_sz, __VA_ARGS__); \ + } _BT_LOG_ONCE + #define BT_LOG_WRITE_AUX(log, lvl, tag, ...) \ + do { \ + if (BT_LOG_ON(lvl)) \ + _bt_log_write_aux_d(_BT_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \ + log, lvl, tag, __VA_ARGS__); \ + } _BT_LOG_ONCE + #define BT_LOG_WRITE_MEM_AUX(log, lvl, tag, d, d_sz, ...) \ + do { \ + if (BT_LOG_ON(lvl)) \ + _bt_log_write_mem_aux_d(_BT_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \ + log, lvl, tag, d, d_sz, __VA_ARGS__); \ + } _BT_LOG_ONCE +#endif + +static _BT_LOG_INLINE void _bt_log_unused(const int dummy, ...) {(void)dummy;} + +#define _BT_LOG_UNUSED(...) \ + do { _BT_LOG_NEVER _bt_log_unused(0, __VA_ARGS__); } _BT_LOG_ONCE + +#if BT_LOG_ENABLED_VERBOSE + #define BT_LOGV(...) \ + BT_LOG_WRITE(BT_LOG_VERBOSE, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGV_AUX(log, ...) \ + BT_LOG_WRITE_AUX(log, BT_LOG_VERBOSE, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGV_MEM(d, d_sz, ...) \ + BT_LOG_WRITE_MEM(BT_LOG_VERBOSE, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) + #define BT_LOGV_MEM_AUX(log, d, d_sz, ...) \ + BT_LOG_WRITE_MEM(log, BT_LOG_VERBOSE, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) +#else + #define BT_LOGV(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGV_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGV_MEM(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGV_MEM_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) +#endif + +#if BT_LOG_ENABLED_DEBUG + #define BT_LOGD(...) \ + BT_LOG_WRITE(BT_LOG_DEBUG, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGD_AUX(log, ...) \ + BT_LOG_WRITE_AUX(log, BT_LOG_DEBUG, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGD_MEM(d, d_sz, ...) \ + BT_LOG_WRITE_MEM(BT_LOG_DEBUG, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) + #define BT_LOGD_MEM_AUX(log, d, d_sz, ...) \ + BT_LOG_WRITE_MEM_AUX(log, BT_LOG_DEBUG, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) +#else + #define BT_LOGD(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGD_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGD_MEM(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGD_MEM_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) +#endif + +#if BT_LOG_ENABLED_INFO + #define BT_LOGI(...) \ + BT_LOG_WRITE(BT_LOG_INFO, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGI_AUX(log, ...) \ + BT_LOG_WRITE_AUX(log, BT_LOG_INFO, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGI_MEM(d, d_sz, ...) \ + BT_LOG_WRITE_MEM(BT_LOG_INFO, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) + #define BT_LOGI_MEM_AUX(log, d, d_sz, ...) \ + BT_LOG_WRITE_MEM_AUX(log, BT_LOG_INFO, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) +#else + #define BT_LOGI(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGI_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGI_MEM(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGI_MEM_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) +#endif + +#if BT_LOG_ENABLED_WARN + #define BT_LOGW(...) \ + BT_LOG_WRITE(BT_LOG_WARN, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGW_AUX(log, ...) \ + BT_LOG_WRITE_AUX(log, BT_LOG_WARN, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGW_MEM(d, d_sz, ...) \ + BT_LOG_WRITE_MEM(BT_LOG_WARN, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) + #define BT_LOGW_MEM_AUX(log, d, d_sz, ...) \ + BT_LOG_WRITE_MEM_AUX(log, BT_LOG_WARN, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) +#else + #define BT_LOGW(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGW_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGW_MEM(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGW_MEM_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) +#endif + +#if BT_LOG_ENABLED_ERROR + #define BT_LOGE(...) \ + BT_LOG_WRITE(BT_LOG_ERROR, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGE_AUX(log, ...) \ + BT_LOG_WRITE_AUX(log, BT_LOG_ERROR, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGE_MEM(d, d_sz, ...) \ + BT_LOG_WRITE_MEM(BT_LOG_ERROR, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) + #define BT_LOGE_MEM_AUX(log, d, d_sz, ...) \ + BT_LOG_WRITE_MEM_AUX(log, BT_LOG_ERROR, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) +#else + #define BT_LOGE(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGE_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGE_MEM(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGE_MEM_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) +#endif + +#if BT_LOG_ENABLED_FATAL + #define BT_LOGF(...) \ + BT_LOG_WRITE(BT_LOG_FATAL, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGF_AUX(log, ...) \ + BT_LOG_WRITE_AUX(log, BT_LOG_FATAL, _BT_LOG_TAG, __VA_ARGS__) + #define BT_LOGF_MEM(d, d_sz, ...) \ + BT_LOG_WRITE_MEM(BT_LOG_FATAL, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) + #define BT_LOGF_MEM_AUX(log, d, d_sz, ...) \ + BT_LOG_WRITE_MEM_AUX(log, BT_LOG_FATAL, _BT_LOG_TAG, d, d_sz, __VA_ARGS__) +#else + #define BT_LOGF(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGF_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGF_MEM(...) _BT_LOG_UNUSED(__VA_ARGS__) + #define BT_LOGF_MEM_AUX(...) _BT_LOG_UNUSED(__VA_ARGS__) +#endif + +#define BT_LOGV_STR(s) BT_LOGV("%s", (s)) +#define BT_LOGD_STR(s) BT_LOGD("%s", (s)) +#define BT_LOGI_STR(s) BT_LOGI("%s", (s)) +#define BT_LOGW_STR(s) BT_LOGW("%s", (s)) +#define BT_LOGE_STR(s) BT_LOGE("%s", (s)) +#define BT_LOGF_STR(s) BT_LOGF("%s", (s)) + +#ifdef __cplusplus +extern "C" { +#endif + +/* Output to standard error stream. Library uses it by default, though in few + * cases it could be necessary to specify it explicitly. For example, when + * bt_log library is compiled with BT_LOG_EXTERN_GLOBAL_OUTPUT, application must + * define and initialize global output variable: + * + * BT_LOG_DEFINE_GLOBAL_OUTPUT = {BT_LOG_OUT_STDERR}; + * + * Another example is when using custom output, stderr could be used as a + * fallback when custom output facility failed to initialize: + * + * bt_log_set_output_v(BT_LOG_OUT_STDERR); + */ +enum { BT_LOG_OUT_STDERR_MASK = BT_LOG_PUT_STD }; +void bt_log_out_stderr_callback(const bt_log_message *const msg, void *arg); +#define BT_LOG_OUT_STDERR BT_LOG_OUT_STDERR_MASK, 0, bt_log_out_stderr_callback + +/* Predefined spec for stderr. Uses global format options (BT_LOG_GLOBAL_FORMAT) + * and BT_LOG_OUT_STDERR. Could be used to force output to stderr for a + * particular message. Example: + * + * f = fopen("foo.log", "w"); + * if (!f) + * BT_LOGE_AUX(BT_LOG_STDERR, "Failed to open log file"); + */ +#define BT_LOG_STDERR (&_bt_log_stderr_spec) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/babeltrace/logging.h b/include/babeltrace/logging.h new file mode 100644 index 00000000..0e1d663a --- /dev/null +++ b/include/babeltrace/logging.h @@ -0,0 +1,152 @@ +#ifndef BABELTRACE_LOGGING_H +#define BABELTRACE_LOGGING_H + +/* + * Babeltrace - Logging + * + * Copyright (c) 2017 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** +@defgroup logging Logging +@ingroup apiref +@brief Logging. + +@code +#include +@endcode + +The functions in this module control the Babeltrace library's logging +behaviour. + +You can set the current global log level of the library with +bt_logging_set_global_level(). Note that, if the level you set is below +the minimal logging level (configured at build time, which you can get +with bt_logging_get_minimal_level()), the logging statement between the +current global log level and the minimal log level are not executed. + +@file +@brief Logging functions. +@sa logging + +@addtogroup logging +@{ +*/ + +/** +@brief Log levels. +*/ +enum bt_logging_level { + /// Additional, low-level debugging context information. + BT_LOGGING_LEVEL_VERBOSE = 1, + + /** + Debugging information, only useful when searching for the + cause of a bug. + */ + BT_LOGGING_LEVEL_DEBUG = 2, + + /** + Non-debugging information and failure to load optional + subsystems. + */ + BT_LOGGING_LEVEL_INFO = 3, + + /** + Errors caused by a bad usage of the library, that is, a + non-observance of the documented function preconditions. + + The library's and object's states remain consistent when a + warning is issued. + */ + BT_LOGGING_LEVEL_WARN = 4, + + /** + An important error from which the library cannot recover, but + the executed stack of functions can still return cleanly. + */ + BT_LOGGING_LEVEL_ERROR = 5, + + /** + The library cannot continue to work in this condition: it must + terminate immediately, without even returning to the user's + execution. + */ + BT_LOGGING_LEVEL_FATAL = 6, + + /// Logging is disabled. + BT_LOGGING_LEVEL_NONE = 0xff, +}; + +/** +@brief Returns the minimal log level of the Babeltrace library. + +The minimal log level is defined at the library's build time. Any +logging statement with a level below the minimal log level is not +compiled. This means that it is useless, although possible, to set the +global log level with bt_logging_set_global_level() below this level. + +@returns Minimal, build time log level. + +@sa bt_logging_get_global_level(): Returns the current global log level. +*/ +extern enum bt_logging_level bt_logging_get_minimal_level(void); + +/** +@brief Returns the current global log level of the Babeltrace library. + +@returns Current global log level. + +@sa bt_logging_set_global_level(): Sets the current global log level. +@sa bt_logging_get_minimal_level(): Returns the minimal log level. +*/ +extern enum bt_logging_level bt_logging_get_global_level(void); + +/** +@brief Sets the current global log level of the Babeltrace library + to \p log_level. + +If \p log_level is below what bt_logging_get_minimal_level() returns, +the logging statements with a level between \p log_level and the minimal +log level cannot be executed. + +@param[in] log_level Library's new global log level. + +@sa bt_logging_get_global_level(): Returns the global log level. +*/ +extern void bt_logging_set_global_level(enum bt_logging_level log_level); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* BABELTRACE_LOGGING_H */ diff --git a/lib/Makefile.am b/lib/Makefile.am index 068d858e..416d703c 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -4,7 +4,7 @@ AM_CFLAGS = $(PACKAGE_CFLAGS) -I$(top_srcdir)/include lib_LTLIBRARIES = libbabeltrace.la -libbabeltrace_la_SOURCES = babeltrace.c values.c ref.c +libbabeltrace_la_SOURCES = babeltrace.c values.c ref.c logging.c libbabeltrace_la_LDFLAGS = -version-info $(BABELTRACE_LIBRARY_VERSION) libbabeltrace_la_LIBADD = \ @@ -13,6 +13,7 @@ libbabeltrace_la_LIBADD = \ graph/libgraph.la \ plugin/libplugin.la \ $(top_builddir)/common/libbabeltrace-common.la \ + $(top_builddir)/logging/libbabeltrace-logging.la \ ctf-ir/libctf-ir.la \ ctf-writer/libctf-writer.la diff --git a/lib/logging.c b/lib/logging.c new file mode 100644 index 00000000..4272677d --- /dev/null +++ b/lib/logging.c @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +BT_HIDDEN +int bt_lib_log_level = BT_LOG_NONE; + +enum bt_logging_level bt_logging_get_minimal_level(void) +{ + return BT_LOG_LEVEL; +} + +enum bt_logging_level bt_logging_get_global_level(void) +{ + return bt_lib_log_level; +} + +void bt_logging_set_global_level(enum bt_logging_level log_level) +{ + bt_lib_log_level = log_level; +} + +static +void __attribute__((constructor)) bt_logging_ctor(void) +{ + enum bt_logging_level log_level = BT_LOG_NONE; + const char *log_level_env = getenv("BABELTRACE_LOGGING_GLOBAL_LEVEL"); + + if (!log_level_env) { + goto set_level; + } + + if (strcmp(log_level_env, "VERBOSE") == 0) { + log_level = BT_LOGGING_LEVEL_VERBOSE; + } else if (strcmp(log_level_env, "DEBUG") == 0) { + log_level = BT_LOGGING_LEVEL_DEBUG; + } else if (strcmp(log_level_env, "INFO") == 0) { + log_level = BT_LOGGING_LEVEL_INFO; + } else if (strcmp(log_level_env, "WARN") == 0) { + log_level = BT_LOGGING_LEVEL_WARN; + } else if (strcmp(log_level_env, "ERROR") == 0) { + log_level = BT_LOGGING_LEVEL_ERROR; + } else if (strcmp(log_level_env, "FATAL") == 0) { + log_level = BT_LOGGING_LEVEL_FATAL; + } + +set_level: + bt_logging_set_global_level(log_level); +} diff --git a/logging/LICENSE b/logging/LICENSE new file mode 100644 index 00000000..5569c1d5 --- /dev/null +++ b/logging/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 wonder-mice + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/logging/Makefile.am b/logging/Makefile.am new file mode 100644 index 00000000..ef9077db --- /dev/null +++ b/logging/Makefile.am @@ -0,0 +1,5 @@ +AM_CFLAGS = $(PACKAGE_CFLAGS) -I$(top_srcdir)/include + +noinst_LTLIBRARIES = libbabeltrace-logging.la + +libbabeltrace_logging_la_SOURCES = log.c diff --git a/logging/log.c b/logging/log.c new file mode 100644 index 00000000..6d09e56b --- /dev/null +++ b/logging/log.c @@ -0,0 +1,1376 @@ +/* + * This is zf_log.c, modified with Babeltrace prefixes. + * See . + * See LICENSE. + */ + +#include + +/* When defined, Android log (android/log.h) will be used by default instead of + * stderr (ignored on non-Android platforms). Date, time, pid and tid (context) + * will be provided by Android log. Android log features will be used to output + * log level and tag. + */ +#ifdef BT_LOG_USE_ANDROID_LOG + #undef BT_LOG_USE_ANDROID_LOG + #if defined(__ANDROID__) + #define BT_LOG_USE_ANDROID_LOG 1 + #else + #define BT_LOG_USE_ANDROID_LOG 0 + #endif +#else + #define BT_LOG_USE_ANDROID_LOG 0 +#endif +/* When defined, NSLog (uses Apple System Log) will be used instead of stderr + * (ignored on non-Apple platforms). Date, time, pid and tid (context) will be + * provided by NSLog. Curiously, doesn't use NSLog() directly, but piggybacks on + * non-public CFLog() function. Both use Apple System Log internally, but it's + * easier to call CFLog() from C than NSLog(). Current implementation doesn't + * support "%@" format specifier. + */ +#ifdef BT_LOG_USE_NSLOG + #undef BT_LOG_USE_NSLOG + #if defined(__APPLE__) && defined(__MACH__) + #define BT_LOG_USE_NSLOG 1 + #else + #define BT_LOG_USE_NSLOG 0 + #endif +#else + #define BT_LOG_USE_NSLOG 0 +#endif +/* When defined, OutputDebugString() will be used instead of stderr (ignored on + * non-Windows platforms). Uses OutputDebugStringA() variant and feeds it with + * UTF-8 data. + */ +#ifdef BT_LOG_USE_DEBUGSTRING + #undef BT_LOG_USE_DEBUGSTRING + #if defined(_WIN32) || defined(_WIN64) + #define BT_LOG_USE_DEBUGSTRING 1 + #else + #define BT_LOG_USE_DEBUGSTRING 0 + #endif +#else + #define BT_LOG_USE_DEBUGSTRING 0 +#endif +/* When defined, bt_log library will not contain definition of tag prefix + * variable. In that case it must be defined elsewhere using + * BT_LOG_DEFINE_TAG_PREFIX macro, for example: + * + * BT_LOG_DEFINE_TAG_PREFIX = "ProcessName"; + * + * This allows to specify custom value for static initialization and avoid + * overhead of setting this value in runtime. + */ +#ifdef BT_LOG_EXTERN_TAG_PREFIX + #undef BT_LOG_EXTERN_TAG_PREFIX + #define BT_LOG_EXTERN_TAG_PREFIX 1 +#else + #define BT_LOG_EXTERN_TAG_PREFIX 0 +#endif +/* When defined, bt_log library will not contain definition of global format + * variable. In that case it must be defined elsewhere using + * BT_LOG_DEFINE_GLOBAL_FORMAT macro, for example: + * + * BT_LOG_DEFINE_GLOBAL_FORMAT = {MEM_WIDTH}; + * + * This allows to specify custom value for static initialization and avoid + * overhead of setting this value in runtime. + */ +#ifdef BT_LOG_EXTERN_GLOBAL_FORMAT + #undef BT_LOG_EXTERN_GLOBAL_FORMAT + #define BT_LOG_EXTERN_GLOBAL_FORMAT 1 +#else + #define BT_LOG_EXTERN_GLOBAL_FORMAT 0 +#endif +/* When defined, bt_log library will not contain definition of global output + * variable. In that case it must be defined elsewhere using + * BT_LOG_DEFINE_GLOBAL_OUTPUT macro, for example: + * + * BT_LOG_DEFINE_GLOBAL_OUTPUT = {BT_LOG_PUT_STD, custom_output_callback}; + * + * This allows to specify custom value for static initialization and avoid + * overhead of setting this value in runtime. + */ +#ifdef BT_LOG_EXTERN_GLOBAL_OUTPUT + #undef BT_LOG_EXTERN_GLOBAL_OUTPUT + #define BT_LOG_EXTERN_GLOBAL_OUTPUT 1 +#else + #define BT_LOG_EXTERN_GLOBAL_OUTPUT 0 +#endif +/* When defined, bt_log library will not contain definition of global output + * level variable. In that case it must be defined elsewhere using + * BT_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL macro, for example: + * + * BT_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL = BT_LOG_WARN; + * + * This allows to specify custom value for static initialization and avoid + * overhead of setting this value in runtime. + */ +#ifdef BT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL + #undef BT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL + #define BT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL 1 +#else + #define BT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL 0 +#endif +/* When defined, implementation will prefer smaller code size over speed. + * Very rough estimate is that code will be up to 2x smaller and up to 2x + * slower. Disabled by default. + */ +#ifdef BT_LOG_OPTIMIZE_SIZE + #undef BT_LOG_OPTIMIZE_SIZE + #define BT_LOG_OPTIMIZE_SIZE 1 +#else + #define BT_LOG_OPTIMIZE_SIZE 0 +#endif +/* Size of the log line buffer. The buffer is allocated on stack. It limits + * maximum length of a log line. + */ +#ifndef BT_LOG_BUF_SZ + #define BT_LOG_BUF_SZ 512 +#endif +/* Default number of bytes in one line of memory output. For large values + * BT_LOG_BUF_SZ also must be increased. + */ +#ifndef BT_LOG_MEM_WIDTH + #define BT_LOG_MEM_WIDTH 32 +#endif +/* String to put in the end of each log line (can be empty). Its value used by + * stderr output callback. Its size used as a default value for BT_LOG_EOL_SZ. + */ +#ifndef BT_LOG_EOL + #define BT_LOG_EOL "\n" +#endif +/* Default delimiter that separates parts of log message. Can NOT contain '%' + * or '\0'. + * + * Log message format specifications can override (or ignore) this value. For + * more details see BT_LOG_MESSAGE_CTX_FORMAT, BT_LOG_MESSAGE_SRC_FORMAT and + * BT_LOG_MESSAGE_TAG_FORMAT. + */ +#ifndef BT_LOG_DEF_DELIMITER + #define BT_LOG_DEF_DELIMITER " " +#endif +/* Specifies log message context format. Log message context includes date, + * time, process id, thread id and message's log level. Custom information can + * be added as well. Supported fields: YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, + * MILLISECOND, PID, TID, LEVEL, S(str), F_INIT(statements), + * F_UINT(width, value). + * + * Must be defined as a tuple, for example: + * + * #define BT_LOG_MESSAGE_CTX_FORMAT (YEAR, S("."), MONTH, S("."), DAY, S(" > ")) + * + * In that case, resulting log message will be: + * + * 2016.12.22 > TAG function@filename.c:line Message text + * + * Note, that tag, source location and message text are not impacted by + * this setting. See BT_LOG_MESSAGE_TAG_FORMAT and BT_LOG_MESSAGE_SRC_FORMAT. + * + * If message context must be visually separated from the rest of the message, + * it must be reflected in context format (notice trailing S(" > ") in the + * example above). + * + * S(str) adds constant string str. String can NOT contain '%' or '\0'. + * + * F_INIT(statements) adds initialization statement(s) that will be evaluated + * once for each log message. All statements are evaluated in specified order. + * Several F_INIT() fields can be used in every log message format + * specification. Fields, like F_UINT(width, value), are allowed to use results + * of initialization statements. If statement introduces variables (or other + * names, like structures) they must be prefixed with "f_". Statements must be + * enclosed into additional "()". Example: + * + * #define BT_LOG_MESSAGE_CTX_FORMAT \ + * (F_INIT(( struct rusage f_ru; getrusage(RUSAGE_SELF, &f_ru); )), \ + * YEAR, S("."), MONTH, S("."), DAY, S(" "), \ + * F_UINT(5, f_ru.ru_nsignals), \ + * S(" ")) + * + * F_UINT(width, value) adds unsigned integer value extended with up to width + * spaces (for alignment purposes). Value can be any expression that evaluates + * to unsigned integer. If expression contains non-standard functions, they + * must be declared with F_INIT(). Example: + * + * #define BT_LOG_MESSAGE_CTX_FORMAT \ + * (YEAR, S("."), MONTH, S("."), DAY, S(" "), \ + * F_INIT(( unsigned tickcount(); )), \ + * F_UINT(5, tickcount()), \ + * S(" ")) + * + * Other log message format specifications follow same rules, but have a + * different set of supported fields. + */ +#ifndef BT_LOG_MESSAGE_CTX_FORMAT + #define BT_LOG_MESSAGE_CTX_FORMAT \ + (MONTH, S("-"), DAY, S(BT_LOG_DEF_DELIMITER), \ + HOUR, S(":"), MINUTE, S(":"), SECOND, S("."), MILLISECOND, S(BT_LOG_DEF_DELIMITER), \ + PID, S(BT_LOG_DEF_DELIMITER), TID, S(BT_LOG_DEF_DELIMITER), \ + LEVEL, S(BT_LOG_DEF_DELIMITER)) +#endif +/* Example: + */ +/* Specifies log message tag format. It includes tag prefix and tag. Custom + * information can be added as well. Supported fields: + * TAG(prefix_delimiter, tag_delimiter), S(str), F_INIT(statements), + * F_UINT(width, value). + * + * TAG(prefix_delimiter, tag_delimiter) adds following string to log message: + * + * PREFIXTAG + * + * Prefix delimiter will be used only when prefix is not empty. Tag delimiter + * will be used only when prefixed tag is not empty. Example: + * + * #define BT_LOG_TAG_FORMAT (S("["), TAG(".", ""), S("] ")) + * + * See BT_LOG_MESSAGE_CTX_FORMAT for details. + */ +#ifndef BT_LOG_MESSAGE_TAG_FORMAT + #define BT_LOG_MESSAGE_TAG_FORMAT \ + (TAG(".", BT_LOG_DEF_DELIMITER)) +#endif +/* Specifies log message source location format. It includes function name, + * file name and file line. Custom information can be added as well. Supported + * fields: FUNCTION, FILENAME, FILELINE, S(str), F_INIT(statements), + * F_UINT(width, value). + * + * See BT_LOG_MESSAGE_CTX_FORMAT for details. + */ +#ifndef BT_LOG_MESSAGE_SRC_FORMAT + #define BT_LOG_MESSAGE_SRC_FORMAT \ + (FUNCTION, S("@"), FILENAME, S(":"), FILELINE, S(BT_LOG_DEF_DELIMITER)) +#endif +/* Fields that can be used in log message format specifications (see above). + * Mentioning them here explicitly, so we know that nobody else defined them + * before us. See BT_LOG_MESSAGE_CTX_FORMAT for details. + */ +#define YEAR YEAR +#define MONTH MONTH +#define DAY DAY +#define MINUTE MINUTE +#define SECOND SECOND +#define MILLISECOND MILLISECOND +#define PID PID +#define TID TID +#define LEVEL LEVEL +#define TAG(prefix_delim, tag_delim) TAG(prefix_delim, tag_delim) +#define FUNCTION FUNCTION +#define FILENAME FILENAME +#define FILELINE FILELINE +#define S(str) S(str) +#define F_INIT(statements) F_INIT(statements) +#define F_UINT(width, value) F_UINT(width, value) +/* Number of bytes to reserve for EOL in the log line buffer (must be >0). + * Must be larger than or equal to length of BT_LOG_EOL with terminating null. + */ +#ifndef BT_LOG_EOL_SZ + #define BT_LOG_EOL_SZ sizeof(BT_LOG_EOL) +#endif +/* Compile instrumented version of the library to facilitate unit testing. + */ +#ifndef BT_LOG_INSTRUMENTED + #define BT_LOG_INSTRUMENTED 0 +#endif + +#if defined(__linux__) + #if !defined(__ANDROID__) && !defined(_GNU_SOURCE) + #define _GNU_SOURCE + #endif +#endif +#if defined(__MINGW32__) + #ifdef __STRICT_ANSI__ + #undef __STRICT_ANSI__ + #endif +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) + #include +#else + #include + #include + #if defined(__linux__) + #include + #else + #include + #endif +#endif + +#if defined(__linux__) + #include + #include + #if !defined(__ANDROID__) + #include + #endif +#endif +#if defined(__MACH__) + #include +#endif + +#define INLINE _BT_LOG_INLINE +#define VAR_UNUSED(var) (void)var +#define RETVAL_UNUSED(expr) do { while(expr) break; } while(0) +#define STATIC_ASSERT(name, cond) \ + typedef char assert_##name[(cond)? 1: -1] +#define ASSERT_UNREACHABLE(why) assert(!sizeof(why)) +#ifndef _countof + #define _countof(xs) (sizeof(xs) / sizeof((xs)[0])) +#endif + +#if BT_LOG_INSTRUMENTED + #define INSTRUMENTED_CONST +#else + #define INSTRUMENTED_CONST const +#endif + +#define _PP_PASTE_2(a, b) a ## b +#define _PP_CONCAT_2(a, b) _PP_PASTE_2(a, b) + +#define _PP_PASTE_3(a, b, c) a ## b ## c +#define _PP_CONCAT_3(a, b, c) _PP_PASTE_3(a, b, c) + +/* Microsoft C preprocessor is a piece of shit. This moron treats __VA_ARGS__ + * as a single token and requires additional expansion to realize that it's + * actually a list. If not for it, there would be no need in this extra + * expansion. + */ +#define _PP_ID(x) x +#define _PP_NARGS_N(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,...) _24 +#define _PP_NARGS(...) _PP_ID(_PP_NARGS_N(__VA_ARGS__,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)) + +/* There is a more efficient way to implement this, but it requires + * working C preprocessor. Unfortunately, Microsoft Visual Studio doesn't + * have one. + */ +#define _PP_HEAD__(x, ...) x +#define _PP_HEAD_(...) _PP_ID(_PP_HEAD__(__VA_ARGS__, ~)) +#define _PP_HEAD(xs) _PP_HEAD_ xs +#define _PP_TAIL_(x, ...) (__VA_ARGS__) +#define _PP_TAIL(xs) _PP_TAIL_ xs +#define _PP_UNTUPLE_(...) __VA_ARGS__ +#define _PP_UNTUPLE(xs) _PP_UNTUPLE_ xs + +/* Apply function macro to each element in tuple. Output is not + * enforced to be a tuple. + */ +#define _PP_MAP_1(f, xs) f(_PP_HEAD(xs)) +#define _PP_MAP_2(f, xs) f(_PP_HEAD(xs)) _PP_MAP_1(f, _PP_TAIL(xs)) +#define _PP_MAP_3(f, xs) f(_PP_HEAD(xs)) _PP_MAP_2(f, _PP_TAIL(xs)) +#define _PP_MAP_4(f, xs) f(_PP_HEAD(xs)) _PP_MAP_3(f, _PP_TAIL(xs)) +#define _PP_MAP_5(f, xs) f(_PP_HEAD(xs)) _PP_MAP_4(f, _PP_TAIL(xs)) +#define _PP_MAP_6(f, xs) f(_PP_HEAD(xs)) _PP_MAP_5(f, _PP_TAIL(xs)) +#define _PP_MAP_7(f, xs) f(_PP_HEAD(xs)) _PP_MAP_6(f, _PP_TAIL(xs)) +#define _PP_MAP_8(f, xs) f(_PP_HEAD(xs)) _PP_MAP_7(f, _PP_TAIL(xs)) +#define _PP_MAP_9(f, xs) f(_PP_HEAD(xs)) _PP_MAP_8(f, _PP_TAIL(xs)) +#define _PP_MAP_10(f, xs) f(_PP_HEAD(xs)) _PP_MAP_9(f, _PP_TAIL(xs)) +#define _PP_MAP_11(f, xs) f(_PP_HEAD(xs)) _PP_MAP_10(f, _PP_TAIL(xs)) +#define _PP_MAP_12(f, xs) f(_PP_HEAD(xs)) _PP_MAP_11(f, _PP_TAIL(xs)) +#define _PP_MAP_13(f, xs) f(_PP_HEAD(xs)) _PP_MAP_12(f, _PP_TAIL(xs)) +#define _PP_MAP_14(f, xs) f(_PP_HEAD(xs)) _PP_MAP_13(f, _PP_TAIL(xs)) +#define _PP_MAP_15(f, xs) f(_PP_HEAD(xs)) _PP_MAP_14(f, _PP_TAIL(xs)) +#define _PP_MAP_16(f, xs) f(_PP_HEAD(xs)) _PP_MAP_15(f, _PP_TAIL(xs)) +#define _PP_MAP_17(f, xs) f(_PP_HEAD(xs)) _PP_MAP_16(f, _PP_TAIL(xs)) +#define _PP_MAP_18(f, xs) f(_PP_HEAD(xs)) _PP_MAP_17(f, _PP_TAIL(xs)) +#define _PP_MAP_19(f, xs) f(_PP_HEAD(xs)) _PP_MAP_18(f, _PP_TAIL(xs)) +#define _PP_MAP_20(f, xs) f(_PP_HEAD(xs)) _PP_MAP_19(f, _PP_TAIL(xs)) +#define _PP_MAP_21(f, xs) f(_PP_HEAD(xs)) _PP_MAP_20(f, _PP_TAIL(xs)) +#define _PP_MAP_22(f, xs) f(_PP_HEAD(xs)) _PP_MAP_21(f, _PP_TAIL(xs)) +#define _PP_MAP_23(f, xs) f(_PP_HEAD(xs)) _PP_MAP_22(f, _PP_TAIL(xs)) +#define _PP_MAP_24(f, xs) f(_PP_HEAD(xs)) _PP_MAP_23(f, _PP_TAIL(xs)) +#define _PP_MAP(f, xs) _PP_CONCAT_2(_PP_MAP_, _PP_NARGS xs) (f, xs) + +/* Apply function macro to each element in tuple in reverse order. + * Output is not enforced to be a tuple. + */ +#define _PP_RMAP_1(f, xs) f(_PP_HEAD(xs)) +#define _PP_RMAP_2(f, xs) _PP_RMAP_1(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_3(f, xs) _PP_RMAP_2(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_4(f, xs) _PP_RMAP_3(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_5(f, xs) _PP_RMAP_4(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_6(f, xs) _PP_RMAP_5(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_7(f, xs) _PP_RMAP_6(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_8(f, xs) _PP_RMAP_7(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_9(f, xs) _PP_RMAP_8(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_10(f, xs) _PP_RMAP_9(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_11(f, xs) _PP_RMAP_10(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_12(f, xs) _PP_RMAP_11(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_13(f, xs) _PP_RMAP_12(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_14(f, xs) _PP_RMAP_13(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_15(f, xs) _PP_RMAP_14(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_16(f, xs) _PP_RMAP_15(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_17(f, xs) _PP_RMAP_16(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_18(f, xs) _PP_RMAP_17(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_19(f, xs) _PP_RMAP_18(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_20(f, xs) _PP_RMAP_19(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_21(f, xs) _PP_RMAP_20(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_22(f, xs) _PP_RMAP_21(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_23(f, xs) _PP_RMAP_22(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP_24(f, xs) _PP_RMAP_23(f, _PP_TAIL(xs)) f(_PP_HEAD(xs)) +#define _PP_RMAP(f, xs) _PP_CONCAT_2(_PP_RMAP_, _PP_NARGS xs) (f, xs) + +/* Used to implement _BT_LOG_MESSAGE_FORMAT_CONTAINS() macro. All possible + * fields must be mentioned here. Not counting F_INIT() here because it's + * somewhat special and is handled spearatly (at least for now). + */ +#define _BT_LOG_MESSAGE_FORMAT_MASK__ (0<<0) +#define _BT_LOG_MESSAGE_FORMAT_MASK__YEAR (1<<1) +#define _BT_LOG_MESSAGE_FORMAT_MASK__MONTH (1<<2) +#define _BT_LOG_MESSAGE_FORMAT_MASK__DAY (1<<3) +#define _BT_LOG_MESSAGE_FORMAT_MASK__HOUR (1<<4) +#define _BT_LOG_MESSAGE_FORMAT_MASK__MINUTE (1<<5) +#define _BT_LOG_MESSAGE_FORMAT_MASK__SECOND (1<<6) +#define _BT_LOG_MESSAGE_FORMAT_MASK__MILLISECOND (1<<7) +#define _BT_LOG_MESSAGE_FORMAT_MASK__PID (1<<8) +#define _BT_LOG_MESSAGE_FORMAT_MASK__TID (1<<9) +#define _BT_LOG_MESSAGE_FORMAT_MASK__LEVEL (1<<10) +#define _BT_LOG_MESSAGE_FORMAT_MASK__TAG(ps, ts) (1<<11) +#define _BT_LOG_MESSAGE_FORMAT_MASK__FUNCTION (1<<12) +#define _BT_LOG_MESSAGE_FORMAT_MASK__FILENAME (1<<13) +#define _BT_LOG_MESSAGE_FORMAT_MASK__FILELINE (1<<14) +#define _BT_LOG_MESSAGE_FORMAT_MASK__S(s) (1<<15) +#define _BT_LOG_MESSAGE_FORMAT_MASK__F_INIT(expr) (0<<16) +#define _BT_LOG_MESSAGE_FORMAT_MASK__F_UINT(w, v) (1<<17) +#define _BT_LOG_MESSAGE_FORMAT_MASK(field) \ + _PP_CONCAT_3(_BT_LOG_MESSAGE_FORMAT_MASK_, _, field) + +/* Logical "or" of masks of fields used in specified format specification. + */ +#define _BT_LOG_MESSAGE_FORMAT_FIELDS(format) \ + (0 _PP_MAP(| _BT_LOG_MESSAGE_FORMAT_MASK, format)) + +/* Expands to expressions that evaluates to true if field is used in + * specified format specification. Example: + * + * #if _BT_LOG_MESSAGE_FORMAT_CONTAINS(F_UINT, BT_LOG_MESSAGE_CTX_FORMAT) + * ... + * #endif + */ +#define _BT_LOG_MESSAGE_FORMAT_CONTAINS(field, format) \ + (_BT_LOG_MESSAGE_FORMAT_MASK(field) & _BT_LOG_MESSAGE_FORMAT_FIELDS(format)) + +/* Same, but checks all supported format specifications. + */ +#define _BT_LOG_MESSAGE_FORMAT_FIELD_USED(field) \ + (_BT_LOG_MESSAGE_FORMAT_CONTAINS(field, BT_LOG_MESSAGE_CTX_FORMAT) || \ + _BT_LOG_MESSAGE_FORMAT_CONTAINS(field, BT_LOG_MESSAGE_TAG_FORMAT) || \ + _BT_LOG_MESSAGE_FORMAT_CONTAINS(field, BT_LOG_MESSAGE_SRC_FORMAT)) + +#define _BT_LOG_MESSAGE_FORMAT_DATETIME_USED \ + (_BT_LOG_MESSAGE_FORMAT_CONTAINS(YEAR, BT_LOG_MESSAGE_CTX_FORMAT) || \ + _BT_LOG_MESSAGE_FORMAT_CONTAINS(MONTH, BT_LOG_MESSAGE_CTX_FORMAT) || \ + _BT_LOG_MESSAGE_FORMAT_CONTAINS(DAY, BT_LOG_MESSAGE_CTX_FORMAT) || \ + _BT_LOG_MESSAGE_FORMAT_CONTAINS(HOUR, BT_LOG_MESSAGE_CTX_FORMAT) || \ + _BT_LOG_MESSAGE_FORMAT_CONTAINS(MINUTE, BT_LOG_MESSAGE_CTX_FORMAT) || \ + _BT_LOG_MESSAGE_FORMAT_CONTAINS(SECOND, BT_LOG_MESSAGE_CTX_FORMAT) || \ + _BT_LOG_MESSAGE_FORMAT_CONTAINS(MILLISECOND, BT_LOG_MESSAGE_CTX_FORMAT)) + +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) + #pragma warning(disable:4204) /* nonstandard extension used: non-constant aggregate initializer */ + #define memccpy _memccpy +#endif + +#if (defined(_MSC_VER) && !defined(__INTEL_COMPILER)) || defined(__MINGW64__) + #define vsnprintf(s, sz, fmt, va) fake_vsnprintf(s, sz, fmt, va) + static int fake_vsnprintf(char *s, size_t sz, const char *fmt, va_list ap) + { + const int n = vsnprintf_s(s, sz, _TRUNCATE, fmt, ap); + return 0 < n? n: (int)sz + 1; /* no need in _vscprintf() for now */ + } + #if BT_LOG_OPTIMIZE_SIZE + #define snprintf(s, sz, ...) fake_snprintf(s, sz, __VA_ARGS__) + static int fake_snprintf(char *s, size_t sz, const char *fmt, ...) + { + va_list va; + va_start(va, fmt); + const int n = fake_vsnprintf(s, sz, fmt, va); + va_end(va); + return n; + } + #endif +#endif + +typedef void (*time_cb)(struct tm *const tm, unsigned *const usec); +typedef void (*pid_cb)(int *const pid, int *const tid); +typedef void (*buffer_cb)(bt_log_message *msg, char *buf); + +typedef struct src_location +{ + const char *const func; + const char *const file; + const unsigned line; +} +src_location; + +typedef struct mem_block +{ + const void *const d; + const unsigned d_sz; +} +mem_block; + +static void time_callback(struct tm *const tm, unsigned *const usec); +static void pid_callback(int *const pid, int *const tid); +static void buffer_callback(bt_log_message *msg, char *buf); + +STATIC_ASSERT(eol_fits_eol_sz, sizeof(BT_LOG_EOL) <= BT_LOG_EOL_SZ); +STATIC_ASSERT(eol_sz_greater_than_zero, 0 < BT_LOG_EOL_SZ); +STATIC_ASSERT(eol_sz_less_than_buf_sz, BT_LOG_EOL_SZ < BT_LOG_BUF_SZ); +#if !defined(_WIN32) && !defined(_WIN64) + STATIC_ASSERT(buf_sz_less_than_pipe_buf, BT_LOG_BUF_SZ <= PIPE_BUF); +#endif +static const char c_hex[] = "0123456789abcdef"; + +static INSTRUMENTED_CONST unsigned g_buf_sz = BT_LOG_BUF_SZ - BT_LOG_EOL_SZ; +static INSTRUMENTED_CONST time_cb g_time_cb = time_callback; +static INSTRUMENTED_CONST pid_cb g_pid_cb = pid_callback; +static INSTRUMENTED_CONST buffer_cb g_buffer_cb = buffer_callback; + +#if BT_LOG_USE_ANDROID_LOG + #include + + static INLINE int android_lvl(const int lvl) + { + switch (lvl) + { + case BT_LOG_VERBOSE: + return ANDROID_LOG_VERBOSE; + case BT_LOG_DEBUG: + return ANDROID_LOG_DEBUG; + case BT_LOG_INFO: + return ANDROID_LOG_INFO; + case BT_LOG_WARN: + return ANDROID_LOG_WARN; + case BT_LOG_ERROR: + return ANDROID_LOG_ERROR; + case BT_LOG_FATAL: + return ANDROID_LOG_FATAL; + default: + ASSERT_UNREACHABLE("Bad log level"); + return ANDROID_LOG_UNKNOWN; + } + } + + static void out_android_callback(const bt_log_message *const msg, void *arg) + { + VAR_UNUSED(arg); + *msg->p = 0; + const char *tag = msg->p; + if (msg->tag_e != msg->tag_b) + { + tag = msg->tag_b; + *msg->tag_e = 0; + } + __android_log_print(android_lvl(msg->lvl), tag, "%s", msg->msg_b); + } + + enum { OUT_ANDROID_MASK = BT_LOG_PUT_STD & ~BT_LOG_PUT_CTX }; + #define OUT_ANDROID OUT_ANDROID_MASK, 0, out_android_callback +#endif + +#if BT_LOG_USE_NSLOG + #include + CF_EXPORT void CFLog(int32_t level, CFStringRef format, ...); + + static INLINE int apple_lvl(const int lvl) + { + switch (lvl) + { + case BT_LOG_VERBOSE: + return 7; /* ASL_LEVEL_DEBUG / kCFLogLevelDebug */; + case BT_LOG_DEBUG: + return 7; /* ASL_LEVEL_DEBUG / kCFLogLevelDebug */; + case BT_LOG_INFO: + return 6; /* ASL_LEVEL_INFO / kCFLogLevelInfo */; + case BT_LOG_WARN: + return 4; /* ASL_LEVEL_WARNING / kCFLogLevelWarning */; + case BT_LOG_ERROR: + return 3; /* ASL_LEVEL_ERR / kCFLogLevelError */; + case BT_LOG_FATAL: + return 0; /* ASL_LEVEL_EMERG / kCFLogLevelEmergency */; + default: + ASSERT_UNREACHABLE("Bad log level"); + return 0; /* ASL_LEVEL_EMERG / kCFLogLevelEmergency */; + } + } + + static void out_nslog_callback(const bt_log_message *const msg, void *arg) + { + VAR_UNUSED(arg); + *msg->p = 0; + CFLog(apple_lvl(msg->lvl), CFSTR("%s"), msg->tag_b); + } + + enum { OUT_NSLOG_MASK = BT_LOG_PUT_STD & ~BT_LOG_PUT_CTX }; + #define OUT_NSLOG OUT_NSLOG_MASK, 0, out_nslog_callback +#endif + +#if BT_LOG_USE_DEBUGSTRING + #include + + static void out_debugstring_callback(const bt_log_message *const msg, void *arg) + { + VAR_UNUSED(arg); + msg->p[0] = '\n'; + msg->p[1] = '\0'; + OutputDebugStringA(msg->buf); + } + + enum { OUT_DEBUGSTRING_MASK = BT_LOG_PUT_STD }; + #define OUT_DEBUGSTRING OUT_DEBUGSTRING_MASK, 0, out_debugstring_callback +#endif + +BT_HIDDEN +void bt_log_out_stderr_callback(const bt_log_message *const msg, void *arg) +{ + VAR_UNUSED(arg); + const size_t eol_len = sizeof(BT_LOG_EOL) - 1; + memcpy(msg->p, BT_LOG_EOL, eol_len); +#if defined(_WIN32) || defined(_WIN64) + /* WriteFile() is atomic for local files opened with FILE_APPEND_DATA and + without FILE_WRITE_DATA */ + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg->buf, + (DWORD)(msg->p - msg->buf + eol_len), 0, 0); +#else + /* write() is atomic for buffers less than or equal to PIPE_BUF. */ + RETVAL_UNUSED(write(STDERR_FILENO, msg->buf, + (size_t)(msg->p - msg->buf) + eol_len)); +#endif +} + +static const bt_log_output out_stderr = {BT_LOG_OUT_STDERR}; + +#if !BT_LOG_EXTERN_TAG_PREFIX + BT_LOG_DEFINE_TAG_PREFIX = 0; +#endif + +#if !BT_LOG_EXTERN_GLOBAL_FORMAT + BT_LOG_DEFINE_GLOBAL_FORMAT = {BT_LOG_MEM_WIDTH}; +#endif + +#if !BT_LOG_EXTERN_GLOBAL_OUTPUT + #if BT_LOG_USE_ANDROID_LOG + BT_LOG_DEFINE_GLOBAL_OUTPUT = {OUT_ANDROID}; + #elif BT_LOG_USE_NSLOG + BT_LOG_DEFINE_GLOBAL_OUTPUT = {OUT_NSLOG}; + #elif BT_LOG_USE_DEBUGSTRING + BT_LOG_DEFINE_GLOBAL_OUTPUT = {OUT_DEBUGSTRING}; + #else + BT_LOG_DEFINE_GLOBAL_OUTPUT = {BT_LOG_OUT_STDERR}; + #endif +#endif + +#if !BT_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL + BT_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL = 0; +#endif + +const bt_log_spec _bt_log_stderr_spec = +{ + BT_LOG_GLOBAL_FORMAT, + &out_stderr, +}; + +static const bt_log_spec global_spec = +{ + BT_LOG_GLOBAL_FORMAT, + BT_LOG_GLOBAL_OUTPUT, +}; + +#if _BT_LOG_MESSAGE_FORMAT_CONTAINS(LEVEL, BT_LOG_MESSAGE_CTX_FORMAT) +static char lvl_char(const int lvl) +{ + switch (lvl) + { + case BT_LOG_VERBOSE: + return 'V'; + case BT_LOG_DEBUG: + return 'D'; + case BT_LOG_INFO: + return 'I'; + case BT_LOG_WARN: + return 'W'; + case BT_LOG_ERROR: + return 'E'; + case BT_LOG_FATAL: + return 'F'; + default: + ASSERT_UNREACHABLE("Bad log level"); + return '?'; + } +} +#endif + +#define GCCVER_LESS(MAJOR, MINOR, PATCH) \ + (__GNUC__ < MAJOR || \ + (__GNUC__ == MAJOR && (__GNUC_MINOR__ < MINOR || \ + (__GNUC_MINOR__ == MINOR && __GNUC_PATCHLEVEL__ < PATCH)))) + +#if !defined(__clang__) && defined(__GNUC__) && GCCVER_LESS(4,7,0) + #define __atomic_load_n(vp, model) __sync_fetch_and_add(vp, 0) + #define __atomic_fetch_add(vp, n, model) __sync_fetch_and_add(vp, n) + #define __atomic_sub_fetch(vp, n, model) __sync_sub_and_fetch(vp, n) + #define __atomic_or_fetch(vp, n, model) __sync_or_and_fetch(vp, n) + #define __atomic_and_fetch(vp, n, model) __sync_and_and_fetch(vp, n) + /* Note: will not store old value of *vp in *ep (non-standard behaviour) */ + #define __atomic_compare_exchange_n(vp, ep, d, weak, smodel, fmodel) \ + __sync_bool_compare_and_swap(vp, *(ep), d) +#endif + +#if !BT_LOG_OPTIMIZE_SIZE && !defined(_WIN32) && !defined(_WIN64) +#define TCACHE +#define TCACHE_STALE (0x40000000) +#define TCACHE_FLUID (0x40000000 | 0x80000000) +static unsigned g_tcache_mode = TCACHE_STALE; +static struct timeval g_tcache_tv = {0, 0}; +static struct tm g_tcache_tm = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +static INLINE int tcache_get(const struct timeval *const tv, struct tm *const tm) +{ + unsigned mode; + mode = __atomic_load_n(&g_tcache_mode, __ATOMIC_RELAXED); + if (0 == (mode & TCACHE_FLUID)) + { + mode = __atomic_fetch_add(&g_tcache_mode, 1, __ATOMIC_ACQUIRE); + if (0 == (mode & TCACHE_FLUID)) + { + if (g_tcache_tv.tv_sec == tv->tv_sec) + { + *tm = g_tcache_tm; + __atomic_sub_fetch(&g_tcache_mode, 1, __ATOMIC_RELEASE); + return !0; + } + __atomic_or_fetch(&g_tcache_mode, TCACHE_STALE, __ATOMIC_RELAXED); + } + __atomic_sub_fetch(&g_tcache_mode, 1, __ATOMIC_RELEASE); + } + return 0; +} + +static INLINE void tcache_set(const struct timeval *const tv, struct tm *const tm) +{ + unsigned stale = TCACHE_STALE; + if (__atomic_compare_exchange_n(&g_tcache_mode, &stale, TCACHE_FLUID, + 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) + { + g_tcache_tv = *tv; + g_tcache_tm = *tm; + __atomic_and_fetch(&g_tcache_mode, ~TCACHE_FLUID, __ATOMIC_RELEASE); + } +} +#endif + +static void time_callback(struct tm *const tm, unsigned *const msec) +{ +#if !_BT_LOG_MESSAGE_FORMAT_DATETIME_USED + VAR_UNUSED(tm); + VAR_UNUSED(msec); +#else + #if defined(_WIN32) || defined(_WIN64) + SYSTEMTIME st; + GetLocalTime(&st); + tm->tm_year = st.wYear; + tm->tm_mon = st.wMonth; + tm->tm_mday = st.wDay; + tm->tm_wday = st.wDayOfWeek; + tm->tm_hour = st.wHour; + tm->tm_min = st.wMinute; + tm->tm_sec = st.wSecond; + *msec = st.wMilliseconds; + #else + struct timeval tv; + gettimeofday(&tv, 0); + #ifndef TCACHE + localtime_r(&tv.tv_sec, tm); + #else + if (!tcache_get(&tv, tm)) + { + localtime_r(&tv.tv_sec, tm); + tcache_set(&tv, tm); + } + #endif + *msec = (unsigned)tv.tv_usec / 1000; + #endif +#endif +} + +static void pid_callback(int *const pid, int *const tid) +{ +#if !_BT_LOG_MESSAGE_FORMAT_CONTAINS(PID, BT_LOG_MESSAGE_CTX_FORMAT) + VAR_UNUSED(pid); +#else + #if defined(_WIN32) || defined(_WIN64) + *pid = GetCurrentProcessId(); + #else + *pid = getpid(); + #endif +#endif + +#if !_BT_LOG_MESSAGE_FORMAT_CONTAINS(TID, BT_LOG_MESSAGE_CTX_FORMAT) + VAR_UNUSED(tid); +#else + #if defined(_WIN32) || defined(_WIN64) + *tid = GetCurrentThreadId(); + #elif defined(__ANDROID__) + *tid = gettid(); + #elif defined(__linux__) + *tid = syscall(SYS_gettid); + #elif defined(__MACH__) + *tid = (int)pthread_mach_thread_np(pthread_self()); + #else + #define Platform not supported + #endif +#endif +} + +static void buffer_callback(bt_log_message *msg, char *buf) +{ + msg->e = (msg->p = msg->buf = buf) + g_buf_sz; +} + +#if _BT_LOG_MESSAGE_FORMAT_CONTAINS(FUNCTION, BT_LOG_MESSAGE_SRC_FORMAT) +static const char *funcname(const char *func) +{ + return func? func: ""; +} +#endif + +#if _BT_LOG_MESSAGE_FORMAT_CONTAINS(FILENAME, BT_LOG_MESSAGE_SRC_FORMAT) +static const char *filename(const char *file) +{ + const char *f = file; + for (const char *p = file; 0 != *p; ++p) + { + if ('/' == *p || '\\' == *p) + { + f = p + 1; + } + } + return f; +} +#endif + +static INLINE size_t nprintf_size(bt_log_message *const msg) +{ + // *nprintf() always puts 0 in the end when input buffer is not empty. This + // 0 is not desired because its presence sets (ctx->p) to (ctx->e - 1) which + // leaves space for one more character. Some put_xxx() functions don't use + // *nprintf() and could use that last character. In that case log line will + // have multiple (two) half-written parts which is confusing. To workaround + // that we allow *nprintf() to write its 0 in the eol area (which is always + // not empty). + return (size_t)(msg->e - msg->p + 1); +} + +static INLINE void put_nprintf(bt_log_message *const msg, const int n) +{ + if (0 < n) + { + msg->p = n < msg->e - msg->p? msg->p + n: msg->e; + } +} + +static INLINE char *put_padding_r(const unsigned w, const char wc, + char *p, char *e) +{ + for (char *const b = e - w; b < p; *--p = wc) {} + return p; +} + +static char *put_integer_r(unsigned v, const int sign, + const unsigned w, const char wc, char *const e) +{ + static const char _signs[] = {'-', '0', '+'}; + static const char *const signs = _signs + 1; + char *p = e; + do { *--p = '0' + v % 10; } while (0 != (v /= 10)); + if (0 == sign) return put_padding_r(w, wc, p, e); + if ('0' != wc) + { + *--p = signs[sign]; + return put_padding_r(w, wc, p, e); + } + p = put_padding_r(w, wc, p, e + 1); + *--p = signs[sign]; + return p; +} + +static INLINE char *put_uint_r(const unsigned v, const unsigned w, const char wc, + char *const e) +{ + return put_integer_r(v, 0, w, wc, e); +} + +static INLINE char *put_int_r(const int v, const unsigned w, const char wc, + char *const e) +{ + return 0 <= v? put_integer_r((unsigned)v, 0, w, wc, e) + : put_integer_r((unsigned)-v, -1, w, wc, e); +} + +static INLINE char *put_stringn(const char *const s_p, const char *const s_e, + char *const p, char *const e) +{ + const ptrdiff_t m = e - p; + ptrdiff_t n = s_e - s_p; + if (n > m) + { + n = m; + } + memcpy(p, s_p, n); + return p + n; +} + +static INLINE char *put_string(const char *s, char *p, char *const e) +{ + const ptrdiff_t n = e - p; + char *const c = (char *)memccpy(p, s, '\0', n); + return 0 != c? c - 1: e; +} + +static INLINE char *put_uint(unsigned v, const unsigned w, const char wc, + char *const p, char *const e) +{ + char buf[16]; + char *const se = buf + _countof(buf); + char *sp = put_uint_r(v, w, wc, se); + return put_stringn(sp, se, p, e); +} + +#define PUT_CSTR_R(p, STR) \ + do { \ + for (unsigned i = sizeof(STR) - 1; 0 < i--;) { \ + *--(p) = (STR)[i]; \ + } \ + } _BT_LOG_ONCE + +#define PUT_CSTR_CHECKED(p, e, STR) \ + do { \ + for (unsigned i = 0; (e) > (p) && (sizeof(STR) - 1) > i; ++i) { \ + *(p)++ = (STR)[i]; \ + } \ + } _BT_LOG_ONCE + +/* F_INIT field support. + */ +#define _BT_LOG_MESSAGE_FORMAT_INIT__ +#define _BT_LOG_MESSAGE_FORMAT_INIT__YEAR +#define _BT_LOG_MESSAGE_FORMAT_INIT__MONTH +#define _BT_LOG_MESSAGE_FORMAT_INIT__DAY +#define _BT_LOG_MESSAGE_FORMAT_INIT__HOUR +#define _BT_LOG_MESSAGE_FORMAT_INIT__MINUTE +#define _BT_LOG_MESSAGE_FORMAT_INIT__SECOND +#define _BT_LOG_MESSAGE_FORMAT_INIT__MILLISECOND +#define _BT_LOG_MESSAGE_FORMAT_INIT__PID +#define _BT_LOG_MESSAGE_FORMAT_INIT__TID +#define _BT_LOG_MESSAGE_FORMAT_INIT__LEVEL +#define _BT_LOG_MESSAGE_FORMAT_INIT__TAG(ps, ts) +#define _BT_LOG_MESSAGE_FORMAT_INIT__FUNCTION +#define _BT_LOG_MESSAGE_FORMAT_INIT__FILENAME +#define _BT_LOG_MESSAGE_FORMAT_INIT__FILELINE +#define _BT_LOG_MESSAGE_FORMAT_INIT__S(s) +#define _BT_LOG_MESSAGE_FORMAT_INIT__F_INIT(expr) _PP_UNTUPLE(expr); +#define _BT_LOG_MESSAGE_FORMAT_INIT__F_UINT(w, v) +#define _BT_LOG_MESSAGE_FORMAT_INIT(field) \ + _PP_CONCAT_3(_BT_LOG_MESSAGE_FORMAT_INIT_, _, field) + +/* Implements generation of printf-like format string for log message + * format specification. + */ +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__ "" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__YEAR "%04u" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__MONTH "%02u" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__DAY "%02u" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__HOUR "%02u" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__MINUTE "%02u" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__SECOND "%02u" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__MILLISECOND "%03u" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__PID "%5i" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__TID "%5i" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__LEVEL "%c" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__TAG UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__FUNCTION "%s" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__FILENAME "%s" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__FILELINE "%u" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__S(s) s +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__F_INIT(expr) "" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT__F_UINT(w, v) "%" #w "u" +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_FMT(field) \ + _PP_CONCAT_3(_BT_LOG_MESSAGE_FORMAT_PRINTF_FMT_, _, field) + +/* Implements generation of printf-like format parameters for log message + * format specification. + */ +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__ +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__YEAR ,(unsigned)(tm.tm_year + 1900) +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__MONTH ,(unsigned)(tm.tm_mon + 1) +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__DAY ,(unsigned)tm.tm_mday +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__HOUR ,(unsigned)tm.tm_hour +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__MINUTE ,(unsigned)tm.tm_min +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__SECOND ,(unsigned)tm.tm_sec +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__MILLISECOND ,(unsigned)msec +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__PID ,pid +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__TID ,tid +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__LEVEL ,(char)lvl_char(msg->lvl) +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__TAG UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__FUNCTION ,funcname(src->func) +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__FILENAME ,filename(src->file) +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__FILELINE ,src->line +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__S(s) +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__F_INIT(expr) +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL__F_UINT(w, v) ,v +#define _BT_LOG_MESSAGE_FORMAT_PRINTF_VAL(field) \ + _PP_CONCAT_3(_BT_LOG_MESSAGE_FORMAT_PRINTF_VAL_, _, field) + +/* Implements generation of put_xxx_t statements for log message specification. + */ +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__ +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__YEAR p = put_uint_r(tm.tm_year + 1900, 4, '0', p); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__MONTH p = put_uint_r((unsigned)tm.tm_mon + 1, 2, '0', p); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__DAY p = put_uint_r((unsigned)tm.tm_mday, 2, '0', p); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__HOUR p = put_uint_r((unsigned)tm.tm_hour, 2, '0', p); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__MINUTE p = put_uint_r((unsigned)tm.tm_min, 2, '0', p); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__SECOND p = put_uint_r((unsigned)tm.tm_sec, 2, '0', p); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__MILLISECOND p = put_uint_r(msec, 3, '0', p); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__PID p = put_int_r(pid, 5, ' ', p); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__TID p = put_int_r(tid, 5, ' ', p); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__LEVEL *--p = lvl_char(msg->lvl); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__TAG UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__FUNCTION UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__FILENAME UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__FILELINE UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__S(s) PUT_CSTR_R(p, s); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__F_INIT(expr) +#define _BT_LOG_MESSAGE_FORMAT_PUT_R__F_UINT(w, v) p = put_uint_r(v, w, ' ', p); +#define _BT_LOG_MESSAGE_FORMAT_PUT_R(field) \ + _PP_CONCAT_3(_BT_LOG_MESSAGE_FORMAT_PUT_R_, _, field) + +static void put_ctx(bt_log_message *const msg) +{ + _PP_MAP(_BT_LOG_MESSAGE_FORMAT_INIT, BT_LOG_MESSAGE_CTX_FORMAT) +#if !_BT_LOG_MESSAGE_FORMAT_FIELDS(BT_LOG_MESSAGE_CTX_FORMAT) + VAR_UNUSED(msg); +#else + #if _BT_LOG_MESSAGE_FORMAT_DATETIME_USED + struct tm tm; + unsigned msec; + g_time_cb(&tm, &msec); + #endif + #if _BT_LOG_MESSAGE_FORMAT_CONTAINS(PID, BT_LOG_MESSAGE_CTX_FORMAT) || \ + _BT_LOG_MESSAGE_FORMAT_CONTAINS(TID, BT_LOG_MESSAGE_CTX_FORMAT) + int pid, tid; + g_pid_cb(&pid, &tid); + #endif + + #if BT_LOG_OPTIMIZE_SIZE + int n; + n = snprintf(msg->p, nprintf_size(msg), + _PP_MAP(_BT_LOG_MESSAGE_FORMAT_PRINTF_FMT, BT_LOG_MESSAGE_CTX_FORMAT) + _PP_MAP(_BT_LOG_MESSAGE_FORMAT_PRINTF_VAL, BT_LOG_MESSAGE_CTX_FORMAT)); + put_nprintf(msg, n); + #else + char buf[64]; + char *const e = buf + sizeof(buf); + char *p = e; + _PP_RMAP(_BT_LOG_MESSAGE_FORMAT_PUT_R, BT_LOG_MESSAGE_CTX_FORMAT) + msg->p = put_stringn(p, e, msg->p, msg->e); + #endif +#endif +} + +#define PUT_TAG(msg, tag, prefix_delim, tag_delim) \ + do { \ + const char *ch; \ + msg->tag_b = msg->p; \ + if (0 != (ch = _bt_log_tag_prefix)) { \ + for (;msg->e != msg->p && 0 != (*msg->p = *ch); ++msg->p, ++ch) {} \ + } \ + if (0 != (ch = tag) && 0 != tag[0]) { \ + if (msg->tag_b != msg->p) { \ + PUT_CSTR_CHECKED(msg->p, msg->e, prefix_delim); \ + } \ + for (;msg->e != msg->p && 0 != (*msg->p = *ch); ++msg->p, ++ch) {} \ + } \ + msg->tag_e = msg->p; \ + if (msg->tag_b != msg->p) { \ + PUT_CSTR_CHECKED(msg->p, msg->e, tag_delim); \ + } \ + } _BT_LOG_ONCE + +/* Implements simple put statements for log message specification. + */ +#define _BT_LOG_MESSAGE_FORMAT_PUT__ +#define _BT_LOG_MESSAGE_FORMAT_PUT__YEAR UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT__MONTH UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT__DAY UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT__HOUR UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT__MINUTE UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT__SECOND UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT__MILLISECOND UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT__PID UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT__TID UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT__LEVEL UNDEFINED +#define _BT_LOG_MESSAGE_FORMAT_PUT__TAG(pd, td) PUT_TAG(msg, tag, pd, td); +#define _BT_LOG_MESSAGE_FORMAT_PUT__FUNCTION msg->p = put_string(funcname(src->func), msg->p, msg->e); +#define _BT_LOG_MESSAGE_FORMAT_PUT__FILENAME msg->p = put_string(filename(src->file), msg->p, msg->e); +#define _BT_LOG_MESSAGE_FORMAT_PUT__FILELINE msg->p = put_uint(src->line, 0, '\0', msg->p, msg->e); +#define _BT_LOG_MESSAGE_FORMAT_PUT__S(s) PUT_CSTR_CHECKED(msg->p, msg->e, s); +#define _BT_LOG_MESSAGE_FORMAT_PUT__F_INIT(expr) +#define _BT_LOG_MESSAGE_FORMAT_PUT__F_UINT(w, v) msg->p = put_uint(v, w, ' ', msg->p, msg->e); +#define _BT_LOG_MESSAGE_FORMAT_PUT(field) \ + _PP_CONCAT_3(_BT_LOG_MESSAGE_FORMAT_PUT_, _, field) + +static void put_tag(bt_log_message *const msg, const char *const tag) +{ + _PP_MAP(_BT_LOG_MESSAGE_FORMAT_INIT, BT_LOG_MESSAGE_TAG_FORMAT) +#if !_BT_LOG_MESSAGE_FORMAT_CONTAINS(TAG, BT_LOG_MESSAGE_TAG_FORMAT) + VAR_UNUSED(tag); +#endif +#if !_BT_LOG_MESSAGE_FORMAT_FIELDS(BT_LOG_MESSAGE_TAG_FORMAT) + VAR_UNUSED(msg); +#else + _PP_MAP(_BT_LOG_MESSAGE_FORMAT_PUT, BT_LOG_MESSAGE_TAG_FORMAT) +#endif +} + +static void put_src(bt_log_message *const msg, const src_location *const src) +{ + _PP_MAP(_BT_LOG_MESSAGE_FORMAT_INIT, BT_LOG_MESSAGE_SRC_FORMAT) +#if !_BT_LOG_MESSAGE_FORMAT_CONTAINS(FUNCTION, BT_LOG_MESSAGE_SRC_FORMAT) && \ + !_BT_LOG_MESSAGE_FORMAT_CONTAINS(FILENAME, BT_LOG_MESSAGE_SRC_FORMAT) && \ + !_BT_LOG_MESSAGE_FORMAT_CONTAINS(FILELINE, BT_LOG_MESSAGE_SRC_FORMAT) + VAR_UNUSED(src); +#endif +#if !_BT_LOG_MESSAGE_FORMAT_FIELDS(BT_LOG_MESSAGE_SRC_FORMAT) + VAR_UNUSED(msg); +#else + #if BT_LOG_OPTIMIZE_SIZE + int n; + n = snprintf(msg->p, nprintf_size(msg), + _PP_MAP(_BT_LOG_MESSAGE_FORMAT_PRINTF_FMT, BT_LOG_MESSAGE_SRC_FORMAT) + _PP_MAP(_BT_LOG_MESSAGE_FORMAT_PRINTF_VAL, BT_LOG_MESSAGE_SRC_FORMAT)); + put_nprintf(msg, n); + #else + _PP_MAP(_BT_LOG_MESSAGE_FORMAT_PUT, BT_LOG_MESSAGE_SRC_FORMAT) + #endif +#endif +} + +static void put_msg(bt_log_message *const msg, + const char *const fmt, va_list va) +{ + int n; + msg->msg_b = msg->p; + n = vsnprintf(msg->p, nprintf_size(msg), fmt, va); + put_nprintf(msg, n); +} + +static void output_mem(const bt_log_spec *log, bt_log_message *const msg, + const mem_block *const mem) +{ + if (0 == mem->d || 0 == mem->d_sz) + { + return; + } + const unsigned char *mem_p = (const unsigned char *)mem->d; + const unsigned char *const mem_e = mem_p + mem->d_sz; + const unsigned char *mem_cut; + const ptrdiff_t mem_width = (ptrdiff_t)log->format->mem_width; + char *const hex_b = msg->msg_b; + char *const ascii_b = hex_b + 2 * mem_width + 2; + char *const ascii_e = ascii_b + mem_width; + if (msg->e < ascii_e) + { + return; + } + while (mem_p != mem_e) + { + char *hex = hex_b; + char *ascii = ascii_b; + for (mem_cut = mem_width < mem_e - mem_p? mem_p + mem_width: mem_e; + mem_cut != mem_p; ++mem_p) + { + const unsigned char ch = *mem_p; + *hex++ = c_hex[(0xf0 & ch) >> 4]; + *hex++ = c_hex[(0x0f & ch)]; + *ascii++ = isprint(ch)? (char)ch: '?'; + } + while (hex != ascii_b) + { + *hex++ = ' '; + } + msg->p = ascii; + log->output->callback(msg, log->output->arg); + } +} + +BT_HIDDEN +void bt_log_set_tag_prefix(const char *const prefix) +{ + _bt_log_tag_prefix = prefix; +} + +BT_HIDDEN +void bt_log_set_mem_width(const unsigned w) +{ + _bt_log_global_format.mem_width = w; +} + +BT_HIDDEN +void bt_log_set_output_level(const int lvl) +{ + _bt_log_global_output_lvl = lvl; +} + +BT_HIDDEN +void bt_log_set_output_v(const unsigned mask, void *const arg, + const bt_log_output_cb callback) +{ + _bt_log_global_output.mask = mask; + _bt_log_global_output.arg = arg; + _bt_log_global_output.callback = callback; +} + +static void _bt_log_write_imp( + const bt_log_spec *log, + const src_location *const src, const mem_block *const mem, + const int lvl, const char *const tag, const char *const fmt, va_list va) +{ + bt_log_message msg; + char buf[BT_LOG_BUF_SZ]; + const unsigned mask = log->output->mask; + msg.lvl = lvl; + msg.tag = tag; + g_buffer_cb(&msg, buf); + if (BT_LOG_PUT_CTX & mask) + { + put_ctx(&msg); + } + if (BT_LOG_PUT_TAG & mask) + { + put_tag(&msg, tag); + } + if (0 != src && BT_LOG_PUT_SRC & mask) + { + put_src(&msg, src); + } + if (BT_LOG_PUT_MSG & mask) + { + put_msg(&msg, fmt, va); + } + log->output->callback(&msg, log->output->arg); + if (0 != mem && BT_LOG_PUT_MSG & mask) + { + output_mem(log, &msg, mem); + } +} + +BT_HIDDEN +void _bt_log_write_d( + const char *const func, const char *const file, const unsigned line, + const int lvl, const char *const tag, + const char *const fmt, ...) +{ + const src_location src = {func, file, line}; + va_list va; + va_start(va, fmt); + _bt_log_write_imp(&global_spec, &src, 0, lvl, tag, fmt, va); + va_end(va); +} + +BT_HIDDEN +void _bt_log_write_aux_d( + const char *const func, const char *const file, const unsigned line, + const bt_log_spec *const log, const int lvl, const char *const tag, + const char *const fmt, ...) +{ + const src_location src = {func, file, line}; + va_list va; + va_start(va, fmt); + _bt_log_write_imp(log, &src, 0, lvl, tag, fmt, va); + va_end(va); +} + +BT_HIDDEN +void _bt_log_write(const int lvl, const char *const tag, + const char *const fmt, ...) +{ + va_list va; + va_start(va, fmt); + _bt_log_write_imp(&global_spec, 0, 0, lvl, tag, fmt, va); + va_end(va); +} + +BT_HIDDEN +void _bt_log_write_aux( + const bt_log_spec *const log, const int lvl, const char *const tag, + const char *const fmt, ...) +{ + va_list va; + va_start(va, fmt); + _bt_log_write_imp(log, 0, 0, lvl, tag, fmt, va); + va_end(va); +} + +BT_HIDDEN +void _bt_log_write_mem_d( + const char *const func, const char *const file, const unsigned line, + const int lvl, const char *const tag, + const void *const d, const unsigned d_sz, + const char *const fmt, ...) +{ + const src_location src = {func, file, line}; + const mem_block mem = {d, d_sz}; + va_list va; + va_start(va, fmt); + _bt_log_write_imp(&global_spec, &src, &mem, lvl, tag, fmt, va); + va_end(va); +} + +BT_HIDDEN +void _bt_log_write_mem_aux_d( + const char *const func, const char *const file, const unsigned line, + const bt_log_spec *const log, const int lvl, const char *const tag, + const void *const d, const unsigned d_sz, + const char *const fmt, ...) +{ + const src_location src = {func, file, line}; + const mem_block mem = {d, d_sz}; + va_list va; + va_start(va, fmt); + _bt_log_write_imp(log, &src, &mem, lvl, tag, fmt, va); + va_end(va); +} + +BT_HIDDEN +void _bt_log_write_mem(const int lvl, const char *const tag, + const void *const d, const unsigned d_sz, + const char *const fmt, ...) +{ + const mem_block mem = {d, d_sz}; + va_list va; + va_start(va, fmt); + _bt_log_write_imp(&global_spec, 0, &mem, lvl, tag, fmt, va); + va_end(va); +} + +BT_HIDDEN +void _bt_log_write_mem_aux( + const bt_log_spec *const log, const int lvl, const char *const tag, + const void *const d, const unsigned d_sz, + const char *const fmt, ...) +{ + const mem_block mem = {d, d_sz}; + va_list va; + va_start(va, fmt); + _bt_log_write_imp(log, 0, &mem, lvl, tag, fmt, va); + va_end(va); +} -- 2.34.1