summaryrefslogtreecommitdiffstats
path: root/src/libnetdata/log
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnetdata/log')
-rw-r--r--src/libnetdata/log/README.md261
-rw-r--r--src/libnetdata/log/log.c2545
-rw-r--r--src/libnetdata/log/nd_log-annotators.c84
-rw-r--r--src/libnetdata/log/nd_log-common.h147
-rw-r--r--src/libnetdata/log/nd_log-config.c207
-rw-r--r--src/libnetdata/log/nd_log-field-formatters.c127
-rw-r--r--src/libnetdata/log/nd_log-format-json.c78
-rw-r--r--src/libnetdata/log/nd_log-format-logfmt.c151
-rw-r--r--src/libnetdata/log/nd_log-init.c313
-rw-r--r--src/libnetdata/log/nd_log-internals.c823
-rw-r--r--src/libnetdata/log/nd_log-internals.h249
-rw-r--r--src/libnetdata/log/nd_log-to-file.c41
-rw-r--r--src/libnetdata/log/nd_log-to-syslog.c20
-rw-r--r--src/libnetdata/log/nd_log-to-systemd-journal.c296
-rw-r--r--src/libnetdata/log/nd_log-to-windows-common.h188
-rw-r--r--src/libnetdata/log/nd_log-to-windows-events.c554
-rw-r--r--src/libnetdata/log/nd_log.c465
-rw-r--r--src/libnetdata/log/nd_log.h (renamed from src/libnetdata/log/log.h)148
-rw-r--r--src/libnetdata/log/nd_log_limit.c100
-rw-r--r--src/libnetdata/log/nd_log_limit.h26
-rw-r--r--src/libnetdata/log/nd_wevents_manifest.xml295
-rw-r--r--src/libnetdata/log/systemd-cat-native.c73
-rw-r--r--src/libnetdata/log/systemd-journal-helpers.c (renamed from src/libnetdata/log/journal.c)2
-rw-r--r--src/libnetdata/log/systemd-journal-helpers.h (renamed from src/libnetdata/log/journal.h)6
-rw-r--r--src/libnetdata/log/wevt_netdata_compile.bat121
-rw-r--r--src/libnetdata/log/wevt_netdata_compile.sh48
-rw-r--r--src/libnetdata/log/wevt_netdata_install.bat52
-rw-r--r--src/libnetdata/log/wevt_netdata_mc_generate.c518
28 files changed, 5146 insertions, 2792 deletions
diff --git a/src/libnetdata/log/README.md b/src/libnetdata/log/README.md
index ef9ca1ef3..c7a42f28b 100644
--- a/src/libnetdata/log/README.md
+++ b/src/libnetdata/log/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Log"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/libnetdata/log/README.md
-sidebar_label: "Log"
-learn_status: "Published"
-learn_topic_type: "Tasks"
-learn_rel_path: "Developers/libnetdata"
--->
-
# Netdata Logging
This document describes how Netdata generates its own logs, not how Netdata manages and queries logs databases.
@@ -26,14 +17,15 @@ For each log source, Netdata supports the following output methods:
- **off**, to disable this log source
- **journal**, to send the logs to systemd-journal.
+- **etw**, to send the logs to Event Tracing for Windows (ETW).
+- **wel**, to send the logs to the Windows Event Log (WEL).
- **syslog**, to send the logs to syslog.
- **system**, to send the output to `stderr` or `stdout` depending on the log source.
- **stdout**, to write the logs to Netdata's `stdout`.
- **stderr**, to write the logs to Netdata's `stderr`.
- **filename**, to send the logs to a file.
-For `daemon` and `collector` the default is `journal` when systemd-journal is available.
-To decide if systemd-journal is available, Netdata checks:
+On Linux, when systemd-journal is available, the default is `journal` for `daemon` and `collector` and `filename` for the rest. To decide if systemd-journal is available, Netdata checks:
1. `stderr` is connected to systemd-journald
2. `/run/systemd/journal/socket` exists
@@ -41,13 +33,16 @@ To decide if systemd-journal is available, Netdata checks:
If any of the above is detected, Netdata will select `journal` for `daemon` and `collector` sources.
-All other sources default to a file.
+On Windows, the default is `etw` and if that is not available it falls back to `wel`. The availability of `etw` is decided at compile time.
## Log formats
| Format | Description |
|---------|--------------------------------------------------------------------------------------------------------|
| journal | journald-specific log format. Automatically selected when logging to systemd-journal. |
+| etw | Event Tracing for Windows specific format. Structured logging in Event Viewer. |
+| wel | Windows Event Log specific format. Basic field-based logging in Event Viewer. |
+| journal | journald-specific log format. Automatically selected when logging to systemd-journal. |
| logfmt | logs data as a series of key/value pairs. The default when logging to any output other than `journal`. |
| json | logs data in JSON format. |
@@ -66,6 +61,9 @@ Each time Netdata logs, it assigns a priority to the log. It can be one of this
| info | the default log level about information the user should know. |
| debug | these are more verbose logs that can be ignored. |
+For `etw` these are mapped to `Verbose`, `Informational`, `Warning`, `Error` and `Critical`.
+For `wel` these are mapped to `Informational`, `Warning`, `Error`.
+
## Logs Configuration
In `netdata.conf`, there are the following settings:
@@ -73,7 +71,7 @@ In `netdata.conf`, there are the following settings:
```
[logs]
# logs to trigger flood protection = 1000
- # logs flood protection period = 60
+ # logs flood protection period = 1m
# facility = daemon
# level = info
# daemon = journal
@@ -117,66 +115,69 @@ Sending a `SIGHUP` to Netdata, will instruct it to re-open all its log files.
<details>
<summary>All fields exposed by Netdata</summary>
-| journal | logfmt | json | Description |
-|:--------------------------------------:|:------------------------------:|:------------------------------:|:---------------------------------------------------------------------------------------------------------:|
-| `_SOURCE_REALTIME_TIMESTAMP` | `time` | `time` | the timestamp of the event |
-| `SYSLOG_IDENTIFIER` | `comm` | `comm` | the program logging the event |
-| `ND_LOG_SOURCE` | `source` | `source` | one of the [log sources](#log-sources) |
-| `PRIORITY`<br/>numeric | `level`<br/>text | `level`<br/>numeric | one of the [log levels](#log-levels) |
-| `ERRNO` | `errno` | `errno` | the numeric value of `errno` |
-| `INVOCATION_ID` | - | - | a unique UUID of the Netdata session, reset on every Netdata restart, inherited by systemd when available |
-| `CODE_LINE` | - | - | the line number of of the source code logging this event |
-| `CODE_FILE` | - | - | the filename of the source code logging this event |
-| `CODE_FUNCTION` | - | - | the function name of the source code logging this event |
-| `TID` | `tid` | `tid` | the thread id of the thread logging this event |
-| `THREAD_TAG` | `thread` | `thread` | the name of the thread logging this event |
-| `MESSAGE_ID` | `msg_id` | `msg_id` | see [message IDs](#message-ids) |
-| `ND_MODULE` | `module` | `module` | the Netdata module logging this event |
-| `ND_NIDL_NODE` | `node` | `node` | the hostname of the node the event is related to |
-| `ND_NIDL_INSTANCE` | `instance` | `instance` | the instance of the node the event is related to |
-| `ND_NIDL_CONTEXT` | `context` | `context` | the context the event is related to (this is usually the chart name, as shown on netdata dashboards |
-| `ND_NIDL_DIMENSION` | `dimension` | `dimension` | the dimension the event is related to |
-| `ND_SRC_TRANSPORT` | `src_transport` | `src_transport` | when the event happened during a request, this is the request transport |
-| `ND_SRC_IP` | `src_ip` | `src_ip` | when the event happened during an inbound request, this is the IP the request came from |
-| `ND_SRC_PORT` | `src_port` | `src_port` | when the event happened during an inbound request, this is the port the request came from |
-| `ND_SRC_FORWARDED_HOST` | `src_forwarded_host` | `src_forwarded_host` | the contents of the HTTP header `X-Forwarded-Host` |
-| `ND_SRC_FORWARDED_FOR` | `src_forwarded_for` | `src_forwarded_for` | the contents of the HTTP header `X-Forwarded-For` |
-| `ND_SRC_CAPABILITIES` | `src_capabilities` | `src_capabilities` | when the request came from a child, this is the communication capabilities of the child |
-| `ND_DST_TRANSPORT` | `dst_transport` | `dst_transport` | when the event happened during an outbound request, this is the outbound request transport |
-| `ND_DST_IP` | `dst_ip` | `dst_ip` | when the event happened during an outbound request, this is the IP the request destination |
-| `ND_DST_PORT` | `dst_port` | `dst_port` | when the event happened during an outbound request, this is the port the request destination |
-| `ND_DST_CAPABILITIES` | `dst_capabilities` | `dst_capabilities` | when the request goes to a parent, this is the communication capabilities of the parent |
-| `ND_REQUEST_METHOD` | `req_method` | `req_method` | when the event happened during an inbound request, this is the method the request was received |
-| `ND_RESPONSE_CODE` | `code` | `code` | when responding to a request, this this the response code |
-| `ND_CONNECTION_ID` | `conn` | `conn` | when there is a connection id for an inbound connection, this is the connection id |
-| `ND_TRANSACTION_ID` | `transaction` | `transaction` | the transaction id (UUID) of all API requests |
-| `ND_RESPONSE_SENT_BYTES` | `sent_bytes` | `sent_bytes` | the bytes we sent to API responses |
-| `ND_RESPONSE_SIZE_BYTES` | `size_bytes` | `size_bytes` | the uncompressed bytes of the API responses |
-| `ND_RESPONSE_PREP_TIME_USEC` | `prep_ut` | `prep_ut` | the time needed to prepare a response |
-| `ND_RESPONSE_SENT_TIME_USEC` | `sent_ut` | `sent_ut` | the time needed to send a response |
-| `ND_RESPONSE_TOTAL_TIME_USEC` | `total_ut` | `total_ut` | the total time needed to complete a response |
-| `ND_ALERT_ID` | `alert_id` | `alert_id` | the alert id this event is related to |
-| `ND_ALERT_EVENT_ID` | `alert_event_id` | `alert_event_id` | a sequential number of the alert transition (per host) |
-| `ND_ALERT_UNIQUE_ID` | `alert_unique_id` | `alert_unique_id` | a sequential number of the alert transition (per alert) |
-| `ND_ALERT_TRANSITION_ID` | `alert_transition_id` | `alert_transition_id` | the unique UUID of this alert transition |
-| `ND_ALERT_CONFIG` | `alert_config` | `alert_config` | the alert configuration hash (UUID) |
-| `ND_ALERT_NAME` | `alert` | `alert` | the alert name |
-| `ND_ALERT_CLASS` | `alert_class` | `alert_class` | the alert classification |
-| `ND_ALERT_COMPONENT` | `alert_component` | `alert_component` | the alert component |
-| `ND_ALERT_TYPE` | `alert_type` | `alert_type` | the alert type |
-| `ND_ALERT_EXEC` | `alert_exec` | `alert_exec` | the alert notification program |
-| `ND_ALERT_RECIPIENT` | `alert_recipient` | `alert_recipient` | the alert recipient(s) |
-| `ND_ALERT_VALUE` | `alert_value` | `alert_value` | the current alert value |
-| `ND_ALERT_VALUE_OLD` | `alert_value_old` | `alert_value_old` | the previous alert value |
-| `ND_ALERT_STATUS` | `alert_status` | `alert_status` | the current alert status |
-| `ND_ALERT_STATUS_OLD` | `alert_value_old` | `alert_value_old` | the previous alert value |
-| `ND_ALERT_UNITS` | `alert_units` | `alert_units` | the units of the alert |
-| `ND_ALERT_SUMMARY` | `alert_summary` | `alert_summary` | the summary text of the alert |
-| `ND_ALERT_INFO` | `alert_info` | `alert_info` | the info text of the alert |
-| `ND_ALERT_DURATION` | `alert_duration` | `alert_duration` | the duration the alert was in its previous state |
-| `ND_ALERT_NOTIFICATION_TIMESTAMP_USEC` | `alert_notification_timestamp` | `alert_notification_timestamp` | the timestamp the notification delivery is scheduled |
-| `ND_REQUEST` | `request` | `request` | the full request during which the event happened |
-| `MESSAGE` | `msg` | `msg` | the event message |
+| `journal` | `logfmt` and `json` | `etw` | `wel` | Description |
+|:--------------------------------------:|:------------------------------:|:-----------------------------:|:-----:|:----------------------------------------------------------------------------------------------------------|
+| `_SOURCE_REALTIME_TIMESTAMP` | `time` | `Timestamp` | 1 | the timestamp of the event |
+| `SYSLOG_IDENTIFIER` | `comm` | `Program` | 2 | the program logging the event |
+| `ND_LOG_SOURCE` | `source` | `NetdataLogSource` | 3 | one of the [log sources](#log-sources) |
+| `PRIORITY`<br/>numeric | `level`<br/>text | `Level`<br/>text | 4 | one of the [log levels](#log-levels) |
+| `ERRNO` | `errno` | `UnixErrno` | 5 | the numeric value of `errno` |
+| `INVOCATION_ID` | - | `InvocationID` | 7 | a unique UUID of the Netdata session, reset on every Netdata restart, inherited by systemd when available |
+| `CODE_LINE` | - | `CodeLine` | 8 | the line number of of the source code logging this event |
+| `CODE_FILE` | - | `CodeFile` | 9 | the filename of the source code logging this event |
+| `CODE_FUNCTION` | - | `CodeFunction` | 10 | the function name of the source code logging this event |
+| `TID` | `tid` | `ThreadID` | 11 | the thread id of the thread logging this event |
+| `THREAD_TAG` | `thread` | `ThreadName` | 12 | the name of the thread logging this event |
+| `MESSAGE_ID` | `msg_id` | `MessageID` | 13 | see [message IDs](#message-ids) |
+| `ND_MODULE` | `module` | `Module` | 14 | the Netdata module logging this event |
+| `ND_NIDL_NODE` | `node` | `Node` | 15 | the hostname of the node the event is related to |
+| `ND_NIDL_INSTANCE` | `instance` | `Instance` | 16 | the instance of the node the event is related to |
+| `ND_NIDL_CONTEXT` | `context` | `Context` | 17 | the context the event is related to (this is usually the chart name, as shown on netdata dashboards |
+| `ND_NIDL_DIMENSION` | `dimension` | `Dimension` | 18 | the dimension the event is related to |
+| `ND_SRC_TRANSPORT` | `src_transport` | `SourceTransport` | 19 | when the event happened during a request, this is the request transport |
+| `ND_SRC_IP` | `src_ip` | `SourceIP` | 24 | when the event happened during an inbound request, this is the IP the request came from |
+| `ND_SRC_PORT` | `src_port` | `SourcePort` | 25 | when the event happened during an inbound request, this is the port the request came from |
+| `ND_SRC_FORWARDED_HOST` | `src_forwarded_host` | `SourceForwardedHost` | 26 | the contents of the HTTP header `X-Forwarded-Host` |
+| `ND_SRC_FORWARDED_FOR` | `src_forwarded_for` | `SourceForwardedFor` | 27 | the contents of the HTTP header `X-Forwarded-For` |
+| `ND_SRC_CAPABILITIES` | `src_capabilities` | `SourceCapabilities` | 28 | when the request came from a child, this is the communication capabilities of the child |
+| `ND_DST_TRANSPORT` | `dst_transport` | `DestinationTransport` | 29 | when the event happened during an outbound request, this is the outbound request transport |
+| `ND_DST_IP` | `dst_ip` | `DestinationIP` | 30 | when the event happened during an outbound request, this is the IP the request destination |
+| `ND_DST_PORT` | `dst_port` | `DestinationPort` | 31 | when the event happened during an outbound request, this is the port the request destination |
+| `ND_DST_CAPABILITIES` | `dst_capabilities` | `DestinationCapabilities` | 32 | when the request goes to a parent, this is the communication capabilities of the parent |
+| `ND_REQUEST_METHOD` | `req_method` | `RequestMethod` | 33 | when the event happened during an inbound request, this is the method the request was received |
+| `ND_RESPONSE_CODE` | `code` | `ResponseCode` | 34 | when responding to a request, this this the response code |
+| `ND_CONNECTION_ID` | `conn` | `ConnectionID` | 35 | when there is a connection id for an inbound connection, this is the connection id |
+| `ND_TRANSACTION_ID` | `transaction` | `TransactionID` | 36 | the transaction id (UUID) of all API requests |
+| `ND_RESPONSE_SENT_BYTES` | `sent_bytes` | `ResponseSentBytes` | 37 | the bytes we sent to API responses |
+| `ND_RESPONSE_SIZE_BYTES` | `size_bytes` | `ResponseSizeBytes` | 38 | the uncompressed bytes of the API responses |
+| `ND_RESPONSE_PREP_TIME_USEC` | `prep_ut` | `ResponsePreparationTimeUsec` | 39 | the time needed to prepare a response |
+| `ND_RESPONSE_SENT_TIME_USEC` | `sent_ut` | `ResponseSentTimeUsec` | 40 | the time needed to send a response |
+| `ND_RESPONSE_TOTAL_TIME_USEC` | `total_ut` | `ResponseTotalTimeUsec` | 41 | the total time needed to complete a response |
+| `ND_ALERT_ID` | `alert_id` | `AlertID` | 42 | the alert id this event is related to |
+| `ND_ALERT_EVENT_ID` | `alert_event_id` | `AlertEventID` | 44 | a sequential number of the alert transition (per host) |
+| `ND_ALERT_UNIQUE_ID` | `alert_unique_id` | `AlertUniqueID` | 43 | a sequential number of the alert transition (per alert) |
+| `ND_ALERT_TRANSITION_ID` | `alert_transition_id` | `AlertTransitionID` | 45 | the unique UUID of this alert transition |
+| `ND_ALERT_CONFIG` | `alert_config` | `AlertConfig` | 46 | the alert configuration hash (UUID) |
+| `ND_ALERT_NAME` | `alert` | `AlertName` | 47 | the alert name |
+| `ND_ALERT_CLASS` | `alert_class` | `AlertClass` | 48 | the alert classification |
+| `ND_ALERT_COMPONENT` | `alert_component` | `AlertComponent` | 49 | the alert component |
+| `ND_ALERT_TYPE` | `alert_type` | `AlertType` | 50 | the alert type |
+| `ND_ALERT_EXEC` | `alert_exec` | `AlertExec` | 51 | the alert notification program |
+| `ND_ALERT_RECIPIENT` | `alert_recipient` | `AlertRecipient` | 52 | the alert recipient(s) |
+| `ND_ALERT_VALUE` | `alert_value` | `AlertValue` | 54 | the current alert value |
+| `ND_ALERT_VALUE_OLD` | `alert_value_old` | `AlertOldValue` | 55 | the previous alert value |
+| `ND_ALERT_STATUS` | `alert_status` | `AlertStatus` | 56 | the current alert status |
+| `ND_ALERT_STATUS_OLD` | `alert_value_old` | `AlertOldStatus` | 57 | the previous alert status |
+| `ND_ALERT_UNITS` | `alert_units` | `AlertUnits` | 59 | the units of the alert |
+| `ND_ALERT_SUMMARY` | `alert_summary` | `AlertSummary` | 60 | the summary text of the alert |
+| `ND_ALERT_INFO` | `alert_info` | `AlertInfo` | 61 | the info text of the alert |
+| `ND_ALERT_DURATION` | `alert_duration` | `AlertDuration` | 53 | the duration the alert was in its previous state |
+| `ND_ALERT_NOTIFICATION_TIMESTAMP_USEC` | `alert_notification_timestamp` | `AlertNotificationTimeUsec` | 62 | the timestamp the notification delivery is scheduled |
+| `ND_REQUEST` | `request` | `Request` | 63 | the full request during which the event happened |
+| `MESSAGE` | `msg` | `Message` | 64 | the event message |
+
+For `wel` (Windows Event Logs), all logs have an array of 64 fields strings, and their index number provides their meaning.
+For `etw` (Event Tracing for Windows), Netdata logs in a structured way, and field names are available.
</details>
@@ -221,3 +222,117 @@ journalctl -u netdata --namespace=netdata
# All netdata logs, the newest entries are displayed first
journalctl -u netdata --namespace=netdata -r
```
+
+## Using Event Tracing for Windows (ETW)
+
+ETW requires the publisher `Netdata` to be registered. Our Windows installer does this automatically.
+
+Registering the publisher is done via a manifest (`%SystemRoot%\System32\wevt_netdata_manifest.xml`)
+and its messages resources DLL (`%SystemRoot%\System32\wevt_netdata.dll`).
+
+If needed, the publisher can be registered and unregistered manually using these commands:
+
+```bat
+REM register the Netdata publisher
+wevtutil im "%SystemRoot%\System32\wevt_netdata_manifest.xml" "/mf:%SystemRoot%\System32\wevt_netdata.dll" "/rf:%SystemRoot%\System32\wevt_netdata.dll"
+
+REM unregister the Netdata publisher
+wevtutil um "%SystemRoot%\System32\wevt_netdata_manifest.xml"
+```
+
+The structure of the logs are as follows:
+
+ - Publisher `Netdata`
+ - Channel `Netdata/Daemon`: general messages about the Netdata service
+ - Channel `Netdata/Collector`: general messages about Netdata external plugins
+ - Channel `Netdata/Health`: alert transitions and general messages generated by Netdata's health engine
+ - Channel `Netdata/Access`: all accesses to Netdata APIs
+ - Channel `Netdata/Aclk`: for cloud connectivity tracing (disabled by default)
+
+Retention can be configured per Channel via the Event Viewer. Netdata does not set a default, so the system default is used.
+
+> **IMPORTANT**<br/>
+> Event Tracing for Windows (ETW) does not allow logging the percentage character `%`.
+> The `%` followed by a number, is recursively used for fields expansion and ETW has not
+> provided any way to escape the character for preventing further expansion.<br/>
+> <br/>
+> To work around this limitation, Netdata replaces all `%` which are followed by a number, with `℅`
+> (the Unicode character `care of`). Visually, they look similar, but when copying IPv6 addresses
+> or URLs from the logs, you have to be careful to manually replace `℅` with `%` before using them.
+
+## Using Windows Event Logs (WEL)
+
+WEL has a different logs structure and unfortunately WEL and ETW need to use different names if they are to be used
+concurrently.
+
+For WEL, Netdata logs as follows:
+
+ - Channel `NetdataWEL` (unfortunately `Netdata` cannot be used, it conflicts with the ETW Publisher name)
+ - Publisher `NetdataDaemon`: general messages about the Netdata service
+ - Publisher `NetdataCollector`: general messages about Netdata external plugins
+ - Publisher `NetdataHealth`: alert transitions and general messages generated by Netdata's health engine
+ - Publisher `NetdataAccess`: all accesses to Netdata APIs
+ - Publisher `NetdataAclk`: for cloud connectivity tracing (disabled by default)
+
+Publishers must have unique names system-wide, so we had to prefix them with `Netdata`.
+
+Retention can be configured per Publisher via the Event Viewer or the Registry.
+Netdata sets by default 20MiB for all of them, except `NetdataAclk` (5MiB) and `NetdataAccess` (35MiB),
+for a total of 100MiB.
+
+For WEL some registry entries are needed. Netdata automatically takes care of them when it starts.
+
+WEL does not have the problem ETW has with the percent character `%`, so Netdata logs it as-is.
+
+## Differences between ETW and WEL
+
+There are key differences between ETW and WEL.
+
+### Publishers and Providers
+**Publishers** are collections of ETW Providers. A Publisher is implied by a manifest file,
+each of which is considered a Publisher, and each manifest file can define multiple **Providers** in it.
+Other than that there is no entity related to **Publishers** in the system.
+
+**Publishers** are not defined for WEL.
+
+**Providers** are the applications or modules logging. Provider names must be unique across the system,
+for ETW and WEL together.
+
+To define a **Provider**:
+
+- ETW requires a **Publisher** manifest coupled with resources DLLs and must be registered
+ via `wevtutil` (handled by the Netdata Windows installer automatically).
+- WEL requires some registry entries and a message resources DLL (handled by Netdata automatically on startup).
+
+The Provider appears as `Source` in the Event Viewer, for both WEL and ETW.
+
+### Channels
+- **Channels** for WEL are collections of WEL Providers, (each WEL Provider is a single Stream of logs).
+- **Channels** for ETW slice the logs of each Provider into multiple Streams.
+
+WEL Channels cannot have the same name as ETW Providers. This is why Netdata's ETW provider is
+called `Netdata`, and WEL channel is called `NetdataWEL`.
+
+Despite the fact that ETW **Publishers** and WEL **Channels** are both collections of Providers,
+they are not similar. In ETW a Publisher is a collection on the publisher's Providers, but in WEL
+a Channel may include independent WEL Providers (e.g. the "Applications" Channel). Additionally,
+WEL Channels cannot include ETW Providers.
+
+### Retention
+Retention is always defined per Stream.
+
+- Retention in ETW is defined per ETW Channel (ETW Provider Stream).
+- Retention in WEL is defined per WEL Provider (each WEL Provider is a single Stream).
+
+### Messages Formatting
+- ETW supports recursive fields expansion, and therefore `%N` in fields is expanded recursively
+ (or replaced with an error message if expansion fails). Netdata replaces `%N` with `℅N` to stop
+ recursive expansion (since `%N` cannot be logged otherwise).
+- WEL performs a single field expansion, and therefore the `%` character in fields is never expanded.
+
+### Usability
+
+- ETW names all the fields and allows multiple datatypes per field, enabling log consumers to know
+ what each field means and its datatype.
+- WEL uses a simple string table for fields, and consumers need to map these string fields based on
+ their index.
diff --git a/src/libnetdata/log/log.c b/src/libnetdata/log/log.c
deleted file mode 100644
index a31127c42..000000000
--- a/src/libnetdata/log/log.c
+++ /dev/null
@@ -1,2545 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-// do not REMOVE this, it is used by systemd-journal includes to prevent saving the file, function, line of the
-// source code that makes the calls, allowing our loggers to log the lines of source code that actually log
-#define SD_JOURNAL_SUPPRESS_LOCATION
-
-#include "../libnetdata.h"
-
-#if defined(OS_WINDOWS)
-#include <windows.h>
-#endif
-
-#ifdef __FreeBSD__
-#include <sys/endian.h>
-#endif
-
-#ifdef __APPLE__
-#include <machine/endian.h>
-#endif
-
-#if !defined(ENABLE_SENTRY) && defined(HAVE_BACKTRACE)
-#include <execinfo.h>
-#endif
-
-#ifdef HAVE_SYSTEMD
-#include <systemd/sd-journal.h>
-#endif
-
-const char *program_name = "";
-
-uint64_t debug_flags = 0;
-
-#ifdef ENABLE_ACLK
-int aclklog_enabled = 0;
-#endif
-
-// ----------------------------------------------------------------------------
-
-struct nd_log_source;
-static bool nd_log_limit_reached(struct nd_log_source *source);
-
-// ----------------------------------------------------------------------------
-
-void errno_clear(void) {
- errno = 0;
-
-#if defined(OS_WINDOWS)
- SetLastError(ERROR_SUCCESS);
-#endif
-}
-
-// ----------------------------------------------------------------------------
-// logging method
-
-typedef enum __attribute__((__packed__)) {
- NDLM_DISABLED = 0,
- NDLM_DEVNULL,
- NDLM_DEFAULT,
- NDLM_JOURNAL,
- NDLM_SYSLOG,
- NDLM_STDOUT,
- NDLM_STDERR,
- NDLM_FILE,
-} ND_LOG_METHOD;
-
-static struct {
- ND_LOG_METHOD method;
- const char *name;
-} nd_log_methods[] = {
- { .method = NDLM_DISABLED, .name = "none" },
- { .method = NDLM_DEVNULL, .name = "/dev/null" },
- { .method = NDLM_DEFAULT, .name = "default" },
- { .method = NDLM_JOURNAL, .name = "journal" },
- { .method = NDLM_SYSLOG, .name = "syslog" },
- { .method = NDLM_STDOUT, .name = "stdout" },
- { .method = NDLM_STDERR, .name = "stderr" },
- { .method = NDLM_FILE, .name = "file" },
-};
-
-static ND_LOG_METHOD nd_log_method2id(const char *method) {
- if(!method || !*method)
- return NDLM_DEFAULT;
-
- size_t entries = sizeof(nd_log_methods) / sizeof(nd_log_methods[0]);
- for(size_t i = 0; i < entries ;i++) {
- if(strcmp(nd_log_methods[i].name, method) == 0)
- return nd_log_methods[i].method;
- }
-
- return NDLM_FILE;
-}
-
-static const char *nd_log_id2method(ND_LOG_METHOD method) {
- size_t entries = sizeof(nd_log_methods) / sizeof(nd_log_methods[0]);
- for(size_t i = 0; i < entries ;i++) {
- if(method == nd_log_methods[i].method)
- return nd_log_methods[i].name;
- }
-
- return "unknown";
-}
-
-#define IS_VALID_LOG_METHOD_FOR_EXTERNAL_PLUGINS(ndlo) ((ndlo) == NDLM_JOURNAL || (ndlo) == NDLM_SYSLOG || (ndlo) == NDLM_STDERR)
-
-const char *nd_log_method_for_external_plugins(const char *s) {
- if(s && *s) {
- ND_LOG_METHOD method = nd_log_method2id(s);
- if(IS_VALID_LOG_METHOD_FOR_EXTERNAL_PLUGINS(method))
- return nd_log_id2method(method);
- }
-
- return nd_log_id2method(NDLM_STDERR);
-}
-
-// ----------------------------------------------------------------------------
-// workaround strerror_r()
-
-#if defined(STRERROR_R_CHAR_P)
-// GLIBC version of strerror_r
-static const char *strerror_result(const char *a, const char *b) { (void)b; return a; }
-#elif defined(HAVE_STRERROR_R)
-// POSIX version of strerror_r
-static const char *strerror_result(int a, const char *b) { (void)a; return b; }
-#elif defined(HAVE_C__GENERIC)
-
-// what a trick!
-// http://stackoverflow.com/questions/479207/function-overloading-in-c
-static const char *strerror_result_int(int a, const char *b) { (void)a; return b; }
-static const char *strerror_result_string(const char *a, const char *b) { (void)b; return a; }
-
-#define strerror_result(a, b) _Generic((a), \
- int: strerror_result_int, \
- char *: strerror_result_string \
- )(a, b)
-
-#else
-#error "cannot detect the format of function strerror_r()"
-#endif
-
-static const char *errno2str(int errnum, char *buf, size_t size) {
- return strerror_result(strerror_r(errnum, buf, size), buf);
-}
-
-// ----------------------------------------------------------------------------
-// facilities
-//
-// sys/syslog.h (Linux)
-// sys/sys/syslog.h (FreeBSD)
-// bsd/sys/syslog.h (darwin-xnu)
-
-static struct {
- int facility;
- const char *name;
-} nd_log_facilities[] = {
- { LOG_AUTH, "auth" },
- { LOG_AUTHPRIV, "authpriv" },
- { LOG_CRON, "cron" },
- { LOG_DAEMON, "daemon" },
- { LOG_FTP, "ftp" },
- { LOG_KERN, "kern" },
- { LOG_LPR, "lpr" },
- { LOG_MAIL, "mail" },
- { LOG_NEWS, "news" },
- { LOG_SYSLOG, "syslog" },
- { LOG_USER, "user" },
- { LOG_UUCP, "uucp" },
- { LOG_LOCAL0, "local0" },
- { LOG_LOCAL1, "local1" },
- { LOG_LOCAL2, "local2" },
- { LOG_LOCAL3, "local3" },
- { LOG_LOCAL4, "local4" },
- { LOG_LOCAL5, "local5" },
- { LOG_LOCAL6, "local6" },
- { LOG_LOCAL7, "local7" },
-
-#ifdef __FreeBSD__
- { LOG_CONSOLE, "console" },
- { LOG_NTP, "ntp" },
-
- // FreeBSD does not consider 'security' as deprecated.
- { LOG_SECURITY, "security" },
-#else
- // For all other O/S 'security' is mapped to 'auth'.
- { LOG_AUTH, "security" },
-#endif
-
-#ifdef __APPLE__
- { LOG_INSTALL, "install" },
- { LOG_NETINFO, "netinfo" },
- { LOG_RAS, "ras" },
- { LOG_REMOTEAUTH, "remoteauth" },
- { LOG_LAUNCHD, "launchd" },
-
-#endif
-};
-
-static int nd_log_facility2id(const char *facility) {
- size_t entries = sizeof(nd_log_facilities) / sizeof(nd_log_facilities[0]);
- for(size_t i = 0; i < entries ;i++) {
- if(strcmp(nd_log_facilities[i].name, facility) == 0)
- return nd_log_facilities[i].facility;
- }
-
- return LOG_DAEMON;
-}
-
-static const char *nd_log_id2facility(int facility) {
- size_t entries = sizeof(nd_log_facilities) / sizeof(nd_log_facilities[0]);
- for(size_t i = 0; i < entries ;i++) {
- if(nd_log_facilities[i].facility == facility)
- return nd_log_facilities[i].name;
- }
-
- return "daemon";
-}
-
-// ----------------------------------------------------------------------------
-// priorities
-
-static struct {
- ND_LOG_FIELD_PRIORITY priority;
- const char *name;
-} nd_log_priorities[] = {
- { .priority = NDLP_EMERG, .name = "emergency" },
- { .priority = NDLP_EMERG, .name = "emerg" },
- { .priority = NDLP_ALERT, .name = "alert" },
- { .priority = NDLP_CRIT, .name = "critical" },
- { .priority = NDLP_CRIT, .name = "crit" },
- { .priority = NDLP_ERR, .name = "error" },
- { .priority = NDLP_ERR, .name = "err" },
- { .priority = NDLP_WARNING, .name = "warning" },
- { .priority = NDLP_WARNING, .name = "warn" },
- { .priority = NDLP_NOTICE, .name = "notice" },
- { .priority = NDLP_INFO, .name = NDLP_INFO_STR },
- { .priority = NDLP_DEBUG, .name = "debug" },
-};
-
-int nd_log_priority2id(const char *priority) {
- size_t entries = sizeof(nd_log_priorities) / sizeof(nd_log_priorities[0]);
- for(size_t i = 0; i < entries ;i++) {
- if(strcmp(nd_log_priorities[i].name, priority) == 0)
- return nd_log_priorities[i].priority;
- }
-
- return NDLP_INFO;
-}
-
-const char *nd_log_id2priority(ND_LOG_FIELD_PRIORITY priority) {
- size_t entries = sizeof(nd_log_priorities) / sizeof(nd_log_priorities[0]);
- for(size_t i = 0; i < entries ;i++) {
- if(priority == nd_log_priorities[i].priority)
- return nd_log_priorities[i].name;
- }
-
- return NDLP_INFO_STR;
-}
-
-// ----------------------------------------------------------------------------
-// log sources
-
-const char *nd_log_sources[] = {
- [NDLS_UNSET] = "UNSET",
- [NDLS_ACCESS] = "access",
- [NDLS_ACLK] = "aclk",
- [NDLS_COLLECTORS] = "collector",
- [NDLS_DAEMON] = "daemon",
- [NDLS_HEALTH] = "health",
- [NDLS_DEBUG] = "debug",
-};
-
-size_t nd_log_source2id(const char *source, ND_LOG_SOURCES def) {
- size_t entries = sizeof(nd_log_sources) / sizeof(nd_log_sources[0]);
- for(size_t i = 0; i < entries ;i++) {
- if(strcmp(nd_log_sources[i], source) == 0)
- return i;
- }
-
- return def;
-}
-
-
-static const char *nd_log_id2source(ND_LOG_SOURCES source) {
- size_t entries = sizeof(nd_log_sources) / sizeof(nd_log_sources[0]);
- if(source < entries)
- return nd_log_sources[source];
-
- return nd_log_sources[NDLS_COLLECTORS];
-}
-
-// ----------------------------------------------------------------------------
-// log output formats
-
-typedef enum __attribute__((__packed__)) {
- NDLF_JOURNAL,
- NDLF_LOGFMT,
- NDLF_JSON,
-} ND_LOG_FORMAT;
-
-static struct {
- ND_LOG_FORMAT format;
- const char *name;
-} nd_log_formats[] = {
- { .format = NDLF_JOURNAL, .name = "journal" },
- { .format = NDLF_LOGFMT, .name = "logfmt" },
- { .format = NDLF_JSON, .name = "json" },
-};
-
-static ND_LOG_FORMAT nd_log_format2id(const char *format) {
- if(!format || !*format)
- return NDLF_LOGFMT;
-
- size_t entries = sizeof(nd_log_formats) / sizeof(nd_log_formats[0]);
- for(size_t i = 0; i < entries ;i++) {
- if(strcmp(nd_log_formats[i].name, format) == 0)
- return nd_log_formats[i].format;
- }
-
- return NDLF_LOGFMT;
-}
-
-static const char *nd_log_id2format(ND_LOG_FORMAT format) {
- size_t entries = sizeof(nd_log_formats) / sizeof(nd_log_formats[0]);
- for(size_t i = 0; i < entries ;i++) {
- if(format == nd_log_formats[i].format)
- return nd_log_formats[i].name;
- }
-
- return "logfmt";
-}
-
-// ----------------------------------------------------------------------------
-// format dates
-
-void log_date(char *buffer, size_t len, time_t now) {
- if(unlikely(!buffer || !len))
- return;
-
- time_t t = now;
- struct tm *tmp, tmbuf;
-
- tmp = localtime_r(&t, &tmbuf);
-
- if (unlikely(!tmp)) {
- buffer[0] = '\0';
- return;
- }
-
- if (unlikely(strftime(buffer, len, "%Y-%m-%d %H:%M:%S", tmp) == 0))
- buffer[0] = '\0';
-
- buffer[len - 1] = '\0';
-}
-
-// ----------------------------------------------------------------------------
-
-struct nd_log_limit {
- usec_t started_monotonic_ut;
- uint32_t counter;
- uint32_t prevented;
-
- uint32_t throttle_period;
- uint32_t logs_per_period;
- uint32_t logs_per_period_backup;
-};
-
-#define ND_LOG_LIMITS_DEFAULT (struct nd_log_limit){ .logs_per_period = ND_LOG_DEFAULT_THROTTLE_LOGS, .logs_per_period_backup = ND_LOG_DEFAULT_THROTTLE_LOGS, .throttle_period = ND_LOG_DEFAULT_THROTTLE_PERIOD, }
-#define ND_LOG_LIMITS_UNLIMITED (struct nd_log_limit){ .logs_per_period = 0, .logs_per_period_backup = 0, .throttle_period = 0, }
-
-struct nd_log_source {
- SPINLOCK spinlock;
- ND_LOG_METHOD method;
- ND_LOG_FORMAT format;
- const char *filename;
- int fd;
- FILE *fp;
-
- ND_LOG_FIELD_PRIORITY min_priority;
- const char *pending_msg;
- struct nd_log_limit limits;
-};
-
-static struct {
- nd_uuid_t invocation_id;
-
- ND_LOG_SOURCES overwrite_process_source;
-
- struct nd_log_source sources[_NDLS_MAX];
-
- struct {
- bool initialized;
- } journal;
-
- struct {
- bool initialized;
- int fd;
- char filename[FILENAME_MAX + 1];
- } journal_direct;
-
- struct {
- bool initialized;
- int facility;
- } syslog;
-
- struct {
- SPINLOCK spinlock;
- bool initialized;
- } std_output;
-
- struct {
- SPINLOCK spinlock;
- bool initialized;
- } std_error;
-
-} nd_log = {
- .overwrite_process_source = 0,
- .journal = {
- .initialized = false,
- },
- .journal_direct = {
- .initialized = false,
- .fd = -1,
- },
- .syslog = {
- .initialized = false,
- .facility = LOG_DAEMON,
- },
- .std_output = {
- .spinlock = NETDATA_SPINLOCK_INITIALIZER,
- .initialized = false,
- },
- .std_error = {
- .spinlock = NETDATA_SPINLOCK_INITIALIZER,
- .initialized = false,
- },
- .sources = {
- [NDLS_UNSET] = {
- .spinlock = NETDATA_SPINLOCK_INITIALIZER,
- .method = NDLM_DISABLED,
- .format = NDLF_JOURNAL,
- .filename = NULL,
- .fd = -1,
- .fp = NULL,
- .min_priority = NDLP_EMERG,
- .limits = ND_LOG_LIMITS_UNLIMITED,
- },
- [NDLS_ACCESS] = {
- .spinlock = NETDATA_SPINLOCK_INITIALIZER,
- .method = NDLM_DEFAULT,
- .format = NDLF_LOGFMT,
- .filename = LOG_DIR "/access.log",
- .fd = -1,
- .fp = NULL,
- .min_priority = NDLP_DEBUG,
- .limits = ND_LOG_LIMITS_UNLIMITED,
- },
- [NDLS_ACLK] = {
- .spinlock = NETDATA_SPINLOCK_INITIALIZER,
- .method = NDLM_FILE,
- .format = NDLF_LOGFMT,
- .filename = LOG_DIR "/aclk.log",
- .fd = -1,
- .fp = NULL,
- .min_priority = NDLP_DEBUG,
- .limits = ND_LOG_LIMITS_UNLIMITED,
- },
- [NDLS_COLLECTORS] = {
- .spinlock = NETDATA_SPINLOCK_INITIALIZER,
- .method = NDLM_DEFAULT,
- .format = NDLF_LOGFMT,
- .filename = LOG_DIR "/collectors.log",
- .fd = STDERR_FILENO,
- .fp = NULL,
- .min_priority = NDLP_INFO,
- .limits = ND_LOG_LIMITS_DEFAULT,
- },
- [NDLS_DEBUG] = {
- .spinlock = NETDATA_SPINLOCK_INITIALIZER,
- .method = NDLM_DISABLED,
- .format = NDLF_LOGFMT,
- .filename = LOG_DIR "/debug.log",
- .fd = STDOUT_FILENO,
- .fp = NULL,
- .min_priority = NDLP_DEBUG,
- .limits = ND_LOG_LIMITS_UNLIMITED,
- },
- [NDLS_DAEMON] = {
- .spinlock = NETDATA_SPINLOCK_INITIALIZER,
- .method = NDLM_DEFAULT,
- .filename = LOG_DIR "/daemon.log",
- .format = NDLF_LOGFMT,
- .fd = -1,
- .fp = NULL,
- .min_priority = NDLP_INFO,
- .limits = ND_LOG_LIMITS_DEFAULT,
- },
- [NDLS_HEALTH] = {
- .spinlock = NETDATA_SPINLOCK_INITIALIZER,
- .method = NDLM_DEFAULT,
- .format = NDLF_LOGFMT,
- .filename = LOG_DIR "/health.log",
- .fd = -1,
- .fp = NULL,
- .min_priority = NDLP_DEBUG,
- .limits = ND_LOG_LIMITS_UNLIMITED,
- },
- },
-};
-
-__attribute__((constructor)) void initialize_invocation_id(void) {
- // check for a NETDATA_INVOCATION_ID
- if(uuid_parse_flexi(getenv("NETDATA_INVOCATION_ID"), nd_log.invocation_id) != 0) {
- // not found, check for systemd set INVOCATION_ID
- if(uuid_parse_flexi(getenv("INVOCATION_ID"), nd_log.invocation_id) != 0) {
- // not found, generate a new one
- uuid_generate_random(nd_log.invocation_id);
- }
- }
-
- char uuid[UUID_COMPACT_STR_LEN];
- uuid_unparse_lower_compact(nd_log.invocation_id, uuid);
- setenv("NETDATA_INVOCATION_ID", uuid, 1);
-}
-
-int nd_log_health_fd(void) {
- if(nd_log.sources[NDLS_HEALTH].method == NDLM_FILE && nd_log.sources[NDLS_HEALTH].fd != -1)
- return nd_log.sources[NDLS_HEALTH].fd;
-
- return STDERR_FILENO;
-}
-
-int nd_log_collectors_fd(void) {
- if(nd_log.sources[NDLS_COLLECTORS].method == NDLM_FILE && nd_log.sources[NDLS_COLLECTORS].fd != -1)
- return nd_log.sources[NDLS_COLLECTORS].fd;
-
- return STDERR_FILENO;
-}
-
-void nd_log_set_user_settings(ND_LOG_SOURCES source, const char *setting) {
- char buf[FILENAME_MAX + 100];
- if(setting && *setting)
- strncpyz(buf, setting, sizeof(buf) - 1);
- else
- buf[0] = '\0';
-
- struct nd_log_source *ls = &nd_log.sources[source];
- char *output = strrchr(buf, '@');
-
- if(!output)
- // all of it is the output
- output = buf;
- else {
- // we found an '@', the next char is the output
- *output = '\0';
- output++;
-
- // parse the other params
- char *remaining = buf;
- while(remaining) {
- char *value = strsep_skip_consecutive_separators(&remaining, ",");
- if (!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if (!name || !*name) continue;
-
- if(strcmp(name, "logfmt") == 0)
- ls->format = NDLF_LOGFMT;
- else if(strcmp(name, "json") == 0)
- ls->format = NDLF_JSON;
- else if(strcmp(name, "journal") == 0)
- ls->format = NDLF_JOURNAL;
- else if(strcmp(name, "level") == 0 && value && *value)
- ls->min_priority = nd_log_priority2id(value);
- else if(strcmp(name, "protection") == 0 && value && *value) {
- if(strcmp(value, "off") == 0 || strcmp(value, "none") == 0) {
- ls->limits = ND_LOG_LIMITS_UNLIMITED;
- ls->limits.counter = 0;
- ls->limits.prevented = 0;
- }
- else {
- ls->limits = ND_LOG_LIMITS_DEFAULT;
-
- char *slash = strchr(value, '/');
- if(slash) {
- *slash = '\0';
- slash++;
- ls->limits.logs_per_period = ls->limits.logs_per_period_backup = str2u(value);
- ls->limits.throttle_period = str2u(slash);
- }
- else {
- ls->limits.logs_per_period = ls->limits.logs_per_period_backup = str2u(value);
- ls->limits.throttle_period = ND_LOG_DEFAULT_THROTTLE_PERIOD;
- }
- }
- }
- else
- nd_log(NDLS_DAEMON, NDLP_ERR, "Error while parsing configuration of log source '%s'. "
- "In config '%s', '%s' is not understood.",
- nd_log_id2source(source), setting, name);
- }
- }
-
- if(!output || !*output || strcmp(output, "none") == 0 || strcmp(output, "off") == 0) {
- ls->method = NDLM_DISABLED;
- ls->filename = "/dev/null";
- }
- else if(strcmp(output, "journal") == 0) {
- ls->method = NDLM_JOURNAL;
- ls->filename = NULL;
- }
- else if(strcmp(output, "syslog") == 0) {
- ls->method = NDLM_SYSLOG;
- ls->filename = NULL;
- }
- else if(strcmp(output, "/dev/null") == 0) {
- ls->method = NDLM_DEVNULL;
- ls->filename = "/dev/null";
- }
- else if(strcmp(output, "system") == 0) {
- if(ls->fd == STDERR_FILENO) {
- ls->method = NDLM_STDERR;
- ls->filename = NULL;
- ls->fd = STDERR_FILENO;
- }
- else {
- ls->method = NDLM_STDOUT;
- ls->filename = NULL;
- ls->fd = STDOUT_FILENO;
- }
- }
- else if(strcmp(output, "stderr") == 0) {
- ls->method = NDLM_STDERR;
- ls->filename = NULL;
- ls->fd = STDERR_FILENO;
- }
- else if(strcmp(output, "stdout") == 0) {
- ls->method = NDLM_STDOUT;
- ls->filename = NULL;
- ls->fd = STDOUT_FILENO;
- }
- else {
- ls->method = NDLM_FILE;
- ls->filename = strdupz(output);
- }
-
-#if defined(NETDATA_INTERNAL_CHECKS) || defined(NETDATA_DEV_MODE)
- ls->min_priority = NDLP_DEBUG;
-#endif
-
- if(source == NDLS_COLLECTORS) {
- // set the method for the collector processes we will spawn
-
- ND_LOG_METHOD method;
- ND_LOG_FORMAT format = ls->format;
- ND_LOG_FIELD_PRIORITY priority = ls->min_priority;
-
- if(ls->method == NDLM_SYSLOG || ls->method == NDLM_JOURNAL)
- method = ls->method;
- else
- method = NDLM_STDERR;
-
- setenv("NETDATA_LOG_METHOD", nd_log_id2method(method), 1);
- setenv("NETDATA_LOG_FORMAT", nd_log_id2format(format), 1);
- setenv("NETDATA_LOG_LEVEL", nd_log_id2priority(priority), 1);
- }
-}
-
-void nd_log_set_priority_level(const char *setting) {
- if(!setting || !*setting)
- setting = "info";
-
- ND_LOG_FIELD_PRIORITY priority = nd_log_priority2id(setting);
-
-#if defined(NETDATA_INTERNAL_CHECKS) || defined(NETDATA_DEV_MODE)
- priority = NDLP_DEBUG;
-#endif
-
- for (size_t i = 0; i < _NDLS_MAX; i++) {
- if (i != NDLS_DEBUG)
- nd_log.sources[i].min_priority = priority;
- }
-
- // the right one
- setenv("NETDATA_LOG_LEVEL", nd_log_id2priority(priority), 1);
-}
-
-void nd_log_set_facility(const char *facility) {
- if(!facility || !*facility)
- facility = "daemon";
-
- nd_log.syslog.facility = nd_log_facility2id(facility);
- setenv("NETDATA_SYSLOG_FACILITY", nd_log_id2facility(nd_log.syslog.facility), 1);
-}
-
-void nd_log_set_flood_protection(size_t logs, time_t period) {
- nd_log.sources[NDLS_DAEMON].limits.logs_per_period =
- nd_log.sources[NDLS_DAEMON].limits.logs_per_period_backup;
- nd_log.sources[NDLS_COLLECTORS].limits.logs_per_period =
- nd_log.sources[NDLS_COLLECTORS].limits.logs_per_period_backup = logs;
-
- nd_log.sources[NDLS_DAEMON].limits.throttle_period =
- nd_log.sources[NDLS_COLLECTORS].limits.throttle_period = period;
-
- char buf[100];
- snprintfz(buf, sizeof(buf), "%" PRIu64, (uint64_t )period);
- setenv("NETDATA_ERRORS_THROTTLE_PERIOD", buf, 1);
- snprintfz(buf, sizeof(buf), "%" PRIu64, (uint64_t )logs);
- setenv("NETDATA_ERRORS_PER_PERIOD", buf, 1);
-}
-
-static bool nd_log_journal_systemd_init(void) {
-#ifdef HAVE_SYSTEMD
- nd_log.journal.initialized = true;
-#else
- nd_log.journal.initialized = false;
-#endif
-
- return nd_log.journal.initialized;
-}
-
-static void nd_log_journal_direct_set_env(void) {
- if(nd_log.sources[NDLS_COLLECTORS].method == NDLM_JOURNAL)
- setenv("NETDATA_SYSTEMD_JOURNAL_PATH", nd_log.journal_direct.filename, 1);
-}
-
-static bool nd_log_journal_direct_init(const char *path) {
- if(nd_log.journal_direct.initialized) {
- nd_log_journal_direct_set_env();
- return true;
- }
-
- int fd;
- char filename[FILENAME_MAX + 1];
- if(!is_path_unix_socket(path)) {
-
- journal_construct_path(filename, sizeof(filename), netdata_configured_host_prefix, "netdata");
- if (!is_path_unix_socket(filename) || (fd = journal_direct_fd(filename)) == -1) {
-
- journal_construct_path(filename, sizeof(filename), netdata_configured_host_prefix, NULL);
- if (!is_path_unix_socket(filename) || (fd = journal_direct_fd(filename)) == -1) {
-
- journal_construct_path(filename, sizeof(filename), NULL, "netdata");
- if (!is_path_unix_socket(filename) || (fd = journal_direct_fd(filename)) == -1) {
-
- journal_construct_path(filename, sizeof(filename), NULL, NULL);
- if (!is_path_unix_socket(filename) || (fd = journal_direct_fd(filename)) == -1)
- return false;
- }
- }
- }
- }
- else {
- snprintfz(filename, sizeof(filename), "%s", path);
- fd = journal_direct_fd(filename);
- }
-
- if(fd < 0)
- return false;
-
- nd_log.journal_direct.fd = fd;
- nd_log.journal_direct.initialized = true;
-
- strncpyz(nd_log.journal_direct.filename, filename, sizeof(nd_log.journal_direct.filename) - 1);
- nd_log_journal_direct_set_env();
-
- return true;
-}
-
-static void nd_log_syslog_init() {
- if(nd_log.syslog.initialized)
- return;
-
- openlog(program_name, LOG_PID, nd_log.syslog.facility);
- nd_log.syslog.initialized = true;
-}
-
-void nd_log_initialize_for_external_plugins(const char *name) {
- // if we don't run under Netdata, log to stderr,
- // otherwise, use the logging method Netdata wants us to use.
- setenv("NETDATA_LOG_METHOD", "stderr", 0);
- setenv("NETDATA_LOG_FORMAT", "logfmt", 0);
-
- nd_log.overwrite_process_source = NDLS_COLLECTORS;
- program_name = name;
-
- for(size_t i = 0; i < _NDLS_MAX ;i++) {
- nd_log.sources[i].method = STDERR_FILENO;
- nd_log.sources[i].fd = -1;
- nd_log.sources[i].fp = NULL;
- }
-
- nd_log_set_priority_level(getenv("NETDATA_LOG_LEVEL"));
- nd_log_set_facility(getenv("NETDATA_SYSLOG_FACILITY"));
-
- time_t period = 1200;
- size_t logs = 200;
- const char *s = getenv("NETDATA_ERRORS_THROTTLE_PERIOD");
- if(s && *s >= '0' && *s <= '9') {
- period = str2l(s);
- if(period < 0) period = 0;
- }
-
- s = getenv("NETDATA_ERRORS_PER_PERIOD");
- if(s && *s >= '0' && *s <= '9')
- logs = str2u(s);
-
- nd_log_set_flood_protection(logs, period);
-
- if(!netdata_configured_host_prefix) {
- s = getenv("NETDATA_HOST_PREFIX");
- if(s && *s)
- netdata_configured_host_prefix = (char *)s;
- }
-
- ND_LOG_METHOD method = nd_log_method2id(getenv("NETDATA_LOG_METHOD"));
- ND_LOG_FORMAT format = nd_log_format2id(getenv("NETDATA_LOG_FORMAT"));
-
- if(!IS_VALID_LOG_METHOD_FOR_EXTERNAL_PLUGINS(method)) {
- if(is_stderr_connected_to_journal()) {
- nd_log(NDLS_COLLECTORS, NDLP_WARNING, "NETDATA_LOG_METHOD is not set. Using journal.");
- method = NDLM_JOURNAL;
- }
- else {
- nd_log(NDLS_COLLECTORS, NDLP_WARNING, "NETDATA_LOG_METHOD is not set. Using stderr.");
- method = NDLM_STDERR;
- }
- }
-
- switch(method) {
- case NDLM_JOURNAL:
- if(!nd_log_journal_direct_init(getenv("NETDATA_SYSTEMD_JOURNAL_PATH")) ||
- !nd_log_journal_direct_init(NULL) || !nd_log_journal_systemd_init()) {
- nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to initialize journal. Using stderr.");
- method = NDLM_STDERR;
- }
- break;
-
- case NDLM_SYSLOG:
- nd_log_syslog_init();
- break;
-
- default:
- method = NDLM_STDERR;
- break;
- }
-
- for(size_t i = 0; i < _NDLS_MAX ;i++) {
- nd_log.sources[i].method = method;
- nd_log.sources[i].format = format;
- nd_log.sources[i].fd = -1;
- nd_log.sources[i].fp = NULL;
- }
-
-// nd_log(NDLS_COLLECTORS, NDLP_NOTICE, "FINAL_LOG_METHOD: %s", nd_log_id2method(method));
-}
-
-static bool nd_log_replace_existing_fd(struct nd_log_source *e, int new_fd) {
- if(new_fd == -1 || e->fd == -1 ||
- (e->fd == STDOUT_FILENO && nd_log.std_output.initialized) ||
- (e->fd == STDERR_FILENO && nd_log.std_error.initialized))
- return false;
-
- if(new_fd != e->fd) {
- int t = dup2(new_fd, e->fd);
-
- bool ret = true;
- if (t == -1) {
- netdata_log_error("Cannot dup2() new fd %d to old fd %d for '%s'", new_fd, e->fd, e->filename);
- ret = false;
- }
- else
- close(new_fd);
-
- if(e->fd == STDOUT_FILENO)
- nd_log.std_output.initialized = true;
- else if(e->fd == STDERR_FILENO)
- nd_log.std_error.initialized = true;
-
- return ret;
- }
-
- return false;
-}
-
-static void nd_log_open(struct nd_log_source *e, ND_LOG_SOURCES source) {
- if(e->method == NDLM_DEFAULT)
- nd_log_set_user_settings(source, e->filename);
-
- if((e->method == NDLM_FILE && !e->filename) ||
- (e->method == NDLM_DEVNULL && e->fd == -1))
- e->method = NDLM_DISABLED;
-
- if(e->fp)
- fflush(e->fp);
-
- switch(e->method) {
- case NDLM_SYSLOG:
- nd_log_syslog_init();
- break;
-
- case NDLM_JOURNAL:
- nd_log_journal_direct_init(NULL);
- nd_log_journal_systemd_init();
- break;
-
- case NDLM_STDOUT:
- e->fp = stdout;
- e->fd = STDOUT_FILENO;
- break;
-
- case NDLM_DISABLED:
- break;
-
- case NDLM_DEFAULT:
- case NDLM_STDERR:
- e->method = NDLM_STDERR;
- e->fp = stderr;
- e->fd = STDERR_FILENO;
- break;
-
- case NDLM_DEVNULL:
- case NDLM_FILE: {
- int fd = open(e->filename, O_WRONLY | O_APPEND | O_CREAT, 0664);
- if(fd == -1) {
- if(e->fd != STDOUT_FILENO && e->fd != STDERR_FILENO) {
- e->fd = STDERR_FILENO;
- e->method = NDLM_STDERR;
- netdata_log_error("Cannot open log file '%s'. Falling back to stderr.", e->filename);
- }
- else
- netdata_log_error("Cannot open log file '%s'. Leaving fd %d as-is.", e->filename, e->fd);
- }
- else {
- if (!nd_log_replace_existing_fd(e, fd)) {
- if(e->fd == STDOUT_FILENO || e->fd == STDERR_FILENO) {
- if(e->fd == STDOUT_FILENO)
- e->method = NDLM_STDOUT;
- else if(e->fd == STDERR_FILENO)
- e->method = NDLM_STDERR;
-
- // we have dup2() fd, so we can close the one we opened
- if(fd != STDOUT_FILENO && fd != STDERR_FILENO)
- close(fd);
- }
- else
- e->fd = fd;
- }
- }
-
- // at this point we have e->fd set properly
-
- if(e->fd == STDOUT_FILENO)
- e->fp = stdout;
- else if(e->fd == STDERR_FILENO)
- e->fp = stderr;
-
- if(!e->fp) {
- e->fp = fdopen(e->fd, "a");
- if (!e->fp) {
- netdata_log_error("Cannot fdopen() fd %d ('%s')", e->fd, e->filename);
-
- if(e->fd != STDOUT_FILENO && e->fd != STDERR_FILENO)
- close(e->fd);
-
- e->fp = stderr;
- e->fd = STDERR_FILENO;
- }
- }
- else {
- if (setvbuf(e->fp, NULL, _IOLBF, 0) != 0)
- netdata_log_error("Cannot set line buffering on fd %d ('%s')", e->fd, e->filename);
- }
- }
- break;
- }
-}
-
-static void nd_log_stdin_init(int fd, const char *filename) {
- int f = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0664);
- if(f == -1)
- return;
-
- if(f != fd) {
- dup2(f, fd);
- close(f);
- }
-}
-
-void nd_log_initialize(void) {
- nd_log_stdin_init(STDIN_FILENO, "/dev/null");
-
- for(size_t i = 0 ; i < _NDLS_MAX ; i++)
- nd_log_open(&nd_log.sources[i], i);
-}
-
-void nd_log_reopen_log_files(bool log) {
- if(log)
- netdata_log_info("Reopening all log files.");
-
- nd_log.std_output.initialized = false;
- nd_log.std_error.initialized = false;
- nd_log_initialize();
-
- if(log)
- netdata_log_info("Log files re-opened.");
-}
-
-void nd_log_reopen_log_files_for_spawn_server(void) {
- if(nd_log.syslog.initialized) {
- closelog();
- nd_log.syslog.initialized = false;
- nd_log_syslog_init();
- }
-
- if(nd_log.journal_direct.initialized) {
- close(nd_log.journal_direct.fd);
- nd_log.journal_direct.fd = -1;
- nd_log.journal_direct.initialized = false;
- nd_log_journal_direct_init(NULL);
- }
-
- nd_log.sources[NDLS_UNSET].method = NDLM_DISABLED;
- nd_log.sources[NDLS_ACCESS].method = NDLM_DISABLED;
- nd_log.sources[NDLS_ACLK].method = NDLM_DISABLED;
- nd_log.sources[NDLS_DEBUG].method = NDLM_DISABLED;
- nd_log.sources[NDLS_HEALTH].method = NDLM_DISABLED;
- nd_log_reopen_log_files(false);
-}
-
-void chown_open_file(int fd, uid_t uid, gid_t gid) {
- if(fd == -1) return;
-
- struct stat buf;
-
- if(fstat(fd, &buf) == -1) {
- netdata_log_error("Cannot fstat() fd %d", fd);
- return;
- }
-
- if((buf.st_uid != uid || buf.st_gid != gid) && S_ISREG(buf.st_mode)) {
- if(fchown(fd, uid, gid) == -1)
- netdata_log_error("Cannot fchown() fd %d.", fd);
- }
-}
-
-void nd_log_chown_log_files(uid_t uid, gid_t gid) {
- for(size_t i = 0 ; i < _NDLS_MAX ; i++) {
- if(nd_log.sources[i].fd != -1 && nd_log.sources[i].fd != STDIN_FILENO)
- chown_open_file(nd_log.sources[i].fd, uid, gid);
- }
-}
-
-// ----------------------------------------------------------------------------
-// annotators
-struct log_field;
-static void errno_annotator(BUFFER *wb, const char *key, struct log_field *lf);
-static void priority_annotator(BUFFER *wb, const char *key, struct log_field *lf);
-static void timestamp_usec_annotator(BUFFER *wb, const char *key, struct log_field *lf);
-
-#if defined(OS_WINDOWS)
-static void winerror_annotator(BUFFER *wb, const char *key, struct log_field *lf);
-#endif
-
-// ----------------------------------------------------------------------------
-
-typedef void (*annotator_t)(BUFFER *wb, const char *key, struct log_field *lf);
-
-struct log_field {
- const char *journal;
- const char *logfmt;
- annotator_t logfmt_annotator;
- struct log_stack_entry entry;
-};
-
-#define THREAD_LOG_STACK_MAX 50
-
-static __thread struct log_stack_entry *thread_log_stack_base[THREAD_LOG_STACK_MAX];
-static __thread size_t thread_log_stack_next = 0;
-
-static __thread struct log_field thread_log_fields[_NDF_MAX] = {
- // THE ORDER DEFINES THE ORDER FIELDS WILL APPEAR IN logfmt
-
- [NDF_STOP] = { // processing will not stop on this - so it is ok to be first
- .journal = NULL,
- .logfmt = NULL,
- .logfmt_annotator = NULL,
- },
- [NDF_TIMESTAMP_REALTIME_USEC] = {
- .journal = NULL,
- .logfmt = "time",
- .logfmt_annotator = timestamp_usec_annotator,
- },
- [NDF_SYSLOG_IDENTIFIER] = {
- .journal = "SYSLOG_IDENTIFIER", // standard journald field
- .logfmt = "comm",
- },
- [NDF_LOG_SOURCE] = {
- .journal = "ND_LOG_SOURCE",
- .logfmt = "source",
- },
- [NDF_PRIORITY] = {
- .journal = "PRIORITY", // standard journald field
- .logfmt = "level",
- .logfmt_annotator = priority_annotator,
- },
- [NDF_ERRNO] = {
- .journal = "ERRNO", // standard journald field
- .logfmt = "errno",
- .logfmt_annotator = errno_annotator,
- },
-#if defined(OS_WINDOWS)
- [NDF_WINERROR] = {
- .journal = "WINERROR",
- .logfmt = "winerror",
- .logfmt_annotator = winerror_annotator,
- },
-#endif
- [NDF_INVOCATION_ID] = {
- .journal = "INVOCATION_ID", // standard journald field
- .logfmt = NULL,
- },
- [NDF_LINE] = {
- .journal = "CODE_LINE", // standard journald field
- .logfmt = NULL,
- },
- [NDF_FILE] = {
- .journal = "CODE_FILE", // standard journald field
- .logfmt = NULL,
- },
- [NDF_FUNC] = {
- .journal = "CODE_FUNC", // standard journald field
- .logfmt = NULL,
- },
- [NDF_TID] = {
- .journal = "TID", // standard journald field
- .logfmt = "tid",
- },
- [NDF_THREAD_TAG] = {
- .journal = "THREAD_TAG",
- .logfmt = "thread",
- },
- [NDF_MESSAGE_ID] = {
- .journal = "MESSAGE_ID",
- .logfmt = "msg_id",
- },
- [NDF_MODULE] = {
- .journal = "ND_MODULE",
- .logfmt = "module",
- },
- [NDF_NIDL_NODE] = {
- .journal = "ND_NIDL_NODE",
- .logfmt = "node",
- },
- [NDF_NIDL_INSTANCE] = {
- .journal = "ND_NIDL_INSTANCE",
- .logfmt = "instance",
- },
- [NDF_NIDL_CONTEXT] = {
- .journal = "ND_NIDL_CONTEXT",
- .logfmt = "context",
- },
- [NDF_NIDL_DIMENSION] = {
- .journal = "ND_NIDL_DIMENSION",
- .logfmt = "dimension",
- },
- [NDF_SRC_TRANSPORT] = {
- .journal = "ND_SRC_TRANSPORT",
- .logfmt = "src_transport",
- },
- [NDF_ACCOUNT_ID] = {
- .journal = "ND_ACCOUNT_ID",
- .logfmt = "account",
- },
- [NDF_USER_NAME] = {
- .journal = "ND_USER_NAME",
- .logfmt = "user",
- },
- [NDF_USER_ROLE] = {
- .journal = "ND_USER_ROLE",
- .logfmt = "role",
- },
- [NDF_USER_ACCESS] = {
- .journal = "ND_USER_PERMISSIONS",
- .logfmt = "permissions",
- },
- [NDF_SRC_IP] = {
- .journal = "ND_SRC_IP",
- .logfmt = "src_ip",
- },
- [NDF_SRC_FORWARDED_HOST] = {
- .journal = "ND_SRC_FORWARDED_HOST",
- .logfmt = "src_forwarded_host",
- },
- [NDF_SRC_FORWARDED_FOR] = {
- .journal = "ND_SRC_FORWARDED_FOR",
- .logfmt = "src_forwarded_for",
- },
- [NDF_SRC_PORT] = {
- .journal = "ND_SRC_PORT",
- .logfmt = "src_port",
- },
- [NDF_SRC_CAPABILITIES] = {
- .journal = "ND_SRC_CAPABILITIES",
- .logfmt = "src_capabilities",
- },
- [NDF_DST_TRANSPORT] = {
- .journal = "ND_DST_TRANSPORT",
- .logfmt = "dst_transport",
- },
- [NDF_DST_IP] = {
- .journal = "ND_DST_IP",
- .logfmt = "dst_ip",
- },
- [NDF_DST_PORT] = {
- .journal = "ND_DST_PORT",
- .logfmt = "dst_port",
- },
- [NDF_DST_CAPABILITIES] = {
- .journal = "ND_DST_CAPABILITIES",
- .logfmt = "dst_capabilities",
- },
- [NDF_REQUEST_METHOD] = {
- .journal = "ND_REQUEST_METHOD",
- .logfmt = "req_method",
- },
- [NDF_RESPONSE_CODE] = {
- .journal = "ND_RESPONSE_CODE",
- .logfmt = "code",
- },
- [NDF_CONNECTION_ID] = {
- .journal = "ND_CONNECTION_ID",
- .logfmt = "conn",
- },
- [NDF_TRANSACTION_ID] = {
- .journal = "ND_TRANSACTION_ID",
- .logfmt = "transaction",
- },
- [NDF_RESPONSE_SENT_BYTES] = {
- .journal = "ND_RESPONSE_SENT_BYTES",
- .logfmt = "sent_bytes",
- },
- [NDF_RESPONSE_SIZE_BYTES] = {
- .journal = "ND_RESPONSE_SIZE_BYTES",
- .logfmt = "size_bytes",
- },
- [NDF_RESPONSE_PREPARATION_TIME_USEC] = {
- .journal = "ND_RESPONSE_PREP_TIME_USEC",
- .logfmt = "prep_ut",
- },
- [NDF_RESPONSE_SENT_TIME_USEC] = {
- .journal = "ND_RESPONSE_SENT_TIME_USEC",
- .logfmt = "sent_ut",
- },
- [NDF_RESPONSE_TOTAL_TIME_USEC] = {
- .journal = "ND_RESPONSE_TOTAL_TIME_USEC",
- .logfmt = "total_ut",
- },
- [NDF_ALERT_ID] = {
- .journal = "ND_ALERT_ID",
- .logfmt = "alert_id",
- },
- [NDF_ALERT_UNIQUE_ID] = {
- .journal = "ND_ALERT_UNIQUE_ID",
- .logfmt = "alert_unique_id",
- },
- [NDF_ALERT_TRANSITION_ID] = {
- .journal = "ND_ALERT_TRANSITION_ID",
- .logfmt = "alert_transition_id",
- },
- [NDF_ALERT_EVENT_ID] = {
- .journal = "ND_ALERT_EVENT_ID",
- .logfmt = "alert_event_id",
- },
- [NDF_ALERT_CONFIG_HASH] = {
- .journal = "ND_ALERT_CONFIG",
- .logfmt = "alert_config",
- },
- [NDF_ALERT_NAME] = {
- .journal = "ND_ALERT_NAME",
- .logfmt = "alert",
- },
- [NDF_ALERT_CLASS] = {
- .journal = "ND_ALERT_CLASS",
- .logfmt = "alert_class",
- },
- [NDF_ALERT_COMPONENT] = {
- .journal = "ND_ALERT_COMPONENT",
- .logfmt = "alert_component",
- },
- [NDF_ALERT_TYPE] = {
- .journal = "ND_ALERT_TYPE",
- .logfmt = "alert_type",
- },
- [NDF_ALERT_EXEC] = {
- .journal = "ND_ALERT_EXEC",
- .logfmt = "alert_exec",
- },
- [NDF_ALERT_RECIPIENT] = {
- .journal = "ND_ALERT_RECIPIENT",
- .logfmt = "alert_recipient",
- },
- [NDF_ALERT_VALUE] = {
- .journal = "ND_ALERT_VALUE",
- .logfmt = "alert_value",
- },
- [NDF_ALERT_VALUE_OLD] = {
- .journal = "ND_ALERT_VALUE_OLD",
- .logfmt = "alert_value_old",
- },
- [NDF_ALERT_STATUS] = {
- .journal = "ND_ALERT_STATUS",
- .logfmt = "alert_status",
- },
- [NDF_ALERT_STATUS_OLD] = {
- .journal = "ND_ALERT_STATUS_OLD",
- .logfmt = "alert_value_old",
- },
- [NDF_ALERT_UNITS] = {
- .journal = "ND_ALERT_UNITS",
- .logfmt = "alert_units",
- },
- [NDF_ALERT_SUMMARY] = {
- .journal = "ND_ALERT_SUMMARY",
- .logfmt = "alert_summary",
- },
- [NDF_ALERT_INFO] = {
- .journal = "ND_ALERT_INFO",
- .logfmt = "alert_info",
- },
- [NDF_ALERT_DURATION] = {
- .journal = "ND_ALERT_DURATION",
- .logfmt = "alert_duration",
- },
- [NDF_ALERT_NOTIFICATION_REALTIME_USEC] = {
- .journal = "ND_ALERT_NOTIFICATION_TIMESTAMP_USEC",
- .logfmt = "alert_notification_timestamp",
- .logfmt_annotator = timestamp_usec_annotator,
- },
-
- // put new items here
- // leave the request URL and the message last
-
- [NDF_REQUEST] = {
- .journal = "ND_REQUEST",
- .logfmt = "request",
- },
- [NDF_MESSAGE] = {
- .journal = "MESSAGE",
- .logfmt = "msg",
- },
-};
-
-#define THREAD_FIELDS_MAX (sizeof(thread_log_fields) / sizeof(thread_log_fields[0]))
-
-ND_LOG_FIELD_ID nd_log_field_id_by_name(const char *field, size_t len) {
- for(size_t i = 0; i < THREAD_FIELDS_MAX ;i++) {
- if(thread_log_fields[i].journal && strlen(thread_log_fields[i].journal) == len && strncmp(field, thread_log_fields[i].journal, len) == 0)
- return i;
- }
-
- return NDF_STOP;
-}
-
-void log_stack_pop(void *ptr) {
- if(!ptr) return;
-
- struct log_stack_entry *lgs = *(struct log_stack_entry (*)[])ptr;
-
- if(unlikely(!thread_log_stack_next || lgs != thread_log_stack_base[thread_log_stack_next - 1])) {
- fatal("You cannot pop in the middle of the stack, or an item not in the stack");
- return;
- }
-
- thread_log_stack_next--;
-}
-
-void log_stack_push(struct log_stack_entry *lgs) {
- if(!lgs || thread_log_stack_next >= THREAD_LOG_STACK_MAX) return;
- thread_log_stack_base[thread_log_stack_next++] = lgs;
-}
-
-// ----------------------------------------------------------------------------
-// json formatter
-
-static void nd_logger_json(BUFFER *wb, struct log_field *fields, size_t fields_max) {
-
- // --- FIELD_PARSER_VERSIONS ---
- //
- // IMPORTANT:
- // THERE ARE 6 VERSIONS OF THIS CODE
- //
- // 1. journal (direct socket API),
- // 2. journal (libsystemd API),
- // 3. logfmt,
- // 4. json,
- // 5. convert to uint64
- // 6. convert to int64
- //
- // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
-
- buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
- CLEAN_BUFFER *tmp = NULL;
-
- for (size_t i = 0; i < fields_max; i++) {
- if (!fields[i].entry.set || !fields[i].logfmt)
- continue;
-
- const char *key = fields[i].logfmt;
-
- const char *s = NULL;
- switch(fields[i].entry.type) {
- case NDFT_TXT:
- s = fields[i].entry.txt;
- break;
- case NDFT_STR:
- s = string2str(fields[i].entry.str);
- break;
- case NDFT_BFR:
- s = buffer_tostring(fields[i].entry.bfr);
- break;
- case NDFT_U64:
- buffer_json_member_add_uint64(wb, key, fields[i].entry.u64);
- break;
- case NDFT_I64:
- buffer_json_member_add_int64(wb, key, fields[i].entry.i64);
- break;
- case NDFT_DBL:
- buffer_json_member_add_double(wb, key, fields[i].entry.dbl);
- break;
- case NDFT_UUID:
- if(!uuid_is_null(*fields[i].entry.uuid)) {
- char u[UUID_COMPACT_STR_LEN];
- uuid_unparse_lower_compact(*fields[i].entry.uuid, u);
- buffer_json_member_add_string(wb, key, u);
- }
- break;
- case NDFT_CALLBACK: {
- if(!tmp)
- tmp = buffer_create(1024, NULL);
- else
- buffer_flush(tmp);
- if(fields[i].entry.cb.formatter(tmp, fields[i].entry.cb.formatter_data))
- s = buffer_tostring(tmp);
- else
- s = NULL;
- }
- break;
- default:
- s = "UNHANDLED";
- break;
- }
-
- if(s && *s)
- buffer_json_member_add_string(wb, key, s);
- }
-
- buffer_json_finalize(wb);
-}
-
-// ----------------------------------------------------------------------------
-// logfmt formatter
-
-
-static int64_t log_field_to_int64(struct log_field *lf) {
-
- // --- FIELD_PARSER_VERSIONS ---
- //
- // IMPORTANT:
- // THERE ARE 6 VERSIONS OF THIS CODE
- //
- // 1. journal (direct socket API),
- // 2. journal (libsystemd API),
- // 3. logfmt,
- // 4. json,
- // 5. convert to uint64
- // 6. convert to int64
- //
- // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
-
- CLEAN_BUFFER *tmp = NULL;
- const char *s = NULL;
-
- switch(lf->entry.type) {
- case NDFT_UUID:
- case NDFT_UNSET:
- return 0;
-
- case NDFT_TXT:
- s = lf->entry.txt;
- break;
-
- case NDFT_STR:
- s = string2str(lf->entry.str);
- break;
-
- case NDFT_BFR:
- s = buffer_tostring(lf->entry.bfr);
- break;
-
- case NDFT_CALLBACK:
- tmp = buffer_create(0, NULL);
-
- if(lf->entry.cb.formatter(tmp, lf->entry.cb.formatter_data))
- s = buffer_tostring(tmp);
- else
- s = NULL;
- break;
-
- case NDFT_U64:
- return (int64_t)lf->entry.u64;
-
- case NDFT_I64:
- return (int64_t)lf->entry.i64;
-
- case NDFT_DBL:
- return (int64_t)lf->entry.dbl;
- }
-
- if(s && *s)
- return str2ll(s, NULL);
-
- return 0;
-}
-
-static uint64_t log_field_to_uint64(struct log_field *lf) {
-
- // --- FIELD_PARSER_VERSIONS ---
- //
- // IMPORTANT:
- // THERE ARE 6 VERSIONS OF THIS CODE
- //
- // 1. journal (direct socket API),
- // 2. journal (libsystemd API),
- // 3. logfmt,
- // 4. json,
- // 5. convert to uint64
- // 6. convert to int64
- //
- // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
-
- CLEAN_BUFFER *tmp = NULL;
- const char *s = NULL;
-
- switch(lf->entry.type) {
- case NDFT_UUID:
- case NDFT_UNSET:
- return 0;
-
- case NDFT_TXT:
- s = lf->entry.txt;
- break;
-
- case NDFT_STR:
- s = string2str(lf->entry.str);
- break;
-
- case NDFT_BFR:
- s = buffer_tostring(lf->entry.bfr);
- break;
-
- case NDFT_CALLBACK:
- tmp = buffer_create(0, NULL);
-
- if(lf->entry.cb.formatter(tmp, lf->entry.cb.formatter_data))
- s = buffer_tostring(tmp);
- else
- s = NULL;
- break;
-
- case NDFT_U64:
- return lf->entry.u64;
-
- case NDFT_I64:
- return lf->entry.i64;
-
- case NDFT_DBL:
- return (uint64_t) lf->entry.dbl;
- }
-
- if(s && *s)
- return str2uint64_t(s, NULL);
-
- return 0;
-}
-
-static void timestamp_usec_annotator(BUFFER *wb, const char *key, struct log_field *lf) {
- usec_t ut = log_field_to_uint64(lf);
-
- if(!ut)
- return;
-
- char datetime[RFC3339_MAX_LENGTH];
- rfc3339_datetime_ut(datetime, sizeof(datetime), ut, 3, false);
-
- if(buffer_strlen(wb))
- buffer_fast_strcat(wb, " ", 1);
-
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=", 1);
- buffer_json_strcat(wb, datetime);
-}
-
-static void errno_annotator(BUFFER *wb, const char *key, struct log_field *lf) {
- int64_t errnum = log_field_to_int64(lf);
-
- if(errnum == 0)
- return;
-
- char buf[1024];
- const char *s = errno2str((int)errnum, buf, sizeof(buf));
-
- if(buffer_strlen(wb))
- buffer_fast_strcat(wb, " ", 1);
-
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=\"", 2);
- buffer_print_int64(wb, errnum);
- buffer_fast_strcat(wb, ", ", 2);
- buffer_json_strcat(wb, s);
- buffer_fast_strcat(wb, "\"", 1);
-}
-
-#if defined(OS_WINDOWS)
-static void winerror_annotator(BUFFER *wb, const char *key, struct log_field *lf) {
- DWORD errnum = log_field_to_uint64(lf);
-
- if(errnum == 0)
- return;
-
- char buf[1024];
- DWORD size = FormatMessageA(
- FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL,
- errnum,
- MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
- buf,
- (DWORD)(sizeof(buf) - 1),
- NULL
- );
- if(size > 0) {
- // remove \r\n at the end
- while(size > 0 && (buf[size - 1] == '\r' || buf[size - 1] == '\n'))
- buf[--size] = '\0';
- }
- else
- size = snprintf(buf, sizeof(buf) - 1, "unknown error code");
-
- buf[size] = '\0';
-
- if(buffer_strlen(wb))
- buffer_fast_strcat(wb, " ", 1);
-
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=\"", 2);
- buffer_print_int64(wb, errnum);
- buffer_fast_strcat(wb, ", ", 2);
- buffer_json_strcat(wb, buf);
- buffer_fast_strcat(wb, "\"", 1);
-}
-#endif
-
-static void priority_annotator(BUFFER *wb, const char *key, struct log_field *lf) {
- uint64_t pri = log_field_to_uint64(lf);
-
- if(buffer_strlen(wb))
- buffer_fast_strcat(wb, " ", 1);
-
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=", 1);
- buffer_strcat(wb, nd_log_id2priority(pri));
-}
-
-static bool needs_quotes_for_logfmt(const char *s)
-{
- static bool safe_for_logfmt[256] = {
- [' '] = true, ['!'] = true, ['"'] = false, ['#'] = true, ['$'] = true, ['%'] = true, ['&'] = true,
- ['\''] = true, ['('] = true, [')'] = true, ['*'] = true, ['+'] = true, [','] = true, ['-'] = true,
- ['.'] = true, ['/'] = true, ['0'] = true, ['1'] = true, ['2'] = true, ['3'] = true, ['4'] = true,
- ['5'] = true, ['6'] = true, ['7'] = true, ['8'] = true, ['9'] = true, [':'] = true, [';'] = true,
- ['<'] = true, ['='] = true, ['>'] = true, ['?'] = true, ['@'] = true, ['A'] = true, ['B'] = true,
- ['C'] = true, ['D'] = true, ['E'] = true, ['F'] = true, ['G'] = true, ['H'] = true, ['I'] = true,
- ['J'] = true, ['K'] = true, ['L'] = true, ['M'] = true, ['N'] = true, ['O'] = true, ['P'] = true,
- ['Q'] = true, ['R'] = true, ['S'] = true, ['T'] = true, ['U'] = true, ['V'] = true, ['W'] = true,
- ['X'] = true, ['Y'] = true, ['Z'] = true, ['['] = true, ['\\'] = false, [']'] = true, ['^'] = true,
- ['_'] = true, ['`'] = true, ['a'] = true, ['b'] = true, ['c'] = true, ['d'] = true, ['e'] = true,
- ['f'] = true, ['g'] = true, ['h'] = true, ['i'] = true, ['j'] = true, ['k'] = true, ['l'] = true,
- ['m'] = true, ['n'] = true, ['o'] = true, ['p'] = true, ['q'] = true, ['r'] = true, ['s'] = true,
- ['t'] = true, ['u'] = true, ['v'] = true, ['w'] = true, ['x'] = true, ['y'] = true, ['z'] = true,
- ['{'] = true, ['|'] = true, ['}'] = true, ['~'] = true, [0x7f] = true,
- };
-
- if(!*s)
- return true;
-
- while(*s) {
- if(*s == '=' || isspace((uint8_t)*s) || !safe_for_logfmt[(uint8_t)*s])
- return true;
-
- s++;
- }
-
- return false;
-}
-
-static void string_to_logfmt(BUFFER *wb, const char *s)
-{
- bool spaces = needs_quotes_for_logfmt(s);
-
- if(spaces)
- buffer_fast_strcat(wb, "\"", 1);
-
- buffer_json_strcat(wb, s);
-
- if(spaces)
- buffer_fast_strcat(wb, "\"", 1);
-}
-
-static void nd_logger_logfmt(BUFFER *wb, struct log_field *fields, size_t fields_max)
-{
-
- // --- FIELD_PARSER_VERSIONS ---
- //
- // IMPORTANT:
- // THERE ARE 6 VERSIONS OF THIS CODE
- //
- // 1. journal (direct socket API),
- // 2. journal (libsystemd API),
- // 3. logfmt,
- // 4. json,
- // 5. convert to uint64
- // 6. convert to int64
- //
- // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
-
- CLEAN_BUFFER *tmp = NULL;
-
- for (size_t i = 0; i < fields_max; i++) {
- if (!fields[i].entry.set || !fields[i].logfmt)
- continue;
-
- const char *key = fields[i].logfmt;
-
- if(fields[i].logfmt_annotator)
- fields[i].logfmt_annotator(wb, key, &fields[i]);
- else {
- if(buffer_strlen(wb))
- buffer_fast_strcat(wb, " ", 1);
-
- switch(fields[i].entry.type) {
- case NDFT_TXT:
- if(*fields[i].entry.txt) {
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=", 1);
- string_to_logfmt(wb, fields[i].entry.txt);
- }
- break;
- case NDFT_STR:
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=", 1);
- string_to_logfmt(wb, string2str(fields[i].entry.str));
- break;
- case NDFT_BFR:
- if(buffer_strlen(fields[i].entry.bfr)) {
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=", 1);
- string_to_logfmt(wb, buffer_tostring(fields[i].entry.bfr));
- }
- break;
- case NDFT_U64:
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=", 1);
- buffer_print_uint64(wb, fields[i].entry.u64);
- break;
- case NDFT_I64:
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=", 1);
- buffer_print_int64(wb, fields[i].entry.i64);
- break;
- case NDFT_DBL:
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=", 1);
- buffer_print_netdata_double(wb, fields[i].entry.dbl);
- break;
- case NDFT_UUID:
- if(!uuid_is_null(*fields[i].entry.uuid)) {
- char u[UUID_COMPACT_STR_LEN];
- uuid_unparse_lower_compact(*fields[i].entry.uuid, u);
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=", 1);
- buffer_fast_strcat(wb, u, sizeof(u) - 1);
- }
- break;
- case NDFT_CALLBACK: {
- if(!tmp)
- tmp = buffer_create(1024, NULL);
- else
- buffer_flush(tmp);
- if(fields[i].entry.cb.formatter(tmp, fields[i].entry.cb.formatter_data)) {
- buffer_strcat(wb, key);
- buffer_fast_strcat(wb, "=", 1);
- string_to_logfmt(wb, buffer_tostring(tmp));
- }
- }
- break;
- default:
- buffer_strcat(wb, "UNHANDLED");
- break;
- }
- }
- }
-}
-
-// ----------------------------------------------------------------------------
-// journal logger
-
-bool nd_log_journal_socket_available(void) {
- if(netdata_configured_host_prefix && *netdata_configured_host_prefix) {
- char filename[FILENAME_MAX + 1];
-
- snprintfz(filename, sizeof(filename), "%s%s",
- netdata_configured_host_prefix, "/run/systemd/journal/socket");
-
- if(is_path_unix_socket(filename))
- return true;
- }
-
- return is_path_unix_socket("/run/systemd/journal/socket");
-}
-
-static bool nd_logger_journal_libsystemd(struct log_field *fields __maybe_unused, size_t fields_max __maybe_unused) {
-#ifdef HAVE_SYSTEMD
-
- // --- FIELD_PARSER_VERSIONS ---
- //
- // IMPORTANT:
- // THERE ARE 6 VERSIONS OF THIS CODE
- //
- // 1. journal (direct socket API),
- // 2. journal (libsystemd API),
- // 3. logfmt,
- // 4. json,
- // 5. convert to uint64
- // 6. convert to int64
- //
- // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
-
- struct iovec iov[fields_max];
- int iov_count = 0;
-
- memset(iov, 0, sizeof(iov));
-
- CLEAN_BUFFER *tmp = NULL;
-
- for (size_t i = 0; i < fields_max; i++) {
- if (!fields[i].entry.set || !fields[i].journal)
- continue;
-
- const char *key = fields[i].journal;
- char *value = NULL;
- int rc = 0;
- switch (fields[i].entry.type) {
- case NDFT_TXT:
- if(*fields[i].entry.txt)
- rc = asprintf(&value, "%s=%s", key, fields[i].entry.txt);
- break;
- case NDFT_STR:
- rc = asprintf(&value, "%s=%s", key, string2str(fields[i].entry.str));
- break;
- case NDFT_BFR:
- if(buffer_strlen(fields[i].entry.bfr))
- rc = asprintf(&value, "%s=%s", key, buffer_tostring(fields[i].entry.bfr));
- break;
- case NDFT_U64:
- rc = asprintf(&value, "%s=%" PRIu64, key, fields[i].entry.u64);
- break;
- case NDFT_I64:
- rc = asprintf(&value, "%s=%" PRId64, key, fields[i].entry.i64);
- break;
- case NDFT_DBL:
- rc = asprintf(&value, "%s=%f", key, fields[i].entry.dbl);
- break;
- case NDFT_UUID:
- if(!uuid_is_null(*fields[i].entry.uuid)) {
- char u[UUID_COMPACT_STR_LEN];
- uuid_unparse_lower_compact(*fields[i].entry.uuid, u);
- rc = asprintf(&value, "%s=%s", key, u);
- }
- break;
- case NDFT_CALLBACK: {
- if(!tmp)
- tmp = buffer_create(1024, NULL);
- else
- buffer_flush(tmp);
- if(fields[i].entry.cb.formatter(tmp, fields[i].entry.cb.formatter_data))
- rc = asprintf(&value, "%s=%s", key, buffer_tostring(tmp));
- }
- break;
- default:
- rc = asprintf(&value, "%s=%s", key, "UNHANDLED");
- break;
- }
-
- if (rc != -1 && value) {
- iov[iov_count].iov_base = value;
- iov[iov_count].iov_len = strlen(value);
- iov_count++;
- }
- }
-
- int r = sd_journal_sendv(iov, iov_count);
-
- // Clean up allocated memory
- for (int i = 0; i < iov_count; i++) {
- if (iov[i].iov_base != NULL) {
- free(iov[i].iov_base);
- }
- }
-
- return r == 0;
-#else
- return false;
-#endif
-}
-
-static bool nd_logger_journal_direct(struct log_field *fields, size_t fields_max) {
- if(!nd_log.journal_direct.initialized)
- return false;
-
- // --- FIELD_PARSER_VERSIONS ---
- //
- // IMPORTANT:
- // THERE ARE 6 VERSIONS OF THIS CODE
- //
- // 1. journal (direct socket API),
- // 2. journal (libsystemd API),
- // 3. logfmt,
- // 4. json,
- // 5. convert to uint64
- // 6. convert to int64
- //
- // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
-
- CLEAN_BUFFER *wb = buffer_create(4096, NULL);
- CLEAN_BUFFER *tmp = NULL;
-
- for (size_t i = 0; i < fields_max; i++) {
- if (!fields[i].entry.set || !fields[i].journal)
- continue;
-
- const char *key = fields[i].journal;
-
- const char *s = NULL;
- switch(fields[i].entry.type) {
- case NDFT_TXT:
- s = fields[i].entry.txt;
- break;
- case NDFT_STR:
- s = string2str(fields[i].entry.str);
- break;
- case NDFT_BFR:
- s = buffer_tostring(fields[i].entry.bfr);
- break;
- case NDFT_U64:
- buffer_strcat(wb, key);
- buffer_putc(wb, '=');
- buffer_print_uint64(wb, fields[i].entry.u64);
- buffer_putc(wb, '\n');
- break;
- case NDFT_I64:
- buffer_strcat(wb, key);
- buffer_putc(wb, '=');
- buffer_print_int64(wb, fields[i].entry.i64);
- buffer_putc(wb, '\n');
- break;
- case NDFT_DBL:
- buffer_strcat(wb, key);
- buffer_putc(wb, '=');
- buffer_print_netdata_double(wb, fields[i].entry.dbl);
- buffer_putc(wb, '\n');
- break;
- case NDFT_UUID:
- if(!uuid_is_null(*fields[i].entry.uuid)) {
- char u[UUID_COMPACT_STR_LEN];
- uuid_unparse_lower_compact(*fields[i].entry.uuid, u);
- buffer_strcat(wb, key);
- buffer_putc(wb, '=');
- buffer_fast_strcat(wb, u, sizeof(u) - 1);
- buffer_putc(wb, '\n');
- }
- break;
- case NDFT_CALLBACK: {
- if(!tmp)
- tmp = buffer_create(1024, NULL);
- else
- buffer_flush(tmp);
- if(fields[i].entry.cb.formatter(tmp, fields[i].entry.cb.formatter_data))
- s = buffer_tostring(tmp);
- else
- s = NULL;
- }
- break;
- default:
- s = "UNHANDLED";
- break;
- }
-
- if(s && *s) {
- buffer_strcat(wb, key);
- if(!strchr(s, '\n')) {
- buffer_putc(wb, '=');
- buffer_strcat(wb, s);
- buffer_putc(wb, '\n');
- }
- else {
- buffer_putc(wb, '\n');
- size_t size = strlen(s);
- uint64_t le_size = htole64(size);
- buffer_memcat(wb, &le_size, sizeof(le_size));
- buffer_memcat(wb, s, size);
- buffer_putc(wb, '\n');
- }
- }
- }
-
- return journal_direct_send(nd_log.journal_direct.fd, buffer_tostring(wb), buffer_strlen(wb));
-}
-
-// ----------------------------------------------------------------------------
-// syslog logger - uses logfmt
-
-static bool nd_logger_syslog(int priority, ND_LOG_FORMAT format __maybe_unused, struct log_field *fields, size_t fields_max) {
- CLEAN_BUFFER *wb = buffer_create(1024, NULL);
-
- nd_logger_logfmt(wb, fields, fields_max);
- syslog(priority, "%s", buffer_tostring(wb));
-
- return true;
-}
-
-// ----------------------------------------------------------------------------
-// file logger - uses logfmt
-
-static bool nd_logger_file(FILE *fp, ND_LOG_FORMAT format, struct log_field *fields, size_t fields_max) {
- BUFFER *wb = buffer_create(1024, NULL);
-
- if(format == NDLF_JSON)
- nd_logger_json(wb, fields, fields_max);
- else
- nd_logger_logfmt(wb, fields, fields_max);
-
- int r = fprintf(fp, "%s\n", buffer_tostring(wb));
- fflush(fp);
-
- buffer_free(wb);
- return r > 0;
-}
-
-// ----------------------------------------------------------------------------
-// logger router
-
-static ND_LOG_METHOD nd_logger_select_output(ND_LOG_SOURCES source, FILE **fpp, SPINLOCK **spinlock) {
- *spinlock = NULL;
- ND_LOG_METHOD output = nd_log.sources[source].method;
-
- switch(output) {
- case NDLM_JOURNAL:
- if(unlikely(!nd_log.journal_direct.initialized && !nd_log.journal.initialized)) {
- output = NDLM_FILE;
- *fpp = stderr;
- *spinlock = &nd_log.std_error.spinlock;
- }
- else {
- *fpp = NULL;
- *spinlock = NULL;
- }
- break;
-
- case NDLM_SYSLOG:
- if(unlikely(!nd_log.syslog.initialized)) {
- output = NDLM_FILE;
- *spinlock = &nd_log.std_error.spinlock;
- *fpp = stderr;
- }
- else {
- *spinlock = NULL;
- *fpp = NULL;
- }
- break;
-
- case NDLM_FILE:
- if(!nd_log.sources[source].fp) {
- *fpp = stderr;
- *spinlock = &nd_log.std_error.spinlock;
- }
- else {
- *fpp = nd_log.sources[source].fp;
- *spinlock = &nd_log.sources[source].spinlock;
- }
- break;
-
- case NDLM_STDOUT:
- output = NDLM_FILE;
- *fpp = stdout;
- *spinlock = &nd_log.std_output.spinlock;
- break;
-
- default:
- case NDLM_DEFAULT:
- case NDLM_STDERR:
- output = NDLM_FILE;
- *fpp = stderr;
- *spinlock = &nd_log.std_error.spinlock;
- break;
-
- case NDLM_DISABLED:
- case NDLM_DEVNULL:
- output = NDLM_DISABLED;
- *fpp = NULL;
- *spinlock = NULL;
- break;
- }
-
- return output;
-}
-
-// ----------------------------------------------------------------------------
-// high level logger
-
-static void nd_logger_log_fields(SPINLOCK *spinlock, FILE *fp, bool limit, ND_LOG_FIELD_PRIORITY priority,
- ND_LOG_METHOD output, struct nd_log_source *source,
- struct log_field *fields, size_t fields_max) {
- if(spinlock)
- spinlock_lock(spinlock);
-
- // check the limits
- if(limit && nd_log_limit_reached(source))
- goto cleanup;
-
- if(output == NDLM_JOURNAL) {
- if(!nd_logger_journal_direct(fields, fields_max) && !nd_logger_journal_libsystemd(fields, fields_max)) {
- // we can't log to journal, let's log to stderr
- if(spinlock)
- spinlock_unlock(spinlock);
-
- output = NDLM_FILE;
- spinlock = &nd_log.std_error.spinlock;
- fp = stderr;
-
- if(spinlock)
- spinlock_lock(spinlock);
- }
- }
-
- if(output == NDLM_SYSLOG)
- nd_logger_syslog(priority, source->format, fields, fields_max);
-
- if(output == NDLM_FILE)
- nd_logger_file(fp, source->format, fields, fields_max);
-
-
-cleanup:
- if(spinlock)
- spinlock_unlock(spinlock);
-}
-
-static void nd_logger_unset_all_thread_fields(void) {
- size_t fields_max = THREAD_FIELDS_MAX;
- for(size_t i = 0; i < fields_max ; i++)
- thread_log_fields[i].entry.set = false;
-}
-
-static void nd_logger_merge_log_stack_to_thread_fields(void) {
- for(size_t c = 0; c < thread_log_stack_next ;c++) {
- struct log_stack_entry *lgs = thread_log_stack_base[c];
-
- for(size_t i = 0; lgs[i].id != NDF_STOP ; i++) {
- if(lgs[i].id >= _NDF_MAX || !lgs[i].set)
- continue;
-
- struct log_stack_entry *e = &lgs[i];
- ND_LOG_STACK_FIELD_TYPE type = lgs[i].type;
-
- // do not add empty / unset fields
- if((type == NDFT_TXT && (!e->txt || !*e->txt)) ||
- (type == NDFT_BFR && (!e->bfr || !buffer_strlen(e->bfr))) ||
- (type == NDFT_STR && !e->str) ||
- (type == NDFT_UUID && (!e->uuid || uuid_is_null(*e->uuid))) ||
- (type == NDFT_CALLBACK && !e->cb.formatter) ||
- type == NDFT_UNSET)
- continue;
-
- thread_log_fields[lgs[i].id].entry = *e;
- }
- }
-}
-
-static void nd_logger(const char *file, const char *function, const unsigned long line,
- ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, bool limit,
- int saved_errno, size_t saved_winerror __maybe_unused, const char *fmt, va_list ap) {
-
- SPINLOCK *spinlock;
- FILE *fp;
- ND_LOG_METHOD output = nd_logger_select_output(source, &fp, &spinlock);
- if(output != NDLM_FILE && output != NDLM_JOURNAL && output != NDLM_SYSLOG)
- return;
-
- // mark all fields as unset
- nd_logger_unset_all_thread_fields();
-
- // flatten the log stack into the fields
- nd_logger_merge_log_stack_to_thread_fields();
-
- // set the common fields that are automatically set by the logging subsystem
-
- if(likely(!thread_log_fields[NDF_INVOCATION_ID].entry.set))
- thread_log_fields[NDF_INVOCATION_ID].entry = ND_LOG_FIELD_UUID(NDF_INVOCATION_ID, &nd_log.invocation_id);
-
- if(likely(!thread_log_fields[NDF_LOG_SOURCE].entry.set))
- thread_log_fields[NDF_LOG_SOURCE].entry = ND_LOG_FIELD_TXT(NDF_LOG_SOURCE, nd_log_id2source(source));
- else {
- ND_LOG_SOURCES src = source;
-
- if(thread_log_fields[NDF_LOG_SOURCE].entry.type == NDFT_TXT)
- src = nd_log_source2id(thread_log_fields[NDF_LOG_SOURCE].entry.txt, source);
- else if(thread_log_fields[NDF_LOG_SOURCE].entry.type == NDFT_U64)
- src = thread_log_fields[NDF_LOG_SOURCE].entry.u64;
-
- if(src != source && src < _NDLS_MAX) {
- source = src;
- output = nd_logger_select_output(source, &fp, &spinlock);
- if(output != NDLM_FILE && output != NDLM_JOURNAL && output != NDLM_SYSLOG)
- return;
- }
- }
-
- if(likely(!thread_log_fields[NDF_SYSLOG_IDENTIFIER].entry.set))
- thread_log_fields[NDF_SYSLOG_IDENTIFIER].entry = ND_LOG_FIELD_TXT(NDF_SYSLOG_IDENTIFIER, program_name);
-
- if(likely(!thread_log_fields[NDF_LINE].entry.set)) {
- thread_log_fields[NDF_LINE].entry = ND_LOG_FIELD_U64(NDF_LINE, line);
- thread_log_fields[NDF_FILE].entry = ND_LOG_FIELD_TXT(NDF_FILE, file);
- thread_log_fields[NDF_FUNC].entry = ND_LOG_FIELD_TXT(NDF_FUNC, function);
- }
-
- if(likely(!thread_log_fields[NDF_PRIORITY].entry.set)) {
- thread_log_fields[NDF_PRIORITY].entry = ND_LOG_FIELD_U64(NDF_PRIORITY, priority);
- }
-
- if(likely(!thread_log_fields[NDF_TID].entry.set))
- thread_log_fields[NDF_TID].entry = ND_LOG_FIELD_U64(NDF_TID, gettid_cached());
-
- if(likely(!thread_log_fields[NDF_THREAD_TAG].entry.set)) {
- const char *thread_tag = nd_thread_tag();
- thread_log_fields[NDF_THREAD_TAG].entry = ND_LOG_FIELD_TXT(NDF_THREAD_TAG, thread_tag);
-
- // TODO: fix the ND_MODULE in logging by setting proper module name in threads
-// if(!thread_log_fields[NDF_MODULE].entry.set)
-// thread_log_fields[NDF_MODULE].entry = ND_LOG_FIELD_CB(NDF_MODULE, thread_tag_to_module, (void *)thread_tag);
- }
-
- if(likely(!thread_log_fields[NDF_TIMESTAMP_REALTIME_USEC].entry.set))
- thread_log_fields[NDF_TIMESTAMP_REALTIME_USEC].entry = ND_LOG_FIELD_U64(NDF_TIMESTAMP_REALTIME_USEC, now_realtime_usec());
-
- if(saved_errno != 0 && !thread_log_fields[NDF_ERRNO].entry.set)
- thread_log_fields[NDF_ERRNO].entry = ND_LOG_FIELD_I64(NDF_ERRNO, saved_errno);
-
-#if defined(OS_WINDOWS)
- if(saved_winerror != 0 && !thread_log_fields[NDF_WINERROR].entry.set)
- thread_log_fields[NDF_WINERROR].entry = ND_LOG_FIELD_U64(NDF_WINERROR, saved_winerror);
-#endif
-
- CLEAN_BUFFER *wb = NULL;
- if(fmt && !thread_log_fields[NDF_MESSAGE].entry.set) {
- wb = buffer_create(1024, NULL);
- buffer_vsprintf(wb, fmt, ap);
- thread_log_fields[NDF_MESSAGE].entry = ND_LOG_FIELD_TXT(NDF_MESSAGE, buffer_tostring(wb));
- }
-
- nd_logger_log_fields(spinlock, fp, limit, priority, output, &nd_log.sources[source],
- thread_log_fields, THREAD_FIELDS_MAX);
-
- if(nd_log.sources[source].pending_msg) {
- // log a pending message
-
- nd_logger_unset_all_thread_fields();
-
- thread_log_fields[NDF_TIMESTAMP_REALTIME_USEC].entry = (struct log_stack_entry){
- .set = true,
- .type = NDFT_U64,
- .u64 = now_realtime_usec(),
- };
-
- thread_log_fields[NDF_LOG_SOURCE].entry = (struct log_stack_entry){
- .set = true,
- .type = NDFT_TXT,
- .txt = nd_log_id2source(source),
- };
-
- thread_log_fields[NDF_SYSLOG_IDENTIFIER].entry = (struct log_stack_entry){
- .set = true,
- .type = NDFT_TXT,
- .txt = program_name,
- };
-
- thread_log_fields[NDF_MESSAGE].entry = (struct log_stack_entry){
- .set = true,
- .type = NDFT_TXT,
- .txt = nd_log.sources[source].pending_msg,
- };
-
- nd_logger_log_fields(spinlock, fp, false, priority, output,
- &nd_log.sources[source],
- thread_log_fields, THREAD_FIELDS_MAX);
-
- freez((void *)nd_log.sources[source].pending_msg);
- nd_log.sources[source].pending_msg = NULL;
- }
-
- errno_clear();
-}
-
-static ND_LOG_SOURCES nd_log_validate_source(ND_LOG_SOURCES source) {
- if(source >= _NDLS_MAX)
- source = NDLS_DAEMON;
-
- if(nd_log.overwrite_process_source)
- source = nd_log.overwrite_process_source;
-
- return source;
-}
-
-// ----------------------------------------------------------------------------
-// public API for loggers
-
-void netdata_logger(ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, const char *file, const char *function, unsigned long line, const char *fmt, ... )
-{
- int saved_errno = errno;
-
- size_t saved_winerror = 0;
-#if defined(OS_WINDOWS)
- saved_winerror = GetLastError();
-#endif
-
- source = nd_log_validate_source(source);
-
- if (source != NDLS_DEBUG && priority > nd_log.sources[source].min_priority)
- return;
-
- va_list args;
- va_start(args, fmt);
- nd_logger(file, function, line, source, priority,
- source == NDLS_DAEMON || source == NDLS_COLLECTORS,
- saved_errno, saved_winerror, fmt, args);
- va_end(args);
-}
-
-void netdata_logger_with_limit(ERROR_LIMIT *erl, ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, const char *fmt, ... ) {
- int saved_errno = errno;
-
- size_t saved_winerror = 0;
-#if defined(OS_WINDOWS)
- saved_winerror = GetLastError();
-#endif
-
- source = nd_log_validate_source(source);
-
- if (source != NDLS_DEBUG && priority > nd_log.sources[source].min_priority)
- return;
-
- if(erl->sleep_ut)
- sleep_usec(erl->sleep_ut);
-
- spinlock_lock(&erl->spinlock);
-
- erl->count++;
- time_t now = now_boottime_sec();
- if(now - erl->last_logged < erl->log_every) {
- spinlock_unlock(&erl->spinlock);
- return;
- }
-
- spinlock_unlock(&erl->spinlock);
-
- va_list args;
- va_start(args, fmt);
- nd_logger(file, function, line, source, priority,
- source == NDLS_DAEMON || source == NDLS_COLLECTORS,
- saved_errno, saved_winerror, fmt, args);
- va_end(args);
- erl->last_logged = now;
- erl->count = 0;
-}
-
-void netdata_logger_fatal( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) {
- int saved_errno = errno;
-
- size_t saved_winerror = 0;
-#if defined(OS_WINDOWS)
- saved_winerror = GetLastError();
-#endif
-
- ND_LOG_SOURCES source = NDLS_DAEMON;
- source = nd_log_validate_source(source);
-
- va_list args;
- va_start(args, fmt);
- nd_logger(file, function, line, source, NDLP_ALERT, true, saved_errno, saved_winerror, fmt, args);
- va_end(args);
-
- char date[LOG_DATE_LENGTH];
- log_date(date, LOG_DATE_LENGTH, now_realtime_sec());
-
- char action_data[70+1];
- snprintfz(action_data, 70, "%04lu@%-10.10s:%-15.15s/%d", line, file, function, saved_errno);
-
- const char *thread_tag = nd_thread_tag();
- const char *tag_to_send = thread_tag;
-
- // anonymize thread names
- if(strncmp(thread_tag, THREAD_TAG_STREAM_RECEIVER, strlen(THREAD_TAG_STREAM_RECEIVER)) == 0)
- tag_to_send = THREAD_TAG_STREAM_RECEIVER;
- if(strncmp(thread_tag, THREAD_TAG_STREAM_SENDER, strlen(THREAD_TAG_STREAM_SENDER)) == 0)
- tag_to_send = THREAD_TAG_STREAM_SENDER;
-
- char action_result[60+1];
- snprintfz(action_result, 60, "%s:%s", program_name, tag_to_send);
-
-#if !defined(ENABLE_SENTRY) && defined(HAVE_BACKTRACE)
- int fd = nd_log.sources[NDLS_DAEMON].fd;
- if(fd == -1)
- fd = STDERR_FILENO;
-
- int nptrs;
- void *buffer[10000];
-
- nptrs = backtrace(buffer, sizeof(buffer));
- if(nptrs)
- backtrace_symbols_fd(buffer, nptrs, fd);
-#endif
-
-#ifdef NETDATA_INTERNAL_CHECKS
- abort();
-#endif
-
- netdata_cleanup_and_exit(1, "FATAL", action_result, action_data);
-}
-
-// ----------------------------------------------------------------------------
-// log limits
-
-void nd_log_limits_reset(void) {
- usec_t now_ut = now_monotonic_usec();
-
- spinlock_lock(&nd_log.std_output.spinlock);
- spinlock_lock(&nd_log.std_error.spinlock);
-
- for(size_t i = 0; i < _NDLS_MAX ;i++) {
- spinlock_lock(&nd_log.sources[i].spinlock);
- nd_log.sources[i].limits.prevented = 0;
- nd_log.sources[i].limits.counter = 0;
- nd_log.sources[i].limits.started_monotonic_ut = now_ut;
- nd_log.sources[i].limits.logs_per_period = nd_log.sources[i].limits.logs_per_period_backup;
- spinlock_unlock(&nd_log.sources[i].spinlock);
- }
-
- spinlock_unlock(&nd_log.std_output.spinlock);
- spinlock_unlock(&nd_log.std_error.spinlock);
-}
-
-void nd_log_limits_unlimited(void) {
- nd_log_limits_reset();
- for(size_t i = 0; i < _NDLS_MAX ;i++) {
- nd_log.sources[i].limits.logs_per_period = 0;
- }
-}
-
-static bool nd_log_limit_reached(struct nd_log_source *source) {
- if(source->limits.throttle_period == 0 || source->limits.logs_per_period == 0)
- return false;
-
- usec_t now_ut = now_monotonic_usec();
- if(!source->limits.started_monotonic_ut)
- source->limits.started_monotonic_ut = now_ut;
-
- source->limits.counter++;
-
- if(now_ut - source->limits.started_monotonic_ut > (usec_t)source->limits.throttle_period) {
- if(source->limits.prevented) {
- BUFFER *wb = buffer_create(1024, NULL);
- buffer_sprintf(wb,
- "LOG FLOOD PROTECTION: resuming logging "
- "(prevented %"PRIu32" logs in the last %"PRIu32" seconds).",
- source->limits.prevented,
- source->limits.throttle_period);
-
- if(source->pending_msg)
- freez((void *)source->pending_msg);
-
- source->pending_msg = strdupz(buffer_tostring(wb));
-
- buffer_free(wb);
- }
-
- // restart the period accounting
- source->limits.started_monotonic_ut = now_ut;
- source->limits.counter = 1;
- source->limits.prevented = 0;
-
- // log this error
- return false;
- }
-
- if(source->limits.counter > source->limits.logs_per_period) {
- if(!source->limits.prevented) {
- BUFFER *wb = buffer_create(1024, NULL);
- buffer_sprintf(wb,
- "LOG FLOOD PROTECTION: too many logs (%"PRIu32" logs in %"PRId64" seconds, threshold is set to %"PRIu32" logs "
- "in %"PRIu32" seconds). Preventing more logs from process '%s' for %"PRId64" seconds.",
- source->limits.counter,
- (int64_t)((now_ut - source->limits.started_monotonic_ut) / USEC_PER_SEC),
- source->limits.logs_per_period,
- source->limits.throttle_period,
- program_name,
- (int64_t)(((source->limits.started_monotonic_ut + (source->limits.throttle_period * USEC_PER_SEC) - now_ut)) / USEC_PER_SEC)
- );
-
- if(source->pending_msg)
- freez((void *)source->pending_msg);
-
- source->pending_msg = strdupz(buffer_tostring(wb));
-
- buffer_free(wb);
- }
-
- source->limits.prevented++;
-
- // prevent logging this error
-#ifdef NETDATA_INTERNAL_CHECKS
- return false;
-#else
- return true;
-#endif
- }
-
- return false;
-}
diff --git a/src/libnetdata/log/nd_log-annotators.c b/src/libnetdata/log/nd_log-annotators.c
new file mode 100644
index 000000000..92e9bf310
--- /dev/null
+++ b/src/libnetdata/log/nd_log-annotators.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log-internals.h"
+
+const char *timestamp_usec_annotator(struct log_field *lf) {
+ usec_t ut = log_field_to_uint64(lf);
+
+ if(!ut)
+ return NULL;
+
+ static __thread char datetime[RFC3339_MAX_LENGTH];
+ rfc3339_datetime_ut(datetime, sizeof(datetime), ut, 3, false);
+ return datetime;
+}
+
+const char *errno_annotator(struct log_field *lf) {
+ int64_t errnum = log_field_to_int64(lf);
+
+ if(errnum == 0)
+ return NULL;
+
+ static __thread char buf[256];
+ size_t len = print_uint64(buf, errnum);
+ buf[len++] = ',';
+ buf[len++] = ' ';
+
+ char *msg_to = &buf[len];
+ size_t msg_size = sizeof(buf) - len;
+
+ const char *s = errno2str((int)errnum, msg_to, msg_size);
+ if(s != msg_to)
+ strncpyz(msg_to, s, msg_size - 1);
+
+ return buf;
+}
+
+#if defined(OS_WINDOWS)
+const char *winerror_annotator(struct log_field *lf) {
+ DWORD errnum = log_field_to_uint64(lf);
+
+ if (errnum == 0)
+ return NULL;
+
+ static __thread char buf[256];
+ size_t len = print_uint64(buf, errnum);
+ buf[len++] = ',';
+ buf[len++] = ' ';
+
+ char *msg_to = &buf[len];
+ size_t msg_size = sizeof(buf) - len;
+
+ wchar_t wbuf[1024];
+ DWORD size = FormatMessageW(
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ errnum,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ wbuf,
+ (DWORD)(sizeof(wbuf) / sizeof(wchar_t) - 1),
+ NULL
+ );
+
+ if (size > 0) {
+ // Remove \r\n at the end
+ while (size > 0 && (wbuf[size - 1] == L'\r' || wbuf[size - 1] == L'\n'))
+ wbuf[--size] = L'\0';
+
+ // Convert wide string to UTF-8
+ int utf8_size = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, msg_to, (int)msg_size, NULL, NULL);
+ if (utf8_size == 0)
+ snprintf(msg_to, msg_size - 1, "unknown error code");
+ msg_to[msg_size - 1] = '\0';
+ }
+ else
+ snprintf(msg_to, msg_size - 1, "unknown error code");
+
+ return buf;
+}
+#endif
+
+const char *priority_annotator(struct log_field *lf) {
+ uint64_t pri = log_field_to_uint64(lf);
+ return nd_log_id2priority(pri);
+}
diff --git a/src/libnetdata/log/nd_log-common.h b/src/libnetdata/log/nd_log-common.h
new file mode 100644
index 000000000..d06bbbd16
--- /dev/null
+++ b/src/libnetdata/log/nd_log-common.h
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_ND_LOG_COMMON_H
+#define NETDATA_ND_LOG_COMMON_H
+
+#include <syslog.h>
+
+typedef enum __attribute__((__packed__)) {
+ NDLS_UNSET = 0, // internal use only
+ NDLS_ACCESS, // access.log
+ NDLS_ACLK, // aclk.log
+ NDLS_COLLECTORS, // collector.log
+ NDLS_DAEMON, // error.log
+ NDLS_HEALTH, // health.log
+ NDLS_DEBUG, // debug.log
+
+ // terminator
+ _NDLS_MAX,
+} ND_LOG_SOURCES;
+
+typedef enum __attribute__((__packed__)) {
+ NDLP_EMERG = LOG_EMERG, // from syslog.h
+ NDLP_ALERT = LOG_ALERT, // from syslog.h
+ NDLP_CRIT = LOG_CRIT, // from syslog.h
+ NDLP_ERR = LOG_ERR, // from syslog.h
+ NDLP_WARNING = LOG_WARNING, // from syslog.h
+ NDLP_NOTICE = LOG_NOTICE, // from syslog.h
+ NDLP_INFO = LOG_INFO, // from syslog.h
+ NDLP_DEBUG = LOG_DEBUG, // from syslog.h
+
+ // terminator
+ _NDLP_MAX,
+} ND_LOG_FIELD_PRIORITY;
+
+typedef enum __attribute__((__packed__)) {
+ // KEEP THESE IN THE SAME ORDER AS in thread_log_fields (log.c)
+ // so that it easy to audit for missing fields
+
+ // NEVER RENUMBER THIS LIST
+ // The Windows Events Log has them at fixed positions
+
+ NDF_STOP = 0,
+ NDF_TIMESTAMP_REALTIME_USEC = 1, // the timestamp of the log message - added automatically
+ NDF_SYSLOG_IDENTIFIER = 2, // the syslog identifier of the application - added automatically
+ NDF_LOG_SOURCE = 3, // DAEMON, COLLECTORS, HEALTH, MSGID_ACCESS, ACLK - set at the log call
+ NDF_PRIORITY = 4, // the syslog priority (severity) - set at the log call
+ NDF_ERRNO = 5, // the ERRNO at the time of the log call - added automatically
+ NDF_WINERROR = 6, // Windows GetLastError()
+ NDF_INVOCATION_ID = 7, // the INVOCATION_ID of Netdata - added automatically
+ NDF_LINE = 8, // the source code file line number - added automatically
+ NDF_FILE = 9, // the source code filename - added automatically
+ NDF_FUNC = 10, // the source code function - added automatically
+ NDF_TID = 11, // the thread ID of the thread logging - added automatically
+ NDF_THREAD_TAG = 12, // the thread tag of the thread logging - added automatically
+ NDF_MESSAGE_ID = 13, // for specific events
+ NDF_MODULE = 14, // for internal plugin module, all other get the NDF_THREAD_TAG
+
+ NDF_NIDL_NODE = 15, // the node / rrdhost currently being worked
+ NDF_NIDL_INSTANCE = 16, // the instance / rrdset currently being worked
+ NDF_NIDL_CONTEXT = 17, // the context of the instance currently being worked
+ NDF_NIDL_DIMENSION = 18, // the dimension / rrddim currently being worked
+
+ // web server, aclk and stream receiver
+ NDF_SRC_TRANSPORT = 19, // the transport we received the request, one of: http, https, pluginsd
+
+ // Netdata Cloud Related
+ NDF_ACCOUNT_ID = 20,
+ NDF_USER_NAME = 21,
+ NDF_USER_ROLE = 22,
+ NDF_USER_ACCESS = 23,
+
+ // web server and stream receiver
+ NDF_SRC_IP = 24, // the streaming / web server source IP
+ NDF_SRC_PORT = 25, // the streaming / web server source Port
+ NDF_SRC_FORWARDED_HOST = 26,
+ NDF_SRC_FORWARDED_FOR = 27,
+ NDF_SRC_CAPABILITIES = 28, // the stream receiver capabilities
+
+ // stream sender (established links)
+ NDF_DST_TRANSPORT = 29, // the transport we send the request, one of: http, https
+ NDF_DST_IP = 30, // the destination streaming IP
+ NDF_DST_PORT = 31, // the destination streaming Port
+ NDF_DST_CAPABILITIES = 32, // the destination streaming capabilities
+
+ // web server, aclk and stream receiver
+ NDF_REQUEST_METHOD = 33, // for http like requests, the http request method
+ NDF_RESPONSE_CODE = 34, // for http like requests, the http response code, otherwise a status string
+
+ // web server (all), aclk (queries)
+ NDF_CONNECTION_ID = 35, // the web server connection ID
+ NDF_TRANSACTION_ID = 36, // the web server and API transaction ID
+ NDF_RESPONSE_SENT_BYTES = 37, // for http like requests, the response bytes
+ NDF_RESPONSE_SIZE_BYTES = 38, // for http like requests, the uncompressed response size
+ NDF_RESPONSE_PREPARATION_TIME_USEC = 39, // for http like requests, the preparation time
+ NDF_RESPONSE_SENT_TIME_USEC = 40, // for http like requests, the time to send the response back
+ NDF_RESPONSE_TOTAL_TIME_USEC = 41, // for http like requests, the total time to complete the response
+
+ // health alerts
+ NDF_ALERT_ID = 42,
+ NDF_ALERT_UNIQUE_ID = 43,
+ NDF_ALERT_EVENT_ID = 44,
+ NDF_ALERT_TRANSITION_ID = 45,
+ NDF_ALERT_CONFIG_HASH = 46,
+ NDF_ALERT_NAME = 47,
+ NDF_ALERT_CLASS = 48,
+ NDF_ALERT_COMPONENT = 49,
+ NDF_ALERT_TYPE = 50,
+ NDF_ALERT_EXEC = 51,
+ NDF_ALERT_RECIPIENT = 52,
+ NDF_ALERT_DURATION = 53,
+ NDF_ALERT_VALUE = 54,
+ NDF_ALERT_VALUE_OLD = 55,
+ NDF_ALERT_STATUS = 56,
+ NDF_ALERT_STATUS_OLD = 57,
+ NDF_ALERT_SOURCE = 58,
+ NDF_ALERT_UNITS = 59,
+ NDF_ALERT_SUMMARY = 60,
+ NDF_ALERT_INFO = 61,
+ NDF_ALERT_NOTIFICATION_REALTIME_USEC = 62,
+ // NDF_ALERT_FLAGS,
+
+ // put new items here
+ // leave the request URL and the message last
+
+ NDF_REQUEST = 63, // the request we are currently working on
+ NDF_MESSAGE = 64, // the log message, if any
+
+ // terminator
+ _NDF_MAX,
+} ND_LOG_FIELD_ID;
+
+typedef enum __attribute__((__packed__)) {
+ NDFT_UNSET = 0,
+ NDFT_TXT,
+ NDFT_STR,
+ NDFT_BFR,
+ NDFT_U64,
+ NDFT_I64,
+ NDFT_DBL,
+ NDFT_UUID,
+ NDFT_CALLBACK,
+
+ // terminator
+ _NDFT_MAX,
+} ND_LOG_STACK_FIELD_TYPE;
+
+#endif //NETDATA_ND_LOG_COMMON_H
diff --git a/src/libnetdata/log/nd_log-config.c b/src/libnetdata/log/nd_log-config.c
new file mode 100644
index 000000000..c8e17402e
--- /dev/null
+++ b/src/libnetdata/log/nd_log-config.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log-internals.h"
+
+void nd_log_set_user_settings(ND_LOG_SOURCES source, const char *setting) {
+ char buf[FILENAME_MAX + 100];
+ if(setting && *setting)
+ strncpyz(buf, setting, sizeof(buf) - 1);
+ else
+ buf[0] = '\0';
+
+ struct nd_log_source *ls = &nd_log.sources[source];
+ char *output = strrchr(buf, '@');
+
+ if(!output)
+ // all of it is the output
+ output = buf;
+ else {
+ // we found an '@', the next char is the output
+ *output = '\0';
+ output++;
+
+ // parse the other params
+ char *remaining = buf;
+ while(remaining) {
+ char *value = strsep_skip_consecutive_separators(&remaining, ",");
+ if (!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if (!name || !*name) continue;
+
+ if(strcmp(name, "logfmt") == 0)
+ ls->format = NDLF_LOGFMT;
+ else if(strcmp(name, "json") == 0)
+ ls->format = NDLF_JSON;
+ else if(strcmp(name, "journal") == 0)
+ ls->format = NDLF_JOURNAL;
+#if defined(OS_WINDOWS)
+#if defined(HAVE_ETW)
+ else if(strcmp(name, ETW_NAME) == 0)
+ ls->format = NDLF_ETW;
+#endif
+#if defined(HAVE_WEL)
+ else if(strcmp(name, WEL_NAME) == 0)
+ ls->format = NDLF_WEL;
+#endif
+#endif
+ else if(strcmp(name, "level") == 0 && value && *value)
+ ls->min_priority = nd_log_priority2id(value);
+ else if(strcmp(name, "protection") == 0 && value && *value) {
+ if(strcmp(value, "off") == 0 || strcmp(value, "none") == 0) {
+ ls->limits = ND_LOG_LIMITS_UNLIMITED;
+ ls->limits.counter = 0;
+ ls->limits.prevented = 0;
+ }
+ else {
+ ls->limits = ND_LOG_LIMITS_DEFAULT;
+
+ char *slash = strchr(value, '/');
+ if(slash) {
+ *slash = '\0';
+ slash++;
+ ls->limits.logs_per_period = ls->limits.logs_per_period_backup = str2u(value);
+
+ int period;
+ if(!duration_parse_seconds(slash, &period)) {
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Error while parsing period '%s'", slash);
+ period = ND_LOG_DEFAULT_THROTTLE_PERIOD;
+ }
+
+ ls->limits.throttle_period = period;
+ }
+ else {
+ ls->limits.logs_per_period = ls->limits.logs_per_period_backup = str2u(value);
+ ls->limits.throttle_period = ND_LOG_DEFAULT_THROTTLE_PERIOD;
+ }
+ }
+ }
+ else
+ nd_log(NDLS_DAEMON, NDLP_ERR,
+ "Error while parsing configuration of log source '%s'. "
+ "In config '%s', '%s' is not understood.",
+ nd_log_id2source(source), setting, name);
+ }
+ }
+
+ if(!output || !*output || strcmp(output, "none") == 0 || strcmp(output, "off") == 0) {
+ ls->method = NDLM_DISABLED;
+ ls->filename = "/dev/null";
+ }
+ else if(strcmp(output, "journal") == 0) {
+ ls->method = NDLM_JOURNAL;
+ ls->filename = NULL;
+ }
+#if defined(OS_WINDOWS)
+#if defined(HAVE_ETW)
+ else if(strcmp(output, ETW_NAME) == 0) {
+ ls->method = NDLM_ETW;
+ ls->filename = NULL;
+ }
+#endif
+#if defined(HAVE_WEL)
+ else if(strcmp(output, WEL_NAME) == 0) {
+ ls->method = NDLM_WEL;
+ ls->filename = NULL;
+ }
+#endif
+#endif
+ else if(strcmp(output, "syslog") == 0) {
+ ls->method = NDLM_SYSLOG;
+ ls->filename = NULL;
+ }
+ else if(strcmp(output, "/dev/null") == 0) {
+ ls->method = NDLM_DEVNULL;
+ ls->filename = "/dev/null";
+ }
+ else if(strcmp(output, "system") == 0) {
+ if(ls->fd == STDERR_FILENO) {
+ ls->method = NDLM_STDERR;
+ ls->filename = NULL;
+ ls->fd = STDERR_FILENO;
+ }
+ else {
+ ls->method = NDLM_STDOUT;
+ ls->filename = NULL;
+ ls->fd = STDOUT_FILENO;
+ }
+ }
+ else if(strcmp(output, "stderr") == 0) {
+ ls->method = NDLM_STDERR;
+ ls->filename = NULL;
+ ls->fd = STDERR_FILENO;
+ }
+ else if(strcmp(output, "stdout") == 0) {
+ ls->method = NDLM_STDOUT;
+ ls->filename = NULL;
+ ls->fd = STDOUT_FILENO;
+ }
+ else {
+ ls->method = NDLM_FILE;
+ ls->filename = strdupz(output);
+ }
+
+#if defined(NETDATA_INTERNAL_CHECKS) || defined(NETDATA_DEV_MODE)
+ ls->min_priority = NDLP_DEBUG;
+#endif
+
+ if(source == NDLS_COLLECTORS) {
+ // set the method for the collector processes we will spawn
+
+ ND_LOG_METHOD method = NDLM_STDERR;
+ ND_LOG_FORMAT format = NDLF_LOGFMT;
+ ND_LOG_FIELD_PRIORITY priority = ls->min_priority;
+
+ if(IS_VALID_LOG_METHOD_FOR_EXTERNAL_PLUGINS(ls->method)) {
+ method = ls->method;
+ format = ls->format;
+ }
+
+ nd_setenv("NETDATA_LOG_METHOD", nd_log_id2method(method), 1);
+ nd_setenv("NETDATA_LOG_FORMAT", nd_log_id2format(format), 1);
+ nd_setenv("NETDATA_LOG_LEVEL", nd_log_id2priority(priority), 1);
+ }
+}
+
+void nd_log_set_priority_level(const char *setting) {
+ if(!setting || !*setting)
+ setting = "info";
+
+ ND_LOG_FIELD_PRIORITY priority = nd_log_priority2id(setting);
+
+#if defined(NETDATA_INTERNAL_CHECKS) || defined(NETDATA_DEV_MODE)
+ priority = NDLP_DEBUG;
+#endif
+
+ for (size_t i = 0; i < _NDLS_MAX; i++) {
+ if (i != NDLS_DEBUG)
+ nd_log.sources[i].min_priority = priority;
+ }
+
+ // the right one
+ nd_setenv("NETDATA_LOG_LEVEL", nd_log_id2priority(priority), 1);
+}
+
+void nd_log_set_facility(const char *facility) {
+ if(!facility || !*facility)
+ facility = "daemon";
+
+ nd_log.syslog.facility = nd_log_facility2id(facility);
+ nd_setenv("NETDATA_SYSLOG_FACILITY", nd_log_id2facility(nd_log.syslog.facility), 1);
+}
+
+void nd_log_set_flood_protection(size_t logs, time_t period) {
+ nd_log.sources[NDLS_DAEMON].limits.logs_per_period =
+ nd_log.sources[NDLS_DAEMON].limits.logs_per_period_backup;
+ nd_log.sources[NDLS_COLLECTORS].limits.logs_per_period =
+ nd_log.sources[NDLS_COLLECTORS].limits.logs_per_period_backup = logs;
+
+ nd_log.sources[NDLS_DAEMON].limits.throttle_period =
+ nd_log.sources[NDLS_COLLECTORS].limits.throttle_period = period;
+
+ char buf[100];
+ snprintfz(buf, sizeof(buf), "%" PRIu64, (uint64_t )period);
+ nd_setenv("NETDATA_ERRORS_THROTTLE_PERIOD", buf, 1);
+ snprintfz(buf, sizeof(buf), "%" PRIu64, (uint64_t )logs);
+ nd_setenv("NETDATA_ERRORS_PER_PERIOD", buf, 1);
+}
diff --git a/src/libnetdata/log/nd_log-field-formatters.c b/src/libnetdata/log/nd_log-field-formatters.c
new file mode 100644
index 000000000..e1b3c0d08
--- /dev/null
+++ b/src/libnetdata/log/nd_log-field-formatters.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log-internals.h"
+
+int64_t log_field_to_int64(struct log_field *lf) {
+
+ // --- FIELD_PARSER_VERSIONS ---
+ //
+ // IMPORTANT:
+ // THERE ARE 6 VERSIONS OF THIS CODE
+ //
+ // 1. journal (direct socket API),
+ // 2. journal (libsystemd API),
+ // 3. logfmt,
+ // 4. json,
+ // 5. convert to uint64
+ // 6. convert to int64
+ //
+ // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
+
+ CLEAN_BUFFER *tmp = NULL;
+ const char *s = NULL;
+
+ switch(lf->entry.type) {
+ default:
+ case NDFT_UUID:
+ case NDFT_UNSET:
+ return 0;
+
+ case NDFT_TXT:
+ s = lf->entry.txt;
+ break;
+
+ case NDFT_STR:
+ s = string2str(lf->entry.str);
+ break;
+
+ case NDFT_BFR:
+ s = buffer_tostring(lf->entry.bfr);
+ break;
+
+ case NDFT_CALLBACK:
+ tmp = buffer_create(0, NULL);
+
+ if(lf->entry.cb.formatter(tmp, lf->entry.cb.formatter_data))
+ s = buffer_tostring(tmp);
+ else
+ s = NULL;
+ break;
+
+ case NDFT_U64:
+ return (int64_t)lf->entry.u64;
+
+ case NDFT_I64:
+ return (int64_t)lf->entry.i64;
+
+ case NDFT_DBL:
+ return (int64_t)lf->entry.dbl;
+ }
+
+ if(s && *s)
+ return str2ll(s, NULL);
+
+ return 0;
+}
+
+uint64_t log_field_to_uint64(struct log_field *lf) {
+
+ // --- FIELD_PARSER_VERSIONS ---
+ //
+ // IMPORTANT:
+ // THERE ARE 6 VERSIONS OF THIS CODE
+ //
+ // 1. journal (direct socket API),
+ // 2. journal (libsystemd API),
+ // 3. logfmt,
+ // 4. json,
+ // 5. convert to uint64
+ // 6. convert to int64
+ //
+ // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
+
+ CLEAN_BUFFER *tmp = NULL;
+ const char *s = NULL;
+
+ switch(lf->entry.type) {
+ default:
+ case NDFT_UUID:
+ case NDFT_UNSET:
+ return 0;
+
+ case NDFT_TXT:
+ s = lf->entry.txt;
+ break;
+
+ case NDFT_STR:
+ s = string2str(lf->entry.str);
+ break;
+
+ case NDFT_BFR:
+ s = buffer_tostring(lf->entry.bfr);
+ break;
+
+ case NDFT_CALLBACK:
+ tmp = buffer_create(0, NULL);
+
+ if(lf->entry.cb.formatter(tmp, lf->entry.cb.formatter_data))
+ s = buffer_tostring(tmp);
+ else
+ s = NULL;
+ break;
+
+ case NDFT_U64:
+ return lf->entry.u64;
+
+ case NDFT_I64:
+ return lf->entry.i64;
+
+ case NDFT_DBL:
+ return (uint64_t) lf->entry.dbl;
+ }
+
+ if(s && *s)
+ return str2uint64_t(s, NULL);
+
+ return 0;
+}
diff --git a/src/libnetdata/log/nd_log-format-json.c b/src/libnetdata/log/nd_log-format-json.c
new file mode 100644
index 000000000..c25bf19c5
--- /dev/null
+++ b/src/libnetdata/log/nd_log-format-json.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log-internals.h"
+
+void nd_logger_json(BUFFER *wb, struct log_field *fields, size_t fields_max) {
+
+ // --- FIELD_PARSER_VERSIONS ---
+ //
+ // IMPORTANT:
+ // THERE ARE 6 VERSIONS OF THIS CODE
+ //
+ // 1. journal (direct socket API),
+ // 2. journal (libsystemd API),
+ // 3. logfmt,
+ // 4. json,
+ // 5. convert to uint64
+ // 6. convert to int64
+ //
+ // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
+
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
+ CLEAN_BUFFER *tmp = NULL;
+
+ for (size_t i = 0; i < fields_max; i++) {
+ if (!fields[i].entry.set || !fields[i].logfmt)
+ continue;
+
+ const char *key = fields[i].logfmt;
+
+ const char *s = NULL;
+ switch(fields[i].entry.type) {
+ case NDFT_TXT:
+ s = fields[i].entry.txt;
+ break;
+ case NDFT_STR:
+ s = string2str(fields[i].entry.str);
+ break;
+ case NDFT_BFR:
+ s = buffer_tostring(fields[i].entry.bfr);
+ break;
+ case NDFT_U64:
+ buffer_json_member_add_uint64(wb, key, fields[i].entry.u64);
+ break;
+ case NDFT_I64:
+ buffer_json_member_add_int64(wb, key, fields[i].entry.i64);
+ break;
+ case NDFT_DBL:
+ buffer_json_member_add_double(wb, key, fields[i].entry.dbl);
+ break;
+ case NDFT_UUID:
+ if(!uuid_is_null(*fields[i].entry.uuid)) {
+ char u[UUID_COMPACT_STR_LEN];
+ uuid_unparse_lower_compact(*fields[i].entry.uuid, u);
+ buffer_json_member_add_string(wb, key, u);
+ }
+ break;
+ case NDFT_CALLBACK: {
+ if(!tmp)
+ tmp = buffer_create(1024, NULL);
+ else
+ buffer_flush(tmp);
+ if(fields[i].entry.cb.formatter(tmp, fields[i].entry.cb.formatter_data))
+ s = buffer_tostring(tmp);
+ else
+ s = NULL;
+ }
+ break;
+ default:
+ s = "UNHANDLED";
+ break;
+ }
+
+ if(s && *s)
+ buffer_json_member_add_string(wb, key, s);
+ }
+
+ buffer_json_finalize(wb);
+}
diff --git a/src/libnetdata/log/nd_log-format-logfmt.c b/src/libnetdata/log/nd_log-format-logfmt.c
new file mode 100644
index 000000000..d65211dfc
--- /dev/null
+++ b/src/libnetdata/log/nd_log-format-logfmt.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log-internals.h"
+
+static bool needs_quotes_for_logfmt(const char *s)
+{
+ static bool safe_for_logfmt[256] = {
+ [' '] = true, ['!'] = true, ['"'] = false, ['#'] = true, ['$'] = true, ['%'] = true, ['&'] = true,
+ ['\''] = true, ['('] = true, [')'] = true, ['*'] = true, ['+'] = true, [','] = true, ['-'] = true,
+ ['.'] = true, ['/'] = true, ['0'] = true, ['1'] = true, ['2'] = true, ['3'] = true, ['4'] = true,
+ ['5'] = true, ['6'] = true, ['7'] = true, ['8'] = true, ['9'] = true, [':'] = true, [';'] = true,
+ ['<'] = true, ['='] = true, ['>'] = true, ['?'] = true, ['@'] = true, ['A'] = true, ['B'] = true,
+ ['C'] = true, ['D'] = true, ['E'] = true, ['F'] = true, ['G'] = true, ['H'] = true, ['I'] = true,
+ ['J'] = true, ['K'] = true, ['L'] = true, ['M'] = true, ['N'] = true, ['O'] = true, ['P'] = true,
+ ['Q'] = true, ['R'] = true, ['S'] = true, ['T'] = true, ['U'] = true, ['V'] = true, ['W'] = true,
+ ['X'] = true, ['Y'] = true, ['Z'] = true, ['['] = true, ['\\'] = false, [']'] = true, ['^'] = true,
+ ['_'] = true, ['`'] = true, ['a'] = true, ['b'] = true, ['c'] = true, ['d'] = true, ['e'] = true,
+ ['f'] = true, ['g'] = true, ['h'] = true, ['i'] = true, ['j'] = true, ['k'] = true, ['l'] = true,
+ ['m'] = true, ['n'] = true, ['o'] = true, ['p'] = true, ['q'] = true, ['r'] = true, ['s'] = true,
+ ['t'] = true, ['u'] = true, ['v'] = true, ['w'] = true, ['x'] = true, ['y'] = true, ['z'] = true,
+ ['{'] = true, ['|'] = true, ['}'] = true, ['~'] = true, [0x7f] = true,
+ };
+
+ if(!*s)
+ return true;
+
+ while(*s) {
+ if(*s == '=' || isspace((uint8_t)*s) || !safe_for_logfmt[(uint8_t)*s])
+ return true;
+
+ s++;
+ }
+
+ return false;
+}
+
+static void string_to_logfmt(BUFFER *wb, const char *s)
+{
+ bool spaces = needs_quotes_for_logfmt(s);
+
+ if(spaces)
+ buffer_fast_strcat(wb, "\"", 1);
+
+ buffer_json_strcat(wb, s);
+
+ if(spaces)
+ buffer_fast_strcat(wb, "\"", 1);
+}
+
+void nd_logger_logfmt(BUFFER *wb, struct log_field *fields, size_t fields_max) {
+
+ // --- FIELD_PARSER_VERSIONS ---
+ //
+ // IMPORTANT:
+ // THERE ARE 6 VERSIONS OF THIS CODE
+ //
+ // 1. journal (direct socket API),
+ // 2. journal (libsystemd API),
+ // 3. logfmt,
+ // 4. json,
+ // 5. convert to uint64
+ // 6. convert to int64
+ //
+ // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
+
+ CLEAN_BUFFER *tmp = NULL;
+
+ for (size_t i = 0; i < fields_max; i++) {
+ if (!fields[i].entry.set || !fields[i].logfmt)
+ continue;
+
+ const char *key = fields[i].logfmt;
+
+ if(fields[i].annotator) {
+ const char *s = fields[i].annotator(&fields[i]);
+ if(!s) continue;
+
+ if(buffer_strlen(wb))
+ buffer_fast_strcat(wb, " ", 1);
+
+ buffer_strcat(wb, key);
+ buffer_fast_strcat(wb, "=", 1);
+ string_to_logfmt(wb, s);
+ }
+ else {
+ if(buffer_strlen(wb))
+ buffer_fast_strcat(wb, " ", 1);
+
+ switch(fields[i].entry.type) {
+ case NDFT_TXT:
+ if(*fields[i].entry.txt) {
+ buffer_strcat(wb, key);
+ buffer_fast_strcat(wb, "=", 1);
+ string_to_logfmt(wb, fields[i].entry.txt);
+ }
+ break;
+ case NDFT_STR:
+ buffer_strcat(wb, key);
+ buffer_fast_strcat(wb, "=", 1);
+ string_to_logfmt(wb, string2str(fields[i].entry.str));
+ break;
+ case NDFT_BFR:
+ if(buffer_strlen(fields[i].entry.bfr)) {
+ buffer_strcat(wb, key);
+ buffer_fast_strcat(wb, "=", 1);
+ string_to_logfmt(wb, buffer_tostring(fields[i].entry.bfr));
+ }
+ break;
+ case NDFT_U64:
+ buffer_strcat(wb, key);
+ buffer_fast_strcat(wb, "=", 1);
+ buffer_print_uint64(wb, fields[i].entry.u64);
+ break;
+ case NDFT_I64:
+ buffer_strcat(wb, key);
+ buffer_fast_strcat(wb, "=", 1);
+ buffer_print_int64(wb, fields[i].entry.i64);
+ break;
+ case NDFT_DBL:
+ buffer_strcat(wb, key);
+ buffer_fast_strcat(wb, "=", 1);
+ buffer_print_netdata_double(wb, fields[i].entry.dbl);
+ break;
+ case NDFT_UUID:
+ if(!uuid_is_null(*fields[i].entry.uuid)) {
+ char u[UUID_COMPACT_STR_LEN];
+ uuid_unparse_lower_compact(*fields[i].entry.uuid, u);
+ buffer_strcat(wb, key);
+ buffer_fast_strcat(wb, "=", 1);
+ buffer_fast_strcat(wb, u, sizeof(u) - 1);
+ }
+ break;
+ case NDFT_CALLBACK: {
+ if(!tmp)
+ tmp = buffer_create(1024, NULL);
+ else
+ buffer_flush(tmp);
+ if(fields[i].entry.cb.formatter(tmp, fields[i].entry.cb.formatter_data)) {
+ buffer_strcat(wb, key);
+ buffer_fast_strcat(wb, "=", 1);
+ string_to_logfmt(wb, buffer_tostring(tmp));
+ }
+ }
+ break;
+ default:
+ buffer_strcat(wb, "UNHANDLED");
+ break;
+ }
+ }
+ }
+}
diff --git a/src/libnetdata/log/nd_log-init.c b/src/libnetdata/log/nd_log-init.c
new file mode 100644
index 000000000..7f846b136
--- /dev/null
+++ b/src/libnetdata/log/nd_log-init.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log-internals.h"
+
+// --------------------------------------------------------------------------------------------------------------------
+
+__attribute__((constructor)) void initialize_invocation_id(void) {
+ // check for a NETDATA_INVOCATION_ID
+ if(uuid_parse_flexi(getenv("NETDATA_INVOCATION_ID"), nd_log.invocation_id) != 0) {
+ // not found, check for systemd set INVOCATION_ID
+ if(uuid_parse_flexi(getenv("INVOCATION_ID"), nd_log.invocation_id) != 0) {
+ // not found, generate a new one
+ uuid_generate_random(nd_log.invocation_id);
+ }
+ }
+
+ char uuid[UUID_COMPACT_STR_LEN];
+ uuid_unparse_lower_compact(nd_log.invocation_id, uuid);
+ nd_setenv("NETDATA_INVOCATION_ID", uuid, 1);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+void nd_log_initialize_for_external_plugins(const char *name) {
+ // if we don't run under Netdata, log to stderr,
+ // otherwise, use the logging method Netdata wants us to use.
+#if defined(OS_WINDOWS)
+#if defined(HAVE_ETW)
+ nd_setenv("NETDATA_LOG_METHOD", ETW_NAME, 0);
+ nd_setenv("NETDATA_LOG_FORMAT", ETW_NAME, 0);
+#elif defined(HAVE_WEL)
+ nd_setenv("NETDATA_LOG_METHOD", WEL_NAME, 0);
+ nd_setenv("NETDATA_LOG_FORMAT", WEL_NAME, 0);
+#else
+ nd_setenv("NETDATA_LOG_METHOD", "stderr", 0);
+ nd_setenv("NETDATA_LOG_FORMAT", "logfmt", 0);
+#endif
+#else
+ nd_setenv("NETDATA_LOG_METHOD", "stderr", 0);
+ nd_setenv("NETDATA_LOG_FORMAT", "logfmt", 0);
+#endif
+
+ nd_log.overwrite_process_source = NDLS_COLLECTORS;
+ program_name = name;
+
+ for(size_t i = 0; i < _NDLS_MAX ;i++) {
+ nd_log.sources[i].method = NDLM_DEFAULT;
+ nd_log.sources[i].fd = -1;
+ nd_log.sources[i].fp = NULL;
+ }
+
+ nd_log_set_priority_level(getenv("NETDATA_LOG_LEVEL"));
+ nd_log_set_facility(getenv("NETDATA_SYSLOG_FACILITY"));
+
+ time_t period = 1200;
+ size_t logs = 200;
+ const char *s = getenv("NETDATA_ERRORS_THROTTLE_PERIOD");
+ if(s && *s >= '0' && *s <= '9') {
+ period = str2l(s);
+ if(period < 0) period = 0;
+ }
+
+ s = getenv("NETDATA_ERRORS_PER_PERIOD");
+ if(s && *s >= '0' && *s <= '9')
+ logs = str2u(s);
+
+ nd_log_set_flood_protection(logs, period);
+
+ if(!netdata_configured_host_prefix) {
+ s = getenv("NETDATA_HOST_PREFIX");
+ if(s && *s)
+ netdata_configured_host_prefix = (char *)s;
+ }
+
+ ND_LOG_METHOD method = nd_log_method2id(getenv("NETDATA_LOG_METHOD"));
+ ND_LOG_FORMAT format = nd_log_format2id(getenv("NETDATA_LOG_FORMAT"));
+
+ if(!IS_VALID_LOG_METHOD_FOR_EXTERNAL_PLUGINS(method)) {
+ if(is_stderr_connected_to_journal()) {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "NETDATA_LOG_METHOD is not set. Using journal.");
+ method = NDLM_JOURNAL;
+ }
+ else {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "NETDATA_LOG_METHOD is not set. Using stderr.");
+ method = NDLM_STDERR;
+ }
+ }
+
+ switch(method) {
+ case NDLM_JOURNAL:
+ if(!nd_log_journal_direct_init(getenv("NETDATA_SYSTEMD_JOURNAL_PATH")) ||
+ !nd_log_journal_direct_init(NULL) || !nd_log_journal_systemd_init()) {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to initialize journal. Using stderr.");
+ method = NDLM_STDERR;
+ }
+ break;
+
+#if defined(OS_WINDOWS)
+#if defined(HAVE_ETW)
+ case NDLM_ETW:
+ if(!nd_log_init_etw()) {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to initialize Events Tracing for Windows (ETW). Using stderr.");
+ method = NDLM_STDERR;
+ }
+ break;
+#endif
+#if defined(HAVE_WEL)
+ case NDLM_WEL:
+ if(!nd_log_init_wel()) {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to initialize Windows Event Log (WEL). Using stderr.");
+ method = NDLM_STDERR;
+ }
+ break;
+#endif
+#endif
+
+ case NDLM_SYSLOG:
+ nd_log_init_syslog();
+ break;
+
+ default:
+ method = NDLM_STDERR;
+ break;
+ }
+
+ nd_log.sources[NDLS_COLLECTORS].method = method;
+ nd_log.sources[NDLS_COLLECTORS].format = format;
+ nd_log.sources[NDLS_COLLECTORS].fd = -1;
+ nd_log.sources[NDLS_COLLECTORS].fp = NULL;
+
+ // nd_log(NDLS_COLLECTORS, NDLP_NOTICE, "FINAL_LOG_METHOD: %s", nd_log_id2method(method));
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+void nd_log_open(struct nd_log_source *e, ND_LOG_SOURCES source) {
+ if(e->method == NDLM_DEFAULT)
+ nd_log_set_user_settings(source, e->filename);
+
+ if((e->method == NDLM_FILE && !e->filename) ||
+ (e->method == NDLM_DEVNULL && e->fd == -1))
+ e->method = NDLM_DISABLED;
+
+ if(e->fp)
+ fflush(e->fp);
+
+ switch(e->method) {
+ case NDLM_SYSLOG:
+ nd_log_init_syslog();
+ break;
+
+ case NDLM_JOURNAL:
+ nd_log_journal_direct_init(NULL);
+ nd_log_journal_systemd_init();
+ break;
+
+#if defined(OS_WINDOWS)
+#if defined(HAVE_ETW)
+ case NDLM_ETW:
+ nd_log_init_etw();
+ break;
+#endif
+#if defined(HAVE_WEL)
+ case NDLM_WEL:
+ nd_log_init_wel();
+ break;
+#endif
+#endif
+
+ case NDLM_STDOUT:
+ e->fp = stdout;
+ e->fd = STDOUT_FILENO;
+ break;
+
+ case NDLM_DISABLED:
+ break;
+
+ case NDLM_DEFAULT:
+ case NDLM_STDERR:
+ e->method = NDLM_STDERR;
+ e->fp = stderr;
+ e->fd = STDERR_FILENO;
+ break;
+
+ case NDLM_DEVNULL:
+ case NDLM_FILE: {
+ int fd = open(e->filename, O_WRONLY | O_APPEND | O_CREAT, 0664);
+ if(fd == -1) {
+ if(e->fd != STDOUT_FILENO && e->fd != STDERR_FILENO) {
+ e->fd = STDERR_FILENO;
+ e->method = NDLM_STDERR;
+ netdata_log_error("Cannot open log file '%s'. Falling back to stderr.", e->filename);
+ }
+ else
+ netdata_log_error("Cannot open log file '%s'. Leaving fd %d as-is.", e->filename, e->fd);
+ }
+ else {
+ if (!nd_log_replace_existing_fd(e, fd)) {
+ if(e->fd == STDOUT_FILENO || e->fd == STDERR_FILENO) {
+ if(e->fd == STDOUT_FILENO)
+ e->method = NDLM_STDOUT;
+ else if(e->fd == STDERR_FILENO)
+ e->method = NDLM_STDERR;
+
+ // we have dup2() fd, so we can close the one we opened
+ if(fd != STDOUT_FILENO && fd != STDERR_FILENO)
+ close(fd);
+ }
+ else
+ e->fd = fd;
+ }
+ }
+
+ // at this point we have e->fd set properly
+
+ if(e->fd == STDOUT_FILENO)
+ e->fp = stdout;
+ else if(e->fd == STDERR_FILENO)
+ e->fp = stderr;
+
+ if(!e->fp) {
+ e->fp = fdopen(e->fd, "a");
+ if (!e->fp) {
+ netdata_log_error("Cannot fdopen() fd %d ('%s')", e->fd, e->filename);
+
+ if(e->fd != STDOUT_FILENO && e->fd != STDERR_FILENO)
+ close(e->fd);
+
+ e->fp = stderr;
+ e->fd = STDERR_FILENO;
+ }
+ }
+ else {
+ if (setvbuf(e->fp, NULL, _IOLBF, 0) != 0)
+ netdata_log_error("Cannot set line buffering on fd %d ('%s')", e->fd, e->filename);
+ }
+ }
+ break;
+ }
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+void nd_log_stdin_init(int fd, const char *filename) {
+ int f = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0664);
+ if(f == -1)
+ return;
+
+ if(f != fd) {
+ dup2(f, fd);
+ close(f);
+ }
+}
+
+void nd_log_initialize(void) {
+ nd_log_stdin_init(STDIN_FILENO, "/dev/null");
+
+ for(size_t i = 0 ; i < _NDLS_MAX ; i++)
+ nd_log_open(&nd_log.sources[i], i);
+}
+
+void nd_log_reopen_log_files(bool log) {
+ if(log)
+ netdata_log_info("Reopening all log files.");
+
+ nd_log_initialize();
+
+ if(log)
+ netdata_log_info("Log files re-opened.");
+}
+
+int nd_log_systemd_journal_fd(void) {
+ return nd_log.journal.fd;
+}
+
+void nd_log_reopen_log_files_for_spawn_server(const char *name) {
+ gettid_uncached();
+
+ if(nd_log.syslog.initialized) {
+ closelog();
+ nd_log.syslog.initialized = false;
+ nd_log_init_syslog();
+ }
+
+ if(nd_log.journal_direct.initialized) {
+ close(nd_log.journal_direct.fd);
+ nd_log.journal_direct.fd = -1;
+ nd_log.journal_direct.initialized = false;
+ }
+
+ for(size_t i = 0; i < _NDLS_MAX ;i++) {
+ spinlock_init(&nd_log.sources[i].spinlock);
+ nd_log.sources[i].method = NDLM_DEFAULT;
+ nd_log.sources[i].fd = -1;
+ nd_log.sources[i].fp = NULL;
+ nd_log.sources[i].pending_msg = NULL;
+#if defined(OS_WINDOWS)
+ nd_log.sources[i].hEventLog = NULL;
+#endif
+ }
+
+ // initialize spinlocks
+ spinlock_init(&nd_log.std_output.spinlock);
+ spinlock_init(&nd_log.std_error.spinlock);
+
+ nd_log.syslog.initialized = false;
+ nd_log.eventlog.initialized = false;
+ nd_log.std_output.initialized = false;
+ nd_log.std_error.initialized = false;
+
+ nd_log_initialize_for_external_plugins(name);
+}
+
diff --git a/src/libnetdata/log/nd_log-internals.c b/src/libnetdata/log/nd_log-internals.c
new file mode 100644
index 000000000..97f521fad
--- /dev/null
+++ b/src/libnetdata/log/nd_log-internals.c
@@ -0,0 +1,823 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log-internals.h"
+
+// --------------------------------------------------------------------------------------------------------------------
+// workaround strerror_r()
+
+#if defined(STRERROR_R_CHAR_P)
+// GLIBC version of strerror_r
+static const char *strerror_result(const char *a, const char *b) { (void)b; return a; }
+#elif defined(HAVE_STRERROR_R)
+// POSIX version of strerror_r
+static const char *strerror_result(int a, const char *b) { (void)a; return b; }
+#elif defined(HAVE_C__GENERIC)
+
+// what a trick!
+// http://stackoverflow.com/questions/479207/function-overloading-in-c
+static const char *strerror_result_int(int a, const char *b) { (void)a; return b; }
+static const char *strerror_result_string(const char *a, const char *b) { (void)b; return a; }
+
+#define strerror_result(a, b) _Generic((a), \
+ int: strerror_result_int, \
+ char *: strerror_result_string \
+ )(a, b)
+
+#else
+#error "cannot detect the format of function strerror_r()"
+#endif
+
+const char *errno2str(int errnum, char *buf, size_t size) {
+ return strerror_result(strerror_r(errnum, buf, size), buf);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// logging method
+
+static struct {
+ ND_LOG_METHOD method;
+ const char *name;
+} nd_log_methods[] = {
+ { .method = NDLM_DISABLED, .name = "none" },
+ { .method = NDLM_DEVNULL, .name = "/dev/null" },
+ { .method = NDLM_DEFAULT, .name = "default" },
+ { .method = NDLM_JOURNAL, .name = "journal" },
+ { .method = NDLM_SYSLOG, .name = "syslog" },
+ { .method = NDLM_STDOUT, .name = "stdout" },
+ { .method = NDLM_STDERR, .name = "stderr" },
+ { .method = NDLM_FILE, .name = "file" },
+#if defined(OS_WINDOWS)
+#if defined(HAVE_ETW)
+ { .method = NDLM_ETW, .name = ETW_NAME },
+#endif
+#if defined(HAVE_WEL)
+ { .method = NDLM_WEL, .name = WEL_NAME },
+#endif
+#endif
+};
+
+ND_LOG_METHOD nd_log_method2id(const char *method) {
+ if(!method || !*method)
+ return NDLM_DEFAULT;
+
+ size_t entries = sizeof(nd_log_methods) / sizeof(nd_log_methods[0]);
+ for(size_t i = 0; i < entries ;i++) {
+ if(strcmp(nd_log_methods[i].name, method) == 0)
+ return nd_log_methods[i].method;
+ }
+
+ return NDLM_FILE;
+}
+
+const char *nd_log_id2method(ND_LOG_METHOD method) {
+ size_t entries = sizeof(nd_log_methods) / sizeof(nd_log_methods[0]);
+ for(size_t i = 0; i < entries ;i++) {
+ if(method == nd_log_methods[i].method)
+ return nd_log_methods[i].name;
+ }
+
+ return "unknown";
+}
+
+const char *nd_log_method_for_external_plugins(const char *s) {
+ if(s && *s) {
+ ND_LOG_METHOD method = nd_log_method2id(s);
+ if(IS_VALID_LOG_METHOD_FOR_EXTERNAL_PLUGINS(method))
+ return nd_log_id2method(method);
+ }
+
+ return nd_log_id2method(NDLM_STDERR);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// facilities
+//
+// sys/syslog.h (Linux)
+// sys/sys/syslog.h (FreeBSD)
+// bsd/sys/syslog.h (darwin-xnu)
+
+static struct {
+ int facility;
+ const char *name;
+} nd_log_facilities[] = {
+ { LOG_AUTH, "auth" },
+ { LOG_AUTHPRIV, "authpriv" },
+ { LOG_CRON, "cron" },
+ { LOG_DAEMON, "daemon" },
+ { LOG_FTP, "ftp" },
+ { LOG_KERN, "kern" },
+ { LOG_LPR, "lpr" },
+ { LOG_MAIL, "mail" },
+ { LOG_NEWS, "news" },
+ { LOG_SYSLOG, "syslog" },
+ { LOG_USER, "user" },
+ { LOG_UUCP, "uucp" },
+ { LOG_LOCAL0, "local0" },
+ { LOG_LOCAL1, "local1" },
+ { LOG_LOCAL2, "local2" },
+ { LOG_LOCAL3, "local3" },
+ { LOG_LOCAL4, "local4" },
+ { LOG_LOCAL5, "local5" },
+ { LOG_LOCAL6, "local6" },
+ { LOG_LOCAL7, "local7" },
+
+#ifdef __FreeBSD__
+ { LOG_CONSOLE, "console" },
+ { LOG_NTP, "ntp" },
+
+ // FreeBSD does not consider 'security' as deprecated.
+ { LOG_SECURITY, "security" },
+#else
+ // For all other O/S 'security' is mapped to 'auth'.
+ { LOG_AUTH, "security" },
+#endif
+
+#ifdef __APPLE__
+ { LOG_INSTALL, "install" },
+ { LOG_NETINFO, "netinfo" },
+ { LOG_RAS, "ras" },
+ { LOG_REMOTEAUTH, "remoteauth" },
+ { LOG_LAUNCHD, "launchd" },
+
+#endif
+};
+
+int nd_log_facility2id(const char *facility) {
+ size_t entries = sizeof(nd_log_facilities) / sizeof(nd_log_facilities[0]);
+ for(size_t i = 0; i < entries ;i++) {
+ if(strcmp(nd_log_facilities[i].name, facility) == 0)
+ return nd_log_facilities[i].facility;
+ }
+
+ return LOG_DAEMON;
+}
+
+const char *nd_log_id2facility(int facility) {
+ size_t entries = sizeof(nd_log_facilities) / sizeof(nd_log_facilities[0]);
+ for(size_t i = 0; i < entries ;i++) {
+ if(nd_log_facilities[i].facility == facility)
+ return nd_log_facilities[i].name;
+ }
+
+ return "daemon";
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// priorities
+
+static struct {
+ ND_LOG_FIELD_PRIORITY priority;
+ const char *name;
+} nd_log_priorities[] = {
+ { .priority = NDLP_EMERG, .name = "emergency" },
+ { .priority = NDLP_EMERG, .name = "emerg" },
+ { .priority = NDLP_ALERT, .name = "alert" },
+ { .priority = NDLP_CRIT, .name = "critical" },
+ { .priority = NDLP_CRIT, .name = "crit" },
+ { .priority = NDLP_ERR, .name = "error" },
+ { .priority = NDLP_ERR, .name = "err" },
+ { .priority = NDLP_WARNING, .name = "warning" },
+ { .priority = NDLP_WARNING, .name = "warn" },
+ { .priority = NDLP_NOTICE, .name = "notice" },
+ { .priority = NDLP_INFO, .name = NDLP_INFO_STR },
+ { .priority = NDLP_DEBUG, .name = "debug" },
+};
+
+int nd_log_priority2id(const char *priority) {
+ size_t entries = sizeof(nd_log_priorities) / sizeof(nd_log_priorities[0]);
+ for(size_t i = 0; i < entries ;i++) {
+ if(strcmp(nd_log_priorities[i].name, priority) == 0)
+ return nd_log_priorities[i].priority;
+ }
+
+ return NDLP_INFO;
+}
+
+const char *nd_log_id2priority(ND_LOG_FIELD_PRIORITY priority) {
+ size_t entries = sizeof(nd_log_priorities) / sizeof(nd_log_priorities[0]);
+ for(size_t i = 0; i < entries ;i++) {
+ if(priority == nd_log_priorities[i].priority)
+ return nd_log_priorities[i].name;
+ }
+
+ return NDLP_INFO_STR;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// log sources
+
+const char *nd_log_sources[] = {
+ [NDLS_UNSET] = "UNSET",
+ [NDLS_ACCESS] = "access",
+ [NDLS_ACLK] = "aclk",
+ [NDLS_COLLECTORS] = "collector",
+ [NDLS_DAEMON] = "daemon",
+ [NDLS_HEALTH] = "health",
+ [NDLS_DEBUG] = "debug",
+};
+
+size_t nd_log_source2id(const char *source, ND_LOG_SOURCES def) {
+ size_t entries = sizeof(nd_log_sources) / sizeof(nd_log_sources[0]);
+ for(size_t i = 0; i < entries ;i++) {
+ if(strcmp(nd_log_sources[i], source) == 0)
+ return i;
+ }
+
+ return def;
+}
+
+
+const char *nd_log_id2source(ND_LOG_SOURCES source) {
+ size_t entries = sizeof(nd_log_sources) / sizeof(nd_log_sources[0]);
+ if(source < entries)
+ return nd_log_sources[source];
+
+ return nd_log_sources[NDLS_COLLECTORS];
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// log output formats
+
+static struct {
+ ND_LOG_FORMAT format;
+ const char *name;
+} nd_log_formats[] = {
+ { .format = NDLF_JOURNAL, .name = "journal" },
+ { .format = NDLF_LOGFMT, .name = "logfmt" },
+ { .format = NDLF_JSON, .name = "json" },
+#if defined(OS_WINDOWS)
+#if defined(HAVE_ETW)
+ { .format = NDLF_ETW, .name = ETW_NAME },
+#endif
+#if defined(HAVE_WEL)
+ { .format = NDLF_WEL, .name = WEL_NAME },
+#endif
+#endif
+};
+
+ND_LOG_FORMAT nd_log_format2id(const char *format) {
+ if(!format || !*format)
+ return NDLF_LOGFMT;
+
+ size_t entries = sizeof(nd_log_formats) / sizeof(nd_log_formats[0]);
+ for(size_t i = 0; i < entries ;i++) {
+ if(strcmp(nd_log_formats[i].name, format) == 0)
+ return nd_log_formats[i].format;
+ }
+
+ return NDLF_LOGFMT;
+}
+
+const char *nd_log_id2format(ND_LOG_FORMAT format) {
+ size_t entries = sizeof(nd_log_formats) / sizeof(nd_log_formats[0]);
+ for(size_t i = 0; i < entries ;i++) {
+ if(format == nd_log_formats[i].format)
+ return nd_log_formats[i].name;
+ }
+
+ return "logfmt";
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+struct nd_log nd_log = {
+ .overwrite_process_source = 0,
+ .journal = {
+ .initialized = false,
+ .first_msg = false,
+ .fd = -1,
+ },
+ .journal_direct = {
+ .initialized = false,
+ .fd = -1,
+ },
+ .syslog = {
+ .initialized = false,
+ .facility = LOG_DAEMON,
+ },
+#if defined(OS_WINDOWS)
+ .eventlog = {
+ .initialized = false,
+ },
+#endif
+ .std_output = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .initialized = false,
+ },
+ .std_error = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .initialized = false,
+ },
+ .sources = {
+ [NDLS_UNSET] = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .method = NDLM_DISABLED,
+ .format = NDLF_JOURNAL,
+ .filename = NULL,
+ .fd = -1,
+ .fp = NULL,
+ .min_priority = NDLP_EMERG,
+ .limits = ND_LOG_LIMITS_UNLIMITED,
+ },
+ [NDLS_ACCESS] = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .method = NDLM_DEFAULT,
+ .format = NDLF_LOGFMT,
+ .filename = LOG_DIR "/access.log",
+ .fd = -1,
+ .fp = NULL,
+ .min_priority = NDLP_DEBUG,
+ .limits = ND_LOG_LIMITS_UNLIMITED,
+ },
+ [NDLS_ACLK] = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .method = NDLM_FILE,
+ .format = NDLF_LOGFMT,
+ .filename = LOG_DIR "/aclk.log",
+ .fd = -1,
+ .fp = NULL,
+ .min_priority = NDLP_DEBUG,
+ .limits = ND_LOG_LIMITS_UNLIMITED,
+ },
+ [NDLS_COLLECTORS] = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .method = NDLM_DEFAULT,
+ .format = NDLF_LOGFMT,
+ .filename = LOG_DIR "/collector.log",
+ .fd = STDERR_FILENO,
+ .fp = NULL,
+ .min_priority = NDLP_INFO,
+ .limits = ND_LOG_LIMITS_DEFAULT,
+ },
+ [NDLS_DEBUG] = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .method = NDLM_DISABLED,
+ .format = NDLF_LOGFMT,
+ .filename = LOG_DIR "/debug.log",
+ .fd = STDOUT_FILENO,
+ .fp = NULL,
+ .min_priority = NDLP_DEBUG,
+ .limits = ND_LOG_LIMITS_UNLIMITED,
+ },
+ [NDLS_DAEMON] = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .method = NDLM_DEFAULT,
+ .filename = LOG_DIR "/daemon.log",
+ .format = NDLF_LOGFMT,
+ .fd = -1,
+ .fp = NULL,
+ .min_priority = NDLP_INFO,
+ .limits = ND_LOG_LIMITS_DEFAULT,
+ },
+ [NDLS_HEALTH] = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .method = NDLM_DEFAULT,
+ .format = NDLF_LOGFMT,
+ .filename = LOG_DIR "/health.log",
+ .fd = -1,
+ .fp = NULL,
+ .min_priority = NDLP_DEBUG,
+ .limits = ND_LOG_LIMITS_UNLIMITED,
+ },
+ },
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+__thread struct log_stack_entry *thread_log_stack_base[THREAD_LOG_STACK_MAX];
+__thread size_t thread_log_stack_next = 0;
+__thread struct log_field thread_log_fields[_NDF_MAX] = {
+ // THE ORDER HERE IS IRRELEVANT (but keep them sorted by their number)
+
+ [NDF_STOP] = { // processing will not stop on this - so it is ok to be first
+ .journal = NULL,
+ .logfmt = NULL,
+ .eventlog = NULL,
+ .annotator = NULL,
+ },
+ [NDF_TIMESTAMP_REALTIME_USEC] = {
+ .journal = NULL,
+ .eventlog = "Timestamp",
+ .logfmt = "time",
+ .annotator = timestamp_usec_annotator,
+ },
+ [NDF_SYSLOG_IDENTIFIER] = {
+ .journal = "SYSLOG_IDENTIFIER", // standard journald field
+ .eventlog = "Program",
+ .logfmt = "comm",
+ },
+ [NDF_LOG_SOURCE] = {
+ .journal = "ND_LOG_SOURCE",
+ .eventlog = "NetdataLogSource",
+ .logfmt = "source",
+ },
+ [NDF_PRIORITY] = {
+ .journal = "PRIORITY", // standard journald field
+ .eventlog = "Level",
+ .logfmt = "level",
+ .annotator = priority_annotator,
+ },
+ [NDF_ERRNO] = {
+ .journal = "ERRNO", // standard journald field
+ .eventlog = "UnixErrno",
+ .logfmt = "errno",
+ .annotator = errno_annotator,
+ },
+ [NDF_WINERROR] = {
+#if defined(OS_WINDOWS)
+ .journal = "WINERROR",
+ .eventlog = "WindowsLastError",
+ .logfmt = "winerror",
+ .annotator = winerror_annotator,
+#endif
+ },
+ [NDF_INVOCATION_ID] = {
+ .journal = "INVOCATION_ID", // standard journald field
+ .eventlog = "InvocationID",
+ .logfmt = NULL,
+ },
+ [NDF_LINE] = {
+ .journal = "CODE_LINE", // standard journald field
+ .eventlog = "CodeLine",
+ .logfmt = NULL,
+ },
+ [NDF_FILE] = {
+ .journal = "CODE_FILE", // standard journald field
+ .eventlog = "CodeFile",
+ .logfmt = NULL,
+ },
+ [NDF_FUNC] = {
+ .journal = "CODE_FUNC", // standard journald field
+ .eventlog = "CodeFunction",
+ .logfmt = NULL,
+ },
+ [NDF_TID] = {
+ .journal = "TID", // standard journald field
+ .eventlog = "ThreadID",
+ .logfmt = "tid",
+ },
+ [NDF_THREAD_TAG] = {
+ .journal = "THREAD_TAG",
+ .eventlog = "ThreadName",
+ .logfmt = "thread",
+ },
+ [NDF_MESSAGE_ID] = {
+ .journal = "MESSAGE_ID",
+ .eventlog = "MessageID",
+ .logfmt = "msg_id",
+ },
+ [NDF_MODULE] = {
+ .journal = "ND_MODULE",
+ .eventlog = "Module",
+ .logfmt = "module",
+ },
+ [NDF_NIDL_NODE] = {
+ .journal = "ND_NIDL_NODE",
+ .eventlog = "Node",
+ .logfmt = "node",
+ },
+ [NDF_NIDL_INSTANCE] = {
+ .journal = "ND_NIDL_INSTANCE",
+ .eventlog = "Instance",
+ .logfmt = "instance",
+ },
+ [NDF_NIDL_CONTEXT] = {
+ .journal = "ND_NIDL_CONTEXT",
+ .eventlog = "Context",
+ .logfmt = "context",
+ },
+ [NDF_NIDL_DIMENSION] = {
+ .journal = "ND_NIDL_DIMENSION",
+ .eventlog = "Dimension",
+ .logfmt = "dimension",
+ },
+ [NDF_SRC_TRANSPORT] = {
+ .journal = "ND_SRC_TRANSPORT",
+ .eventlog = "SourceTransport",
+ .logfmt = "src_transport",
+ },
+ [NDF_ACCOUNT_ID] = {
+ .journal = "ND_ACCOUNT_ID",
+ .eventlog = "AccountID",
+ .logfmt = "account",
+ },
+ [NDF_USER_NAME] = {
+ .journal = "ND_USER_NAME",
+ .eventlog = "UserName",
+ .logfmt = "user",
+ },
+ [NDF_USER_ROLE] = {
+ .journal = "ND_USER_ROLE",
+ .eventlog = "UserRole",
+ .logfmt = "role",
+ },
+ [NDF_USER_ACCESS] = {
+ .journal = "ND_USER_PERMISSIONS",
+ .eventlog = "UserPermissions",
+ .logfmt = "permissions",
+ },
+ [NDF_SRC_IP] = {
+ .journal = "ND_SRC_IP",
+ .eventlog = "SourceIP",
+ .logfmt = "src_ip",
+ },
+ [NDF_SRC_FORWARDED_HOST] = {
+ .journal = "ND_SRC_FORWARDED_HOST",
+ .eventlog = "SourceForwardedHost",
+ .logfmt = "src_forwarded_host",
+ },
+ [NDF_SRC_FORWARDED_FOR] = {
+ .journal = "ND_SRC_FORWARDED_FOR",
+ .eventlog = "SourceForwardedFor",
+ .logfmt = "src_forwarded_for",
+ },
+ [NDF_SRC_PORT] = {
+ .journal = "ND_SRC_PORT",
+ .eventlog = "SourcePort",
+ .logfmt = "src_port",
+ },
+ [NDF_SRC_CAPABILITIES] = {
+ .journal = "ND_SRC_CAPABILITIES",
+ .eventlog = "SourceCapabilities",
+ .logfmt = "src_capabilities",
+ },
+ [NDF_DST_TRANSPORT] = {
+ .journal = "ND_DST_TRANSPORT",
+ .eventlog = "DestinationTransport",
+ .logfmt = "dst_transport",
+ },
+ [NDF_DST_IP] = {
+ .journal = "ND_DST_IP",
+ .eventlog = "DestinationIP",
+ .logfmt = "dst_ip",
+ },
+ [NDF_DST_PORT] = {
+ .journal = "ND_DST_PORT",
+ .eventlog = "DestinationPort",
+ .logfmt = "dst_port",
+ },
+ [NDF_DST_CAPABILITIES] = {
+ .journal = "ND_DST_CAPABILITIES",
+ .eventlog = "DestinationCapabilities",
+ .logfmt = "dst_capabilities",
+ },
+ [NDF_REQUEST_METHOD] = {
+ .journal = "ND_REQUEST_METHOD",
+ .eventlog = "RequestMethod",
+ .logfmt = "req_method",
+ },
+ [NDF_RESPONSE_CODE] = {
+ .journal = "ND_RESPONSE_CODE",
+ .eventlog = "ResponseCode",
+ .logfmt = "code",
+ },
+ [NDF_CONNECTION_ID] = {
+ .journal = "ND_CONNECTION_ID",
+ .eventlog = "ConnectionID",
+ .logfmt = "conn",
+ },
+ [NDF_TRANSACTION_ID] = {
+ .journal = "ND_TRANSACTION_ID",
+ .eventlog = "TransactionID",
+ .logfmt = "transaction",
+ },
+ [NDF_RESPONSE_SENT_BYTES] = {
+ .journal = "ND_RESPONSE_SENT_BYTES",
+ .eventlog = "ResponseSentBytes",
+ .logfmt = "sent_bytes",
+ },
+ [NDF_RESPONSE_SIZE_BYTES] = {
+ .journal = "ND_RESPONSE_SIZE_BYTES",
+ .eventlog = "ResponseSizeBytes",
+ .logfmt = "size_bytes",
+ },
+ [NDF_RESPONSE_PREPARATION_TIME_USEC] = {
+ .journal = "ND_RESPONSE_PREP_TIME_USEC",
+ .eventlog = "ResponsePreparationTimeUsec",
+ .logfmt = "prep_ut",
+ },
+ [NDF_RESPONSE_SENT_TIME_USEC] = {
+ .journal = "ND_RESPONSE_SENT_TIME_USEC",
+ .eventlog = "ResponseSentTimeUsec",
+ .logfmt = "sent_ut",
+ },
+ [NDF_RESPONSE_TOTAL_TIME_USEC] = {
+ .journal = "ND_RESPONSE_TOTAL_TIME_USEC",
+ .eventlog = "ResponseTotalTimeUsec",
+ .logfmt = "total_ut",
+ },
+ [NDF_ALERT_ID] = {
+ .journal = "ND_ALERT_ID",
+ .eventlog = "AlertID",
+ .logfmt = "alert_id",
+ },
+ [NDF_ALERT_UNIQUE_ID] = {
+ .journal = "ND_ALERT_UNIQUE_ID",
+ .eventlog = "AlertUniqueID",
+ .logfmt = "alert_unique_id",
+ },
+ [NDF_ALERT_TRANSITION_ID] = {
+ .journal = "ND_ALERT_TRANSITION_ID",
+ .eventlog = "AlertTransitionID",
+ .logfmt = "alert_transition_id",
+ },
+ [NDF_ALERT_EVENT_ID] = {
+ .journal = "ND_ALERT_EVENT_ID",
+ .eventlog = "AlertEventID",
+ .logfmt = "alert_event_id",
+ },
+ [NDF_ALERT_CONFIG_HASH] = {
+ .journal = "ND_ALERT_CONFIG",
+ .eventlog = "AlertConfig",
+ .logfmt = "alert_config",
+ },
+ [NDF_ALERT_NAME] = {
+ .journal = "ND_ALERT_NAME",
+ .eventlog = "AlertName",
+ .logfmt = "alert",
+ },
+ [NDF_ALERT_CLASS] = {
+ .journal = "ND_ALERT_CLASS",
+ .eventlog = "AlertClass",
+ .logfmt = "alert_class",
+ },
+ [NDF_ALERT_COMPONENT] = {
+ .journal = "ND_ALERT_COMPONENT",
+ .eventlog = "AlertComponent",
+ .logfmt = "alert_component",
+ },
+ [NDF_ALERT_TYPE] = {
+ .journal = "ND_ALERT_TYPE",
+ .eventlog = "AlertType",
+ .logfmt = "alert_type",
+ },
+ [NDF_ALERT_EXEC] = {
+ .journal = "ND_ALERT_EXEC",
+ .eventlog = "AlertExec",
+ .logfmt = "alert_exec",
+ },
+ [NDF_ALERT_RECIPIENT] = {
+ .journal = "ND_ALERT_RECIPIENT",
+ .eventlog = "AlertRecipient",
+ .logfmt = "alert_recipient",
+ },
+ [NDF_ALERT_VALUE] = {
+ .journal = "ND_ALERT_VALUE",
+ .eventlog = "AlertValue",
+ .logfmt = "alert_value",
+ },
+ [NDF_ALERT_VALUE_OLD] = {
+ .journal = "ND_ALERT_VALUE_OLD",
+ .eventlog = "AlertOldValue",
+ .logfmt = "alert_value_old",
+ },
+ [NDF_ALERT_STATUS] = {
+ .journal = "ND_ALERT_STATUS",
+ .eventlog = "AlertStatus",
+ .logfmt = "alert_status",
+ },
+ [NDF_ALERT_STATUS_OLD] = {
+ .journal = "ND_ALERT_STATUS_OLD",
+ .eventlog = "AlertOldStatus",
+ .logfmt = "alert_value_old",
+ },
+ [NDF_ALERT_UNITS] = {
+ .journal = "ND_ALERT_UNITS",
+ .eventlog = "AlertUnits",
+ .logfmt = "alert_units",
+ },
+ [NDF_ALERT_SUMMARY] = {
+ .journal = "ND_ALERT_SUMMARY",
+ .eventlog = "AlertSummary",
+ .logfmt = "alert_summary",
+ },
+ [NDF_ALERT_INFO] = {
+ .journal = "ND_ALERT_INFO",
+ .eventlog = "AlertInfo",
+ .logfmt = "alert_info",
+ },
+ [NDF_ALERT_DURATION] = {
+ .journal = "ND_ALERT_DURATION",
+ .eventlog = "AlertDuration",
+ .logfmt = "alert_duration",
+ },
+ [NDF_ALERT_NOTIFICATION_REALTIME_USEC] = {
+ .journal = "ND_ALERT_NOTIFICATION_TIMESTAMP_USEC",
+ .eventlog = "AlertNotificationTime",
+ .logfmt = "alert_notification_timestamp",
+ .annotator = timestamp_usec_annotator,
+ },
+
+ // put new items here
+ // leave the request URL and the message last
+
+ [NDF_REQUEST] = {
+ .journal = "ND_REQUEST",
+ .eventlog = "Request",
+ .logfmt = "request",
+ },
+ [NDF_MESSAGE] = {
+ .journal = "MESSAGE",
+ .eventlog = "Message",
+ .logfmt = "msg",
+ },
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+void log_stack_pop(void *ptr) {
+ if(!ptr) return;
+
+ struct log_stack_entry *lgs = *(struct log_stack_entry (*)[])ptr;
+
+ if(unlikely(!thread_log_stack_next || lgs != thread_log_stack_base[thread_log_stack_next - 1])) {
+ fatal("You cannot pop in the middle of the stack, or an item not in the stack");
+ return;
+ }
+
+ thread_log_stack_next--;
+}
+
+void log_stack_push(struct log_stack_entry *lgs) {
+ if(!lgs || thread_log_stack_next >= THREAD_LOG_STACK_MAX) return;
+ thread_log_stack_base[thread_log_stack_next++] = lgs;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+ND_LOG_FIELD_ID nd_log_field_id_by_journal_name(const char *field, size_t len) {
+ for(size_t i = 0; i < THREAD_FIELDS_MAX ;i++) {
+ if(thread_log_fields[i].journal && strlen(thread_log_fields[i].journal) == len && strncmp(field, thread_log_fields[i].journal, len) == 0)
+ return i;
+ }
+
+ return NDF_STOP;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+int nd_log_health_fd(void) {
+ if(nd_log.sources[NDLS_HEALTH].method == NDLM_FILE && nd_log.sources[NDLS_HEALTH].fd != -1)
+ return nd_log.sources[NDLS_HEALTH].fd;
+
+ return STDERR_FILENO;
+}
+
+int nd_log_collectors_fd(void) {
+ if(nd_log.sources[NDLS_COLLECTORS].method == NDLM_FILE && nd_log.sources[NDLS_COLLECTORS].fd != -1)
+ return nd_log.sources[NDLS_COLLECTORS].fd;
+
+ return STDERR_FILENO;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+void log_date(char *buffer, size_t len, time_t now) {
+ if(unlikely(!buffer || !len))
+ return;
+
+ time_t t = now;
+ struct tm *tmp, tmbuf;
+
+ tmp = localtime_r(&t, &tmbuf);
+
+ if (unlikely(!tmp)) {
+ buffer[0] = '\0';
+ return;
+ }
+
+ if (unlikely(strftime(buffer, len, "%Y-%m-%d %H:%M:%S", tmp) == 0))
+ buffer[0] = '\0';
+
+ buffer[len - 1] = '\0';
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+bool nd_log_replace_existing_fd(struct nd_log_source *e, int new_fd) {
+ if(new_fd == -1 || e->fd == -1 ||
+ (e->fd == STDOUT_FILENO && nd_log.std_output.initialized) ||
+ (e->fd == STDERR_FILENO && nd_log.std_error.initialized))
+ return false;
+
+ if(new_fd != e->fd) {
+ int t = dup2(new_fd, e->fd);
+
+ bool ret = true;
+ if (t == -1) {
+ netdata_log_error("Cannot dup2() new fd %d to old fd %d for '%s'", new_fd, e->fd, e->filename);
+ ret = false;
+ }
+ else
+ close(new_fd);
+
+ if(e->fd == STDOUT_FILENO)
+ nd_log.std_output.initialized = true;
+ else if(e->fd == STDERR_FILENO)
+ nd_log.std_error.initialized = true;
+
+ return ret;
+ }
+
+ return false;
+}
diff --git a/src/libnetdata/log/nd_log-internals.h b/src/libnetdata/log/nd_log-internals.h
new file mode 100644
index 000000000..7bebf5a4a
--- /dev/null
+++ b/src/libnetdata/log/nd_log-internals.h
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_ND_LOG_INTERNALS_H
+#define NETDATA_ND_LOG_INTERNALS_H
+
+#include "../libnetdata.h"
+
+#ifdef __FreeBSD__
+#include <sys/endian.h>
+#endif
+
+#ifdef __APPLE__
+#include <machine/endian.h>
+#endif
+
+#if !defined(ENABLE_SENTRY) && defined(HAVE_BACKTRACE)
+#include <execinfo.h>
+#endif
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-journal.h>
+#endif
+
+const char *errno2str(int errnum, char *buf, size_t size);
+
+// --------------------------------------------------------------------------------------------------------------------
+// ND_LOG_METHOD
+
+typedef enum __attribute__((__packed__)) {
+ NDLM_DISABLED = 0,
+ NDLM_DEVNULL,
+ NDLM_DEFAULT,
+ NDLM_JOURNAL,
+ NDLM_SYSLOG,
+ NDLM_STDOUT,
+ NDLM_STDERR,
+ NDLM_FILE,
+#if defined(OS_WINDOWS)
+#if defined(HAVE_ETW)
+ NDLM_ETW,
+#endif
+#if defined(HAVE_WEL)
+ NDLM_WEL,
+#endif
+#endif
+} ND_LOG_METHOD;
+
+// all the log methods are finally mapped to these
+#if defined(HAVE_ETW)
+#define ETW_CONDITION(ndlo) ((ndlo) == NDLM_ETW)
+#else
+#define ETW_CONDITION(ndlo) (false)
+#endif
+
+#if defined(HAVE_WEL)
+#define WEL_CONDITION(ndlo) ((ndlo) == NDLM_WEL)
+#else
+#define WEL_CONDITION(ndlo) (false)
+#endif
+
+#define IS_VALID_LOG_METHOD_FOR_EXTERNAL_PLUGINS(ndlo) ((ndlo) == NDLM_JOURNAL || (ndlo) == NDLM_SYSLOG || (ndlo) == NDLM_STDERR || ETW_CONDITION(ndlo) || WEL_CONDITION(ndlo))
+#define IS_FINAL_LOG_METHOD(ndlo) ((ndlo) == NDLM_FILE || (ndlo) == NDLM_JOURNAL || (ndlo) == NDLM_SYSLOG || ETW_CONDITION(ndlo) || WEL_CONDITION(ndlo))
+
+ND_LOG_METHOD nd_log_method2id(const char *method);
+const char *nd_log_id2method(ND_LOG_METHOD method);
+
+// --------------------------------------------------------------------------------------------------------------------
+// ND_LOG_FORMAT
+
+typedef enum __attribute__((__packed__)) {
+ NDLF_JOURNAL,
+ NDLF_LOGFMT,
+ NDLF_JSON,
+#if defined(OS_WINDOWS)
+#if defined(HAVE_ETW)
+ NDLF_ETW, // Event Tracing for Windows
+#endif
+#if defined(HAVE_WEL)
+ NDLF_WEL, // Windows Event Log
+#endif
+#endif
+} ND_LOG_FORMAT;
+
+#define ETW_NAME "etw"
+#define WEL_NAME "wel"
+
+const char *nd_log_id2format(ND_LOG_FORMAT format);
+ND_LOG_FORMAT nd_log_format2id(const char *format);
+
+size_t nd_log_source2id(const char *source, ND_LOG_SOURCES def);
+const char *nd_log_id2source(ND_LOG_SOURCES source);
+
+const char *nd_log_id2priority(ND_LOG_FIELD_PRIORITY priority);
+int nd_log_priority2id(const char *priority);
+
+const char *nd_log_id2facility(int facility);
+int nd_log_facility2id(const char *facility);
+
+#include "nd_log_limit.h"
+
+struct nd_log_source {
+ SPINLOCK spinlock;
+ ND_LOG_METHOD method;
+ ND_LOG_FORMAT format;
+ const char *filename;
+ int fd;
+ FILE *fp;
+
+ ND_LOG_FIELD_PRIORITY min_priority;
+ const char *pending_msg;
+ struct nd_log_limit limits;
+
+#if defined(OS_WINDOWS)
+ ND_LOG_SOURCES source;
+ HANDLE hEventLog;
+ USHORT channelID;
+ UCHAR Opcode;
+ USHORT Task;
+ ULONGLONG Keyword;
+#endif
+};
+
+struct nd_log {
+ nd_uuid_t invocation_id;
+
+ ND_LOG_SOURCES overwrite_process_source;
+
+ struct nd_log_source sources[_NDLS_MAX];
+
+ struct {
+ bool initialized;
+ bool first_msg;
+ int fd; // we don't control this, we just detect it to keep it open
+ } journal;
+
+ struct {
+ bool initialized;
+ int fd;
+ char filename[FILENAME_MAX];
+ } journal_direct;
+
+ struct {
+ bool initialized;
+ int facility;
+ } syslog;
+
+ struct {
+ bool etw; // when set use etw, otherwise wel
+ bool initialized;
+ } eventlog;
+
+ struct {
+ SPINLOCK spinlock;
+ bool initialized;
+ } std_output;
+
+ struct {
+ SPINLOCK spinlock;
+ bool initialized;
+ } std_error;
+
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+struct log_field;
+typedef const char *(*annotator_t)(struct log_field *lf);
+
+struct log_field {
+ const char *journal;
+ const char *logfmt;
+ const char *eventlog;
+ annotator_t annotator;
+ struct log_stack_entry entry;
+};
+
+#define THREAD_LOG_STACK_MAX 50
+#define THREAD_FIELDS_MAX (sizeof(thread_log_fields) / sizeof(thread_log_fields[0]))
+
+extern __thread struct log_stack_entry *thread_log_stack_base[THREAD_LOG_STACK_MAX];
+extern __thread size_t thread_log_stack_next;
+extern __thread struct log_field thread_log_fields[_NDF_MAX];
+
+// --------------------------------------------------------------------------------------------------------------------
+
+extern struct nd_log nd_log;
+bool nd_log_replace_existing_fd(struct nd_log_source *e, int new_fd);
+void nd_log_open(struct nd_log_source *e, ND_LOG_SOURCES source);
+void nd_log_stdin_init(int fd, const char *filename);
+
+// --------------------------------------------------------------------------------------------------------------------
+// annotators
+
+struct log_field;
+const char *errno_annotator(struct log_field *lf);
+const char *priority_annotator(struct log_field *lf);
+const char *timestamp_usec_annotator(struct log_field *lf);
+
+#if defined(OS_WINDOWS)
+const char *winerror_annotator(struct log_field *lf);
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+// field formatters
+
+uint64_t log_field_to_uint64(struct log_field *lf);
+int64_t log_field_to_int64(struct log_field *lf);
+
+// --------------------------------------------------------------------------------------------------------------------
+// common text formatters
+
+void nd_logger_logfmt(BUFFER *wb, struct log_field *fields, size_t fields_max);
+void nd_logger_json(BUFFER *wb, struct log_field *fields, size_t fields_max);
+
+// --------------------------------------------------------------------------------------------------------------------
+// output to syslog
+
+void nd_log_init_syslog(void);
+void nd_log_reset_syslog(void);
+bool nd_logger_syslog(int priority, ND_LOG_FORMAT format, struct log_field *fields, size_t fields_max);
+
+// --------------------------------------------------------------------------------------------------------------------
+// output to systemd-journal
+
+bool nd_log_journal_systemd_init(void);
+bool nd_log_journal_direct_init(const char *path);
+bool nd_logger_journal_direct(struct log_field *fields, size_t fields_max);
+bool nd_logger_journal_libsystemd(struct log_field *fields, size_t fields_max);
+
+// --------------------------------------------------------------------------------------------------------------------
+// output to file
+
+bool nd_logger_file(FILE *fp, ND_LOG_FORMAT format, struct log_field *fields, size_t fields_max);
+
+// --------------------------------------------------------------------------------------------------------------------
+// output to windows events log
+
+#if defined(OS_WINDOWS)
+#if defined(HAVE_ETW)
+bool nd_log_init_etw(void);
+bool nd_logger_etw(struct nd_log_source *source, struct log_field *fields, size_t fields_max);
+#endif
+#if defined(HAVE_WEL)
+bool nd_log_init_wel(void);
+bool nd_logger_wel(struct nd_log_source *source, struct log_field *fields, size_t fields_max);
+#endif
+#endif
+
+#endif //NETDATA_ND_LOG_INTERNALS_H
diff --git a/src/libnetdata/log/nd_log-to-file.c b/src/libnetdata/log/nd_log-to-file.c
new file mode 100644
index 000000000..2de76536b
--- /dev/null
+++ b/src/libnetdata/log/nd_log-to-file.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log-internals.h"
+
+void chown_open_file(int fd, uid_t uid, gid_t gid) {
+ if(fd == -1) return;
+
+ struct stat buf;
+
+ if(fstat(fd, &buf) == -1) {
+ netdata_log_error("Cannot fstat() fd %d", fd);
+ return;
+ }
+
+ if((buf.st_uid != uid || buf.st_gid != gid) && S_ISREG(buf.st_mode)) {
+ if(fchown(fd, uid, gid) == -1)
+ netdata_log_error("Cannot fchown() fd %d.", fd);
+ }
+}
+
+void nd_log_chown_log_files(uid_t uid, gid_t gid) {
+ for(size_t i = 0 ; i < _NDLS_MAX ; i++) {
+ if(nd_log.sources[i].fd != -1 && nd_log.sources[i].fd != STDIN_FILENO)
+ chown_open_file(nd_log.sources[i].fd, uid, gid);
+ }
+}
+
+bool nd_logger_file(FILE *fp, ND_LOG_FORMAT format, struct log_field *fields, size_t fields_max) {
+ BUFFER *wb = buffer_create(1024, NULL);
+
+ if(format == NDLF_JSON)
+ nd_logger_json(wb, fields, fields_max);
+ else
+ nd_logger_logfmt(wb, fields, fields_max);
+
+ int r = fprintf(fp, "%s\n", buffer_tostring(wb));
+ fflush(fp);
+
+ buffer_free(wb);
+ return r > 0;
+}
diff --git a/src/libnetdata/log/nd_log-to-syslog.c b/src/libnetdata/log/nd_log-to-syslog.c
new file mode 100644
index 000000000..2903bf591
--- /dev/null
+++ b/src/libnetdata/log/nd_log-to-syslog.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log-internals.h"
+
+void nd_log_init_syslog(void) {
+ if(nd_log.syslog.initialized)
+ return;
+
+ openlog(program_name, LOG_PID, nd_log.syslog.facility);
+ nd_log.syslog.initialized = true;
+}
+
+bool nd_logger_syslog(int priority, ND_LOG_FORMAT format __maybe_unused, struct log_field *fields, size_t fields_max) {
+ CLEAN_BUFFER *wb = buffer_create(1024, NULL);
+
+ nd_logger_logfmt(wb, fields, fields_max);
+ syslog(priority, "%s", buffer_tostring(wb));
+
+ return true;
+}
diff --git a/src/libnetdata/log/nd_log-to-systemd-journal.c b/src/libnetdata/log/nd_log-to-systemd-journal.c
new file mode 100644
index 000000000..922427777
--- /dev/null
+++ b/src/libnetdata/log/nd_log-to-systemd-journal.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log-internals.h"
+
+bool nd_log_journal_systemd_init(void) {
+#ifdef HAVE_SYSTEMD
+ nd_log.journal.initialized = true;
+#else
+ nd_log.journal.initialized = false;
+#endif
+
+ return nd_log.journal.initialized;
+}
+
+static int nd_log_journal_direct_fd_find_and_open(char *filename, size_t size) {
+ int fd;
+
+ if(netdata_configured_host_prefix && *netdata_configured_host_prefix) {
+ journal_construct_path(filename, size, netdata_configured_host_prefix, "netdata");
+ if (is_path_unix_socket(filename) && (fd = journal_direct_fd(filename)) != -1)
+ return fd;
+
+ journal_construct_path(filename, size, netdata_configured_host_prefix, NULL);
+ if (is_path_unix_socket(filename) && (fd = journal_direct_fd(filename)) != -1)
+ return fd;
+ }
+
+ journal_construct_path(filename, size, NULL, "netdata");
+ if (is_path_unix_socket(filename) && (fd = journal_direct_fd(filename)) != -1)
+ return fd;
+
+ journal_construct_path(filename, size, NULL, NULL);
+ if (is_path_unix_socket(filename) && (fd = journal_direct_fd(filename)) != -1)
+ return fd;
+
+ return -1;
+}
+
+bool nd_log_journal_socket_available(void) {
+ char filename[FILENAME_MAX];
+ int fd = nd_log_journal_direct_fd_find_and_open(filename, sizeof(filename));
+ if(fd == -1) return false;
+ close(fd);
+ return true;
+}
+
+static void nd_log_journal_direct_set_env(void) {
+ if(nd_log.sources[NDLS_COLLECTORS].method == NDLM_JOURNAL)
+ nd_setenv("NETDATA_SYSTEMD_JOURNAL_PATH", nd_log.journal_direct.filename, 1);
+}
+
+bool nd_log_journal_direct_init(const char *path) {
+ if(nd_log.journal_direct.initialized) {
+ nd_log_journal_direct_set_env();
+ return true;
+ }
+
+ int fd;
+ char filename[FILENAME_MAX];
+ if(!is_path_unix_socket(path))
+ fd = nd_log_journal_direct_fd_find_and_open(filename, sizeof(filename));
+ else {
+ snprintfz(filename, sizeof(filename), "%s", path);
+ fd = journal_direct_fd(filename);
+ }
+
+ if(fd < 0)
+ return false;
+
+ nd_log.journal_direct.fd = fd;
+ nd_log.journal_direct.initialized = true;
+
+ strncpyz(nd_log.journal_direct.filename, filename, sizeof(nd_log.journal_direct.filename) - 1);
+ nd_log_journal_direct_set_env();
+
+ return true;
+}
+
+bool nd_logger_journal_libsystemd(struct log_field *fields __maybe_unused, size_t fields_max __maybe_unused) {
+#ifdef HAVE_SYSTEMD
+
+ // --- FIELD_PARSER_VERSIONS ---
+ //
+ // IMPORTANT:
+ // THERE ARE 6 VERSIONS OF THIS CODE
+ //
+ // 1. journal (direct socket API),
+ // 2. journal (libsystemd API),
+ // 3. logfmt,
+ // 4. json,
+ // 5. convert to uint64
+ // 6. convert to int64
+ //
+ // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
+
+ struct iovec iov[fields_max];
+ int iov_count = 0;
+
+ memset(iov, 0, sizeof(iov));
+
+ CLEAN_BUFFER *tmp = NULL;
+
+ for (size_t i = 0; i < fields_max; i++) {
+ if (!fields[i].entry.set || !fields[i].journal)
+ continue;
+
+ const char *key = fields[i].journal;
+ char *value = NULL;
+ int rc = 0;
+ switch (fields[i].entry.type) {
+ case NDFT_TXT:
+ if(*fields[i].entry.txt)
+ rc = asprintf(&value, "%s=%s", key, fields[i].entry.txt);
+ break;
+ case NDFT_STR:
+ rc = asprintf(&value, "%s=%s", key, string2str(fields[i].entry.str));
+ break;
+ case NDFT_BFR:
+ if(buffer_strlen(fields[i].entry.bfr))
+ rc = asprintf(&value, "%s=%s", key, buffer_tostring(fields[i].entry.bfr));
+ break;
+ case NDFT_U64:
+ rc = asprintf(&value, "%s=%" PRIu64, key, fields[i].entry.u64);
+ break;
+ case NDFT_I64:
+ rc = asprintf(&value, "%s=%" PRId64, key, fields[i].entry.i64);
+ break;
+ case NDFT_DBL:
+ rc = asprintf(&value, "%s=%f", key, fields[i].entry.dbl);
+ break;
+ case NDFT_UUID:
+ if(!uuid_is_null(*fields[i].entry.uuid)) {
+ char u[UUID_COMPACT_STR_LEN];
+ uuid_unparse_lower_compact(*fields[i].entry.uuid, u);
+ rc = asprintf(&value, "%s=%s", key, u);
+ }
+ break;
+ case NDFT_CALLBACK: {
+ if(!tmp)
+ tmp = buffer_create(1024, NULL);
+ else
+ buffer_flush(tmp);
+ if(fields[i].entry.cb.formatter(tmp, fields[i].entry.cb.formatter_data))
+ rc = asprintf(&value, "%s=%s", key, buffer_tostring(tmp));
+ }
+ break;
+ default:
+ rc = asprintf(&value, "%s=%s", key, "UNHANDLED");
+ break;
+ }
+
+ if (rc != -1 && value) {
+ iov[iov_count].iov_base = value;
+ iov[iov_count].iov_len = strlen(value);
+ iov_count++;
+ }
+ }
+
+ static bool sockets_before[1024];
+ bool detect_systemd_socket = __atomic_load_n(&nd_log.journal.first_msg, __ATOMIC_RELAXED) == false;
+ if(detect_systemd_socket) {
+ for(int i = 3 ; (size_t)i < _countof(sockets_before); i++)
+ sockets_before[i] = fd_is_socket(i);
+ }
+
+ int r = sd_journal_sendv(iov, iov_count);
+
+ if(r == 0 && detect_systemd_socket) {
+ __atomic_store_n(&nd_log.journal.first_msg, true, __ATOMIC_RELAXED);
+
+ // this is the first successful libsystemd log
+ // let's detect its fd number (we need it for the spawn server)
+
+ for(int i = 3 ; (size_t)i < _countof(sockets_before); i++) {
+ if (!sockets_before[i] && fd_is_socket(i)) {
+ nd_log.journal.fd = i;
+ break;
+ }
+ }
+ }
+
+ // Clean up allocated memory
+ for (int i = 0; i < iov_count; i++) {
+ if (iov[i].iov_base != NULL) {
+ free(iov[i].iov_base);
+ }
+ }
+
+ return r == 0;
+#else
+ return false;
+#endif
+}
+
+bool nd_logger_journal_direct(struct log_field *fields, size_t fields_max) {
+ if(!nd_log.journal_direct.initialized)
+ return false;
+
+ // --- FIELD_PARSER_VERSIONS ---
+ //
+ // IMPORTANT:
+ // THERE ARE 6 VERSIONS OF THIS CODE
+ //
+ // 1. journal (direct socket API),
+ // 2. journal (libsystemd API),
+ // 3. logfmt,
+ // 4. json,
+ // 5. convert to uint64
+ // 6. convert to int64
+ //
+ // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
+
+ CLEAN_BUFFER *wb = buffer_create(4096, NULL);
+ CLEAN_BUFFER *tmp = NULL;
+
+ for (size_t i = 0; i < fields_max; i++) {
+ if (!fields[i].entry.set || !fields[i].journal)
+ continue;
+
+ const char *key = fields[i].journal;
+
+ const char *s = NULL;
+ switch(fields[i].entry.type) {
+ case NDFT_TXT:
+ s = fields[i].entry.txt;
+ break;
+ case NDFT_STR:
+ s = string2str(fields[i].entry.str);
+ break;
+ case NDFT_BFR:
+ s = buffer_tostring(fields[i].entry.bfr);
+ break;
+ case NDFT_U64:
+ buffer_strcat(wb, key);
+ buffer_putc(wb, '=');
+ buffer_print_uint64(wb, fields[i].entry.u64);
+ buffer_putc(wb, '\n');
+ break;
+ case NDFT_I64:
+ buffer_strcat(wb, key);
+ buffer_putc(wb, '=');
+ buffer_print_int64(wb, fields[i].entry.i64);
+ buffer_putc(wb, '\n');
+ break;
+ case NDFT_DBL:
+ buffer_strcat(wb, key);
+ buffer_putc(wb, '=');
+ buffer_print_netdata_double(wb, fields[i].entry.dbl);
+ buffer_putc(wb, '\n');
+ break;
+ case NDFT_UUID:
+ if(!uuid_is_null(*fields[i].entry.uuid)) {
+ char u[UUID_COMPACT_STR_LEN];
+ uuid_unparse_lower_compact(*fields[i].entry.uuid, u);
+ buffer_strcat(wb, key);
+ buffer_putc(wb, '=');
+ buffer_fast_strcat(wb, u, sizeof(u) - 1);
+ buffer_putc(wb, '\n');
+ }
+ break;
+ case NDFT_CALLBACK: {
+ if(!tmp)
+ tmp = buffer_create(1024, NULL);
+ else
+ buffer_flush(tmp);
+ if(fields[i].entry.cb.formatter(tmp, fields[i].entry.cb.formatter_data))
+ s = buffer_tostring(tmp);
+ else
+ s = NULL;
+ }
+ break;
+ default:
+ s = "UNHANDLED";
+ break;
+ }
+
+ if(s && *s) {
+ buffer_strcat(wb, key);
+ if(!strchr(s, '\n')) {
+ buffer_putc(wb, '=');
+ buffer_strcat(wb, s);
+ buffer_putc(wb, '\n');
+ }
+ else {
+ buffer_putc(wb, '\n');
+ size_t size = strlen(s);
+ uint64_t le_size = htole64(size);
+ buffer_memcat(wb, &le_size, sizeof(le_size));
+ buffer_memcat(wb, s, size);
+ buffer_putc(wb, '\n');
+ }
+ }
+ }
+
+ return journal_direct_send(nd_log.journal_direct.fd, buffer_tostring(wb), buffer_strlen(wb));
+}
diff --git a/src/libnetdata/log/nd_log-to-windows-common.h b/src/libnetdata/log/nd_log-to-windows-common.h
new file mode 100644
index 000000000..2b2833ed1
--- /dev/null
+++ b/src/libnetdata/log/nd_log-to-windows-common.h
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_ND_LOG_TO_WINDOWS_COMMON_H
+#define NETDATA_ND_LOG_TO_WINDOWS_COMMON_H
+
+// Helper macro to create wide string literals
+#define WIDEN2(x) L ## x
+#define WIDEN(x) WIDEN2(x)
+
+#define NETDATA_ETW_PROVIDER_GUID_STR "{96c5ca72-9bd8-4634-81e5-000014e7da7a}"
+#define NETDATA_ETW_PROVIDER_GUID_STR_W WIDEN(NETDATA_ETW_PROVIDER_GUID)
+
+#define NETDATA_CHANNEL_NAME "Netdata"
+#define NETDATA_CHANNEL_NAME_W WIDEN(NETDATA_CHANNEL_NAME)
+
+#define NETDATA_WEL_CHANNEL_NAME "NetdataWEL"
+#define NETDATA_WEL_CHANNEL_NAME_W WIDEN(NETDATA_WEL_CHANNEL_NAME)
+
+#define NETDATA_ETW_CHANNEL_NAME "Netdata"
+#define NETDATA_ETW_CHANNEL_NAME_W WIDEN(NETDATA_ETW_CHANNEL_NAME)
+
+#define NETDATA_ETW_PROVIDER_NAME "Netdata"
+#define NETDATA_ETW_PROVIDER_NAME_W WIDEN(NETDATA_ETW_PROVIDER_NAME)
+
+#define NETDATA_WEL_PROVIDER_PREFIX "Netdata"
+#define NETDATA_WEL_PROVIDER_PREFIX_W WIDEN(NETDATA_WEL_PROVIDER_PREFIX)
+
+#define NETDATA_WEL_PROVIDER_ACCESS NETDATA_WEL_PROVIDER_PREFIX "Access"
+#define NETDATA_WEL_PROVIDER_ACCESS_W WIDEN(NETDATA_WEL_PROVIDER_ACCESS)
+
+#define NETDATA_WEL_PROVIDER_ACLK NETDATA_WEL_PROVIDER_PREFIX "Aclk"
+#define NETDATA_WEL_PROVIDER_ACLK_W WIDEN(NETDATA_WEL_PROVIDER_ACLK)
+
+#define NETDATA_WEL_PROVIDER_COLLECTORS NETDATA_WEL_PROVIDER_PREFIX "Collectors"
+#define NETDATA_WEL_PROVIDER_COLLECTORS_W WIDEN(NETDATA_WEL_PROVIDER_COLLECTORS)
+
+#define NETDATA_WEL_PROVIDER_DAEMON NETDATA_WEL_PROVIDER_PREFIX "Daemon"
+#define NETDATA_WEL_PROVIDER_DAEMON_W WIDEN(NETDATA_WEL_PROVIDER_DAEMON)
+
+#define NETDATA_WEL_PROVIDER_HEALTH NETDATA_WEL_PROVIDER_PREFIX "Health"
+#define NETDATA_WEL_PROVIDER_HEALTH_W WIDEN(NETDATA_WEL_PROVIDER_HEALTH)
+
+
+#define NETDATA_ETW_SUBCHANNEL_ACCESS "Access"
+#define NETDATA_ETW_SUBCHANNEL_ACCESS_W WIDEN(NETDATA_ETW_SUBCHANNEL_ACCESS)
+
+#define NETDATA_ETW_SUBCHANNEL_ACLK "Aclk"
+#define NETDATA_ETW_SUBCHANNEL_ACLK_W WIDEN(NETDATA_ETW_SUBCHANNEL_ACLK)
+
+#define NETDATA_ETW_SUBCHANNEL_COLLECTORS "Collectors"
+#define NETDATA_ETW_SUBCHANNEL_COLLECTORS_W WIDEN(NETDATA_ETW_SUBCHANNEL_COLLECTORS)
+
+#define NETDATA_ETW_SUBCHANNEL_DAEMON "Daemon"
+#define NETDATA_ETW_SUBCHANNEL_DAEMON_W WIDEN(NETDATA_ETW_SUBCHANNEL_DAEMON)
+
+#define NETDATA_ETW_SUBCHANNEL_HEALTH "Health"
+#define NETDATA_ETW_SUBCHANNEL_HEALTH_W WIDEN(NETDATA_ETW_SUBCHANNEL_HEALTH)
+
+// Define shift values
+#define EVENT_ID_SEV_SHIFT 30
+#define EVENT_ID_C_SHIFT 29
+#define EVENT_ID_R_SHIFT 28
+#define EVENT_ID_FACILITY_SHIFT 16
+#define EVENT_ID_CODE_SHIFT 0
+
+#define EVENT_ID_PRIORITY_SHIFT 0 // Shift 0 bits
+#define EVENT_ID_SOURCE_SHIFT 4 // Shift 4 bits
+
+// Define masks
+#define EVENT_ID_SEV_MASK 0xC0000000 // Bits 31-30
+#define EVENT_ID_C_MASK 0x20000000 // Bit 29
+#define EVENT_ID_R_MASK 0x10000000 // Bit 28
+#define EVENT_ID_FACILITY_MASK 0x0FFF0000 // Bits 27-16
+#define EVENT_ID_CODE_MASK 0x0000FFFF // Bits 15-0
+
+#define EVENT_ID_PRIORITY_MASK 0x000F // Bits 0-3
+#define EVENT_ID_SOURCE_MASK 0x00F0 // Bits 4-7
+
+typedef enum __attribute__((packed)) {
+ MSGID_MESSAGE_ONLY = 1,
+ MSGID_MESSAGE_ERRNO,
+ MSGID_REQUEST_ONLY,
+ MSGID_ALERT_TRANSITION,
+ MSGID_ACCESS,
+ MSGID_ACCESS_FORWARDER,
+ MSGID_ACCESS_USER,
+ MSGID_ACCESS_FORWARDER_USER,
+ MSGID_ACCESS_MESSAGE,
+ MSGID_ACCESS_MESSAGE_REQUEST,
+ MSGID_ACCESS_MESSAGE_USER,
+
+ // terminator
+ _MSGID_MAX,
+} MESSAGE_ID;
+
+static inline uint32_t get_event_type_from_priority(ND_LOG_FIELD_PRIORITY priority) {
+ switch (priority) {
+ case NDLP_EMERG:
+ case NDLP_ALERT:
+ case NDLP_CRIT:
+ case NDLP_ERR:
+ return EVENTLOG_ERROR_TYPE;
+
+ case NDLP_WARNING:
+ return EVENTLOG_WARNING_TYPE;
+
+ case NDLP_NOTICE:
+ case NDLP_INFO:
+ case NDLP_DEBUG:
+ default:
+ return EVENTLOG_INFORMATION_TYPE;
+ }
+}
+
+static inline uint8_t get_severity_from_priority(ND_LOG_FIELD_PRIORITY priority) {
+ switch (priority) {
+ case NDLP_EMERG:
+ case NDLP_ALERT:
+ case NDLP_CRIT:
+ case NDLP_ERR:
+ return STATUS_SEVERITY_ERROR;
+
+ case NDLP_WARNING:
+ return STATUS_SEVERITY_WARNING;
+
+ case NDLP_NOTICE:
+ case NDLP_INFO:
+ case NDLP_DEBUG:
+ default:
+ return STATUS_SEVERITY_INFORMATIONAL;
+ }
+}
+
+static inline uint8_t get_level_from_priority(ND_LOG_FIELD_PRIORITY priority) {
+ switch (priority) {
+ // return 0 = log an event regardless of any filtering applied
+
+ case NDLP_EMERG:
+ case NDLP_ALERT:
+ case NDLP_CRIT:
+ return 1;
+
+ case NDLP_ERR:
+ return 2;
+
+ case NDLP_WARNING:
+ return 3;
+
+ case NDLP_NOTICE:
+ case NDLP_INFO:
+ return 4;
+
+ case NDLP_DEBUG:
+ default:
+ return 5;
+ }
+}
+
+static inline const char *get_level_from_priority_str(ND_LOG_FIELD_PRIORITY priority) {
+ switch (priority) {
+ // return "win:LogAlways" to log an event regardless of any filtering applied
+
+ case NDLP_EMERG:
+ case NDLP_ALERT:
+ case NDLP_CRIT:
+ return "win:Critical";
+
+ case NDLP_ERR:
+ return "win:Error";
+
+ case NDLP_WARNING:
+ return "win:Warning";
+
+ case NDLP_NOTICE:
+ case NDLP_INFO:
+ return "win:Informational";
+
+ case NDLP_DEBUG:
+ default:
+ return "win:Verbose";
+ }
+}
+
+static inline uint16_t construct_event_code(ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, MESSAGE_ID messageID) {
+ return (source << 12 | priority << 8 | messageID << 0);
+}
+
+#endif //NETDATA_ND_LOG_TO_WINDOWS_COMMON_H
diff --git a/src/libnetdata/log/nd_log-to-windows-events.c b/src/libnetdata/log/nd_log-to-windows-events.c
new file mode 100644
index 000000000..f32289daa
--- /dev/null
+++ b/src/libnetdata/log/nd_log-to-windows-events.c
@@ -0,0 +1,554 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log-internals.h"
+
+#if defined(OS_WINDOWS) && (defined(HAVE_ETW) || defined(HAVE_WEL))
+
+// --------------------------------------------------------------------------------------------------------------------
+// construct an event id
+
+// load message resources generated header
+#include "wevt_netdata.h"
+
+// include the common definitions with the message resources and manifest generator
+#include "nd_log-to-windows-common.h"
+
+#if defined(HAVE_ETW)
+// we need the manifest, only in ETW mode
+
+// eliminate compiler warnings and load manifest generated header
+#undef EXTERN_C
+#define EXTERN_C
+#undef __declspec
+#define __declspec(x)
+#include "wevt_netdata_manifest.h"
+
+static REGHANDLE regHandle;
+#endif
+
+// Function to construct EventID
+static DWORD complete_event_id(DWORD facility, DWORD severity, DWORD event_code) {
+ DWORD event_id = 0;
+
+ // Set Severity
+ event_id |= ((DWORD)(severity) << EVENT_ID_SEV_SHIFT) & EVENT_ID_SEV_MASK;
+
+ // Set Customer Code Flag (C)
+ event_id |= (0x0 << EVENT_ID_C_SHIFT) & EVENT_ID_C_MASK;
+
+ // Set Reserved Bit (R) - typically 0
+ event_id |= (0x0 << EVENT_ID_R_SHIFT) & EVENT_ID_R_MASK;
+
+ // Set Facility
+ event_id |= ((DWORD)(facility) << EVENT_ID_FACILITY_SHIFT) & EVENT_ID_FACILITY_MASK;
+
+ // Set Code
+ event_id |= ((DWORD)(event_code) << EVENT_ID_CODE_SHIFT) & EVENT_ID_CODE_MASK;
+
+ return event_id;
+}
+
+DWORD construct_event_id(ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, MESSAGE_ID messageID) {
+ DWORD event_code = construct_event_code(source, priority, messageID);
+ return complete_event_id(FACILITY_NETDATA, get_severity_from_priority(priority), event_code);
+}
+
+static bool check_event_id(ND_LOG_SOURCES source __maybe_unused, ND_LOG_FIELD_PRIORITY priority __maybe_unused, MESSAGE_ID messageID __maybe_unused, DWORD event_code __maybe_unused) {
+#ifdef NETDATA_INTERNAL_CHECKS
+ DWORD generated = construct_event_id(source, priority, messageID);
+ if(generated != event_code) {
+
+ // this is just used for a break point, to see the values in hex
+ char current[UINT64_HEX_MAX_LENGTH];
+ print_uint64_hex(current, generated);
+
+ char wanted[UINT64_HEX_MAX_LENGTH];
+ print_uint64_hex(wanted, event_code);
+
+ const char *got = current;
+ const char *good = wanted;
+ internal_fatal(true, "EventIDs mismatch, expected %s, got %s", good, got);
+ }
+#endif
+
+ return true;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// initialization
+
+// Define provider names per source (only when not using ETW)
+static const wchar_t *wel_provider_per_source[_NDLS_MAX] = {
+ [NDLS_UNSET] = NULL, // not used, linked to NDLS_DAEMON
+ [NDLS_ACCESS] = NETDATA_WEL_PROVIDER_ACCESS_W, //
+ [NDLS_ACLK] = NETDATA_WEL_PROVIDER_ACLK_W, //
+ [NDLS_COLLECTORS] = NETDATA_WEL_PROVIDER_COLLECTORS_W,//
+ [NDLS_DAEMON] = NETDATA_WEL_PROVIDER_DAEMON_W, //
+ [NDLS_HEALTH] = NETDATA_WEL_PROVIDER_HEALTH_W, //
+ [NDLS_DEBUG] = NULL, // used, linked to NDLS_DAEMON
+};
+
+bool wel_replace_program_with_wevt_netdata_dll(wchar_t *str, size_t size) {
+ const wchar_t *replacement = L"\\wevt_netdata.dll";
+
+ // Find the last occurrence of '\\' to isolate the filename
+ wchar_t *lastBackslash = wcsrchr(str, L'\\');
+
+ if (lastBackslash != NULL) {
+ // Calculate new length after replacement
+ size_t newLen = (lastBackslash - str) + wcslen(replacement);
+
+ // Ensure new length does not exceed buffer size
+ if (newLen >= size)
+ return false; // Not enough space in the buffer
+
+ // Terminate the string at the last backslash
+ *lastBackslash = L'\0';
+
+ // Append the replacement filename
+ wcsncat(str, replacement, size - wcslen(str) - 1);
+
+ // Check if the new file exists
+ if (GetFileAttributesW(str) != INVALID_FILE_ATTRIBUTES)
+ return true; // The file exists
+ else
+ return false; // The file does not exist
+ }
+
+ return false; // No backslash found (likely invalid input)
+}
+
+static bool wel_add_to_registry(const wchar_t *channel, const wchar_t *provider, DWORD defaultMaxSize) {
+ // Build the registry path: SYSTEM\CurrentControlSet\Services\EventLog\<LogName>\<SourceName>
+ wchar_t key[MAX_PATH];
+ if(!provider)
+ swprintf(key, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\%ls", channel);
+ else
+ swprintf(key, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\%ls\\%ls", channel, provider);
+
+ HKEY hRegKey;
+ DWORD disposition;
+ LONG result = RegCreateKeyExW(HKEY_LOCAL_MACHINE, key,
+ 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hRegKey, &disposition);
+
+ if (result != ERROR_SUCCESS)
+ return false; // Could not create the registry key
+
+ // Check if MaxSize is already set
+ DWORD maxSize = 0;
+ DWORD size = sizeof(maxSize);
+ if (RegQueryValueExW(hRegKey, L"MaxSize", NULL, NULL, (LPBYTE)&maxSize, &size) != ERROR_SUCCESS) {
+ // MaxSize is not set, set it to the default value
+ RegSetValueExW(hRegKey, L"MaxSize", 0, REG_DWORD, (const BYTE*)&defaultMaxSize, sizeof(defaultMaxSize));
+ }
+
+ wchar_t modulePath[MAX_PATH];
+ if (GetModuleFileNameW(NULL, modulePath, MAX_PATH) == 0) {
+ RegCloseKey(hRegKey);
+ return false;
+ }
+
+ if(wel_replace_program_with_wevt_netdata_dll(modulePath, _countof(modulePath))) {
+ RegSetValueExW(hRegKey, L"EventMessageFile", 0, REG_EXPAND_SZ,
+ (LPBYTE)modulePath, (wcslen(modulePath) + 1) * sizeof(wchar_t));
+
+ DWORD types_supported = EVENTLOG_SUCCESS | EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
+ RegSetValueExW(hRegKey, L"TypesSupported", 0, REG_DWORD, (LPBYTE)&types_supported, sizeof(DWORD));
+ }
+
+ RegCloseKey(hRegKey);
+ return true;
+}
+
+#if defined(HAVE_ETW)
+static void etw_set_source_meta(struct nd_log_source *source, USHORT channelID, const EVENT_DESCRIPTOR *ed) {
+ // It turns out that the keyword varies per only per channel!
+ // so, to log with the right keyword, Task, Opcode we copy the ids from the header
+ // the messages compiler (mc.exe) generated from the manifest.
+
+ source->channelID = channelID;
+ source->Opcode = ed->Opcode;
+ source->Task = ed->Task;
+ source->Keyword = ed->Keyword;
+}
+
+static bool etw_register_provider(void) {
+ // Register the ETW provider
+ if (EventRegister(&NETDATA_ETW_PROVIDER_GUID, NULL, NULL, &regHandle) != ERROR_SUCCESS)
+ return false;
+
+ etw_set_source_meta(&nd_log.sources[NDLS_DAEMON], CHANNEL_DAEMON, &ED_DAEMON_INFO_MESSAGE_ONLY);
+ etw_set_source_meta(&nd_log.sources[NDLS_COLLECTORS], CHANNEL_COLLECTORS, &ED_COLLECTORS_INFO_MESSAGE_ONLY);
+ etw_set_source_meta(&nd_log.sources[NDLS_ACCESS], CHANNEL_ACCESS, &ED_ACCESS_INFO_MESSAGE_ONLY);
+ etw_set_source_meta(&nd_log.sources[NDLS_HEALTH], CHANNEL_HEALTH, &ED_HEALTH_INFO_MESSAGE_ONLY);
+ etw_set_source_meta(&nd_log.sources[NDLS_ACLK], CHANNEL_ACLK, &ED_ACLK_INFO_MESSAGE_ONLY);
+ etw_set_source_meta(&nd_log.sources[NDLS_UNSET], CHANNEL_DAEMON, &ED_DAEMON_INFO_MESSAGE_ONLY);
+ etw_set_source_meta(&nd_log.sources[NDLS_DEBUG], CHANNEL_DAEMON, &ED_DAEMON_INFO_MESSAGE_ONLY);
+
+ return true;
+}
+#endif
+
+bool nd_log_init_windows(void) {
+ if(nd_log.eventlog.initialized)
+ return true;
+
+ // validate we have the right keys
+ if(
+ !check_event_id(NDLS_COLLECTORS, NDLP_INFO, MSGID_MESSAGE_ONLY, MC_COLLECTORS_INFO_MESSAGE_ONLY) ||
+ !check_event_id(NDLS_DAEMON, NDLP_ERR, MSGID_MESSAGE_ONLY, MC_DAEMON_ERR_MESSAGE_ONLY) ||
+ !check_event_id(NDLS_ACCESS, NDLP_WARNING, MSGID_ACCESS_USER, MC_ACCESS_WARN_ACCESS_USER) ||
+ !check_event_id(NDLS_HEALTH, NDLP_CRIT, MSGID_ALERT_TRANSITION, MC_HEALTH_CRIT_ALERT_TRANSITION) ||
+ !check_event_id(NDLS_DEBUG, NDLP_ALERT, MSGID_ACCESS_FORWARDER_USER, MC_DEBUG_ALERT_ACCESS_FORWARDER_USER))
+ return false;
+
+#if defined(HAVE_ETW)
+ if(nd_log.eventlog.etw && !etw_register_provider())
+ return false;
+#endif
+
+// if(!nd_log.eventlog.etw && !wel_add_to_registry(NETDATA_WEL_CHANNEL_NAME_W, NULL, 50 * 1024 * 1024))
+// return false;
+
+ // Loop through each source and add it to the registry
+ for(size_t i = 0; i < _NDLS_MAX; i++) {
+ nd_log.sources[i].source = i;
+
+ const wchar_t *sub_channel = wel_provider_per_source[i];
+
+ if(!sub_channel)
+ // we will map these to NDLS_DAEMON
+ continue;
+
+ DWORD defaultMaxSize = 0;
+ switch (i) {
+ case NDLS_ACLK:
+ defaultMaxSize = 5 * 1024 * 1024;
+ break;
+
+ case NDLS_HEALTH:
+ defaultMaxSize = 35 * 1024 * 1024;
+ break;
+
+ default:
+ case NDLS_ACCESS:
+ case NDLS_COLLECTORS:
+ case NDLS_DAEMON:
+ defaultMaxSize = 20 * 1024 * 1024;
+ break;
+ }
+
+ if(!nd_log.eventlog.etw) {
+ if(!wel_add_to_registry(NETDATA_WEL_CHANNEL_NAME_W, sub_channel, defaultMaxSize))
+ return false;
+
+ // when not using a manifest, each source is a provider
+ nd_log.sources[i].hEventLog = RegisterEventSourceW(NULL, sub_channel);
+ if (!nd_log.sources[i].hEventLog)
+ return false;
+ }
+ }
+
+ if(!nd_log.eventlog.etw) {
+ // Map the unset ones to NDLS_DAEMON
+ for (size_t i = 0; i < _NDLS_MAX; i++) {
+ if (!nd_log.sources[i].hEventLog)
+ nd_log.sources[i].hEventLog = nd_log.sources[NDLS_DAEMON].hEventLog;
+ }
+ }
+
+ nd_log.eventlog.initialized = true;
+ return true;
+}
+
+bool nd_log_init_etw(void) {
+ nd_log.eventlog.etw = true;
+ return nd_log_init_windows();
+}
+
+bool nd_log_init_wel(void) {
+ nd_log.eventlog.etw = false;
+ return nd_log_init_windows();
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// we pass all our fields to the windows events logs
+// numbered the same way we have them in memory.
+//
+// to avoid runtime memory allocations, we use a static allocations with ready to use buffers
+// which are immediately available for logging.
+
+#define SMALL_WIDE_BUFFERS_SIZE 256
+#define MEDIUM_WIDE_BUFFERS_SIZE 2048
+#define BIG_WIDE_BUFFERS_SIZE 16384
+static wchar_t small_wide_buffers[_NDF_MAX][SMALL_WIDE_BUFFERS_SIZE];
+static wchar_t medium_wide_buffers[2][MEDIUM_WIDE_BUFFERS_SIZE];
+static wchar_t big_wide_buffers[2][BIG_WIDE_BUFFERS_SIZE];
+
+static struct {
+ size_t size;
+ wchar_t *buf;
+} fields_buffers[_NDF_MAX] = { 0 };
+
+#if defined(HAVE_ETW)
+static EVENT_DATA_DESCRIPTOR etw_eventData[_NDF_MAX - 1];
+#endif
+
+static LPCWSTR wel_messages[_NDF_MAX - 1];
+
+__attribute__((constructor)) void wevents_initialize_buffers(void) {
+ for(size_t i = 0; i < _NDF_MAX ;i++) {
+ fields_buffers[i].buf = small_wide_buffers[i];
+ fields_buffers[i].size = SMALL_WIDE_BUFFERS_SIZE;
+ }
+
+ fields_buffers[NDF_NIDL_INSTANCE].buf = medium_wide_buffers[0];
+ fields_buffers[NDF_NIDL_INSTANCE].size = MEDIUM_WIDE_BUFFERS_SIZE;
+
+ fields_buffers[NDF_REQUEST].buf = big_wide_buffers[0];
+ fields_buffers[NDF_REQUEST].size = BIG_WIDE_BUFFERS_SIZE;
+ fields_buffers[NDF_MESSAGE].buf = big_wide_buffers[1];
+ fields_buffers[NDF_MESSAGE].size = BIG_WIDE_BUFFERS_SIZE;
+
+ for(size_t i = 1; i < _NDF_MAX ;i++)
+ wel_messages[i - 1] = fields_buffers[i].buf;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#define is_field_set(fields, fields_max, field) ((field) < (fields_max) && (fields)[field].entry.set)
+
+static const char *get_field_value_unsafe(struct log_field *fields, ND_LOG_FIELD_ID i, size_t fields_max, BUFFER **tmp) {
+ if(!is_field_set(fields, fields_max, i) || !fields[i].eventlog)
+ return "";
+
+ static char number_str[MAX(MAX(UINT64_MAX_LENGTH, DOUBLE_MAX_LENGTH), UUID_STR_LEN)];
+
+ const char *s = NULL;
+ if (fields[i].annotator)
+ s = fields[i].annotator(&fields[i]);
+
+ else
+ switch (fields[i].entry.type) {
+ case NDFT_TXT:
+ s = fields[i].entry.txt;
+ break;
+ case NDFT_STR:
+ s = string2str(fields[i].entry.str);
+ break;
+ case NDFT_BFR:
+ s = buffer_tostring(fields[i].entry.bfr);
+ break;
+ case NDFT_U64:
+ print_uint64(number_str, fields[i].entry.u64);
+ s = number_str;
+ break;
+ case NDFT_I64:
+ print_int64(number_str, fields[i].entry.i64);
+ s = number_str;
+ break;
+ case NDFT_DBL:
+ print_netdata_double(number_str, fields[i].entry.dbl);
+ s = number_str;
+ break;
+ case NDFT_UUID:
+ if (!uuid_is_null(*fields[i].entry.uuid)) {
+ uuid_unparse_lower(*fields[i].entry.uuid, number_str);
+ s = number_str;
+ }
+ break;
+ case NDFT_CALLBACK:
+ if (!*tmp)
+ *tmp = buffer_create(1024, NULL);
+ else
+ buffer_flush(*tmp);
+
+ if (fields[i].entry.cb.formatter(*tmp, fields[i].entry.cb.formatter_data))
+ s = buffer_tostring(*tmp);
+ else
+ s = NULL;
+ break;
+
+ default:
+ s = "UNHANDLED";
+ break;
+ }
+
+ if(!s || !*s) return "";
+ return s;
+}
+static void etw_replace_percent_with_unicode(wchar_t *s, size_t size) {
+ size_t original_len = wcslen(s);
+
+ // Traverse the string, replacing '%' with the Unicode fullwidth percent sign
+ for (size_t i = 0; i < original_len && i < size - 1; i++) {
+ if (s[i] == L'%' && iswdigit(s[i + 1])) {
+ // s[i] = 0xFF05; // Replace '%' with fullwidth percent sign '%'
+ // s[i] = 0x29BC; // ⦼
+ s[i] = 0x2105; // ℅
+ }
+ }
+
+ // Ensure null termination if needed
+ s[size - 1] = L'\0';
+}
+
+static void wevt_generate_all_fields_unsafe(struct log_field *fields, size_t fields_max, BUFFER **tmp) {
+ for (size_t i = 0; i < fields_max; i++) {
+ fields_buffers[i].buf[0] = L'\0';
+
+ if (!fields[i].entry.set || !fields[i].eventlog)
+ continue;
+
+ const char *s = get_field_value_unsafe(fields, i, fields_max, tmp);
+ if (s && *s) {
+ utf8_to_utf16(fields_buffers[i].buf, (int) fields_buffers[i].size, s, -1);
+
+ if(nd_log.eventlog.etw)
+ // UNBELIEVABLE! they do recursive parameter expansion in ETW...
+ etw_replace_percent_with_unicode(fields_buffers[i].buf, fields_buffers[i].size);
+ }
+ }
+}
+
+static bool has_user_role_permissions(struct log_field *fields, size_t fields_max, BUFFER **tmp) {
+ const char *t;
+
+ t = get_field_value_unsafe(fields, NDF_USER_NAME, fields_max, tmp);
+ if (*t) return true;
+
+ t = get_field_value_unsafe(fields, NDF_USER_ROLE, fields_max, tmp);
+ if (*t && strcmp(t, "none") != 0) return true;
+
+ t = get_field_value_unsafe(fields, NDF_USER_ACCESS, fields_max, tmp);
+ if (*t && strcmp(t, "0x0") != 0) return true;
+
+ return false;
+}
+
+static bool nd_logger_windows(struct nd_log_source *source, struct log_field *fields, size_t fields_max) {
+ if (!nd_log.eventlog.initialized)
+ return false;
+
+ ND_LOG_FIELD_PRIORITY priority = NDLP_INFO;
+ if (fields[NDF_PRIORITY].entry.set)
+ priority = (ND_LOG_FIELD_PRIORITY) fields[NDF_PRIORITY].entry.u64;
+
+ DWORD wType = get_event_type_from_priority(priority);
+ (void) wType;
+
+ CLEAN_BUFFER *tmp = NULL;
+
+ static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
+ spinlock_lock(&spinlock);
+ wevt_generate_all_fields_unsafe(fields, fields_max, &tmp);
+
+ MESSAGE_ID messageID;
+ switch (source->source) {
+ default:
+ case NDLS_DEBUG:
+ case NDLS_DAEMON:
+ case NDLS_COLLECTORS:
+ messageID = MSGID_MESSAGE_ONLY;
+ break;
+
+ case NDLS_HEALTH:
+ messageID = MSGID_ALERT_TRANSITION;
+ break;
+
+ case NDLS_ACCESS:
+ if (is_field_set(fields, fields_max, NDF_MESSAGE)) {
+ messageID = MSGID_ACCESS_MESSAGE;
+
+ if (has_user_role_permissions(fields, fields_max, &tmp))
+ messageID = MSGID_ACCESS_MESSAGE_USER;
+ else if (*get_field_value_unsafe(fields, NDF_REQUEST, fields_max, &tmp))
+ messageID = MSGID_ACCESS_MESSAGE_REQUEST;
+ } else if (is_field_set(fields, fields_max, NDF_RESPONSE_CODE)) {
+ messageID = MSGID_ACCESS;
+
+ if (*get_field_value_unsafe(fields, NDF_SRC_FORWARDED_FOR, fields_max, &tmp))
+ messageID = MSGID_ACCESS_FORWARDER;
+
+ if (has_user_role_permissions(fields, fields_max, &tmp)) {
+ if (messageID == MSGID_ACCESS)
+ messageID = MSGID_ACCESS_USER;
+ else
+ messageID = MSGID_ACCESS_FORWARDER_USER;
+ }
+ } else
+ messageID = MSGID_REQUEST_ONLY;
+ break;
+
+ case NDLS_ACLK:
+ messageID = MSGID_MESSAGE_ONLY;
+ break;
+ }
+
+ if (messageID == MSGID_MESSAGE_ONLY && (
+ *get_field_value_unsafe(fields, NDF_ERRNO, fields_max, &tmp) ||
+ *get_field_value_unsafe(fields, NDF_WINERROR, fields_max, &tmp))) {
+ messageID = MSGID_MESSAGE_ERRNO;
+ }
+
+ DWORD eventID = construct_event_id(source->source, priority, messageID);
+
+ // wType
+ //
+ // without a manifest => this determines the Level of the event
+ // with a manifest => Level from the manifest is used (wType ignored)
+ // [however it is good to have, in case the manifest is not accessible somehow]
+ //
+
+ // wCategory
+ //
+ // without a manifest => numeric Task values appear
+ // with a manifest => Task from the manifest is used (wCategory ignored)
+
+ BOOL rc;
+#if defined(HAVE_ETW)
+ if (nd_log.eventlog.etw) {
+ // metadata based logging - ETW
+
+ for (size_t i = 1; i < _NDF_MAX; i++)
+ EventDataDescCreate(&etw_eventData[i - 1], fields_buffers[i].buf,
+ (wcslen(fields_buffers[i].buf) + 1) * sizeof(WCHAR));
+
+ EVENT_DESCRIPTOR EventDesc = {
+ .Id = eventID & EVENT_ID_CODE_MASK, // ETW needs the raw event id
+ .Version = 0,
+ .Channel = source->channelID,
+ .Level = get_level_from_priority(priority),
+ .Opcode = source->Opcode,
+ .Task = source->Task,
+ .Keyword = source->Keyword,
+ };
+
+ rc = ERROR_SUCCESS == EventWrite(regHandle, &EventDesc, _NDF_MAX - 1, etw_eventData);
+
+ }
+ else
+#endif
+ {
+ // eventID based logging - WEL
+ rc = ReportEventW(source->hEventLog, wType, 0, eventID, NULL, _NDF_MAX - 1, 0, wel_messages, NULL);
+ }
+
+ spinlock_unlock(&spinlock);
+
+ return rc == TRUE;
+}
+
+#if defined(HAVE_ETW)
+bool nd_logger_etw(struct nd_log_source *source, struct log_field *fields, size_t fields_max) {
+ return nd_logger_windows(source, fields, fields_max);
+}
+#endif
+
+#if defined(HAVE_WEL)
+bool nd_logger_wel(struct nd_log_source *source, struct log_field *fields, size_t fields_max) {
+ return nd_logger_windows(source, fields, fields_max);
+}
+#endif
+
+#endif
diff --git a/src/libnetdata/log/nd_log.c b/src/libnetdata/log/nd_log.c
new file mode 100644
index 000000000..a605fe460
--- /dev/null
+++ b/src/libnetdata/log/nd_log.c
@@ -0,0 +1,465 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// do not REMOVE this, it is used by systemd-journal includes to prevent saving the file, function, line of the
+// source code that makes the calls, allowing our loggers to log the lines of source code that actually log
+#define SD_JOURNAL_SUPPRESS_LOCATION
+
+#include "../libnetdata.h"
+#include "nd_log-internals.h"
+
+const char *program_name = "";
+uint64_t debug_flags = 0;
+int aclklog_enabled = 0;
+
+// --------------------------------------------------------------------------------------------------------------------
+
+void errno_clear(void) {
+ errno = 0;
+
+#if defined(OS_WINDOWS)
+ SetLastError(ERROR_SUCCESS);
+#endif
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// logger router
+
+static ND_LOG_METHOD nd_logger_select_output(ND_LOG_SOURCES source, FILE **fpp, SPINLOCK **spinlock) {
+ *spinlock = NULL;
+ ND_LOG_METHOD output = nd_log.sources[source].method;
+
+ switch(output) {
+ case NDLM_JOURNAL:
+ if(unlikely(!nd_log.journal_direct.initialized && !nd_log.journal.initialized)) {
+ output = NDLM_FILE;
+ *fpp = stderr;
+ *spinlock = &nd_log.std_error.spinlock;
+ }
+ else {
+ *fpp = NULL;
+ *spinlock = NULL;
+ }
+ break;
+
+#if defined(OS_WINDOWS) && (defined(HAVE_ETW) || defined(HAVE_WEL))
+#if defined(HAVE_ETW)
+ case NDLM_ETW:
+#endif
+#if defined(HAVE_WEL)
+ case NDLM_WEL:
+#endif
+ if(unlikely(!nd_log.eventlog.initialized)) {
+ output = NDLM_FILE;
+ *fpp = stderr;
+ *spinlock = &nd_log.std_error.spinlock;
+ }
+ else {
+ *fpp = NULL;
+ *spinlock = NULL;
+ }
+ break;
+#endif
+
+ case NDLM_SYSLOG:
+ if(unlikely(!nd_log.syslog.initialized)) {
+ output = NDLM_FILE;
+ *spinlock = &nd_log.std_error.spinlock;
+ *fpp = stderr;
+ }
+ else {
+ *spinlock = NULL;
+ *fpp = NULL;
+ }
+ break;
+
+ case NDLM_FILE:
+ if(!nd_log.sources[source].fp) {
+ *fpp = stderr;
+ *spinlock = &nd_log.std_error.spinlock;
+ }
+ else {
+ *fpp = nd_log.sources[source].fp;
+ *spinlock = &nd_log.sources[source].spinlock;
+ }
+ break;
+
+ case NDLM_STDOUT:
+ output = NDLM_FILE;
+ *fpp = stdout;
+ *spinlock = &nd_log.std_output.spinlock;
+ break;
+
+ default:
+ case NDLM_DEFAULT:
+ case NDLM_STDERR:
+ output = NDLM_FILE;
+ *fpp = stderr;
+ *spinlock = &nd_log.std_error.spinlock;
+ break;
+
+ case NDLM_DISABLED:
+ case NDLM_DEVNULL:
+ output = NDLM_DISABLED;
+ *fpp = NULL;
+ *spinlock = NULL;
+ break;
+ }
+
+ return output;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// high level logger
+
+static void nd_logger_log_fields(SPINLOCK *spinlock, FILE *fp, bool limit, ND_LOG_FIELD_PRIORITY priority,
+ ND_LOG_METHOD output, struct nd_log_source *source,
+ struct log_field *fields, size_t fields_max) {
+ if(spinlock)
+ spinlock_lock(spinlock);
+
+ // check the limits
+ if(limit && nd_log_limit_reached(source))
+ goto cleanup;
+
+ if(output == NDLM_JOURNAL) {
+ if(!nd_logger_journal_direct(fields, fields_max) && !nd_logger_journal_libsystemd(fields, fields_max)) {
+ // we can't log to journal, let's log to stderr
+ if(spinlock)
+ spinlock_unlock(spinlock);
+
+ output = NDLM_FILE;
+ spinlock = &nd_log.std_error.spinlock;
+ fp = stderr;
+
+ if(spinlock)
+ spinlock_lock(spinlock);
+ }
+ }
+
+#if defined(OS_WINDOWS)
+#if defined(HAVE_ETW)
+ if(output == NDLM_ETW) {
+ if(!nd_logger_etw(source, fields, fields_max)) {
+ // we can't log to windows events, let's log to stderr
+ if(spinlock)
+ spinlock_unlock(spinlock);
+
+ output = NDLM_FILE;
+ spinlock = &nd_log.std_error.spinlock;
+ fp = stderr;
+
+ if(spinlock)
+ spinlock_lock(spinlock);
+ }
+ }
+#endif
+#if defined(HAVE_WEL)
+ if(output == NDLM_WEL) {
+ if(!nd_logger_wel(source, fields, fields_max)) {
+ // we can't log to windows events, let's log to stderr
+ if(spinlock)
+ spinlock_unlock(spinlock);
+
+ output = NDLM_FILE;
+ spinlock = &nd_log.std_error.spinlock;
+ fp = stderr;
+
+ if(spinlock)
+ spinlock_lock(spinlock);
+ }
+ }
+#endif
+#endif
+
+ if(output == NDLM_SYSLOG)
+ nd_logger_syslog(priority, source->format, fields, fields_max);
+
+ if(output == NDLM_FILE)
+ nd_logger_file(fp, source->format, fields, fields_max);
+
+
+cleanup:
+ if(spinlock)
+ spinlock_unlock(spinlock);
+}
+
+static void nd_logger_unset_all_thread_fields(void) {
+ size_t fields_max = THREAD_FIELDS_MAX;
+ for(size_t i = 0; i < fields_max ; i++)
+ thread_log_fields[i].entry.set = false;
+}
+
+static void nd_logger_merge_log_stack_to_thread_fields(void) {
+ for(size_t c = 0; c < thread_log_stack_next ;c++) {
+ struct log_stack_entry *lgs = thread_log_stack_base[c];
+
+ for(size_t i = 0; lgs[i].id != NDF_STOP ; i++) {
+ if(lgs[i].id >= _NDF_MAX || !lgs[i].set)
+ continue;
+
+ struct log_stack_entry *e = &lgs[i];
+ ND_LOG_STACK_FIELD_TYPE type = lgs[i].type;
+
+ // do not add empty / unset fields
+ if((type == NDFT_TXT && (!e->txt || !*e->txt)) ||
+ (type == NDFT_BFR && (!e->bfr || !buffer_strlen(e->bfr))) ||
+ (type == NDFT_STR && !e->str) ||
+ (type == NDFT_UUID && (!e->uuid || uuid_is_null(*e->uuid))) ||
+ (type == NDFT_CALLBACK && !e->cb.formatter) ||
+ type == NDFT_UNSET)
+ continue;
+
+ thread_log_fields[lgs[i].id].entry = *e;
+ }
+ }
+}
+
+static void nd_logger(const char *file, const char *function, const unsigned long line,
+ ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, bool limit,
+ int saved_errno, size_t saved_winerror __maybe_unused, const char *fmt, va_list ap) {
+
+ SPINLOCK *spinlock;
+ FILE *fp;
+ ND_LOG_METHOD output = nd_logger_select_output(source, &fp, &spinlock);
+ if(!IS_FINAL_LOG_METHOD(output))
+ return;
+
+ // mark all fields as unset
+ nd_logger_unset_all_thread_fields();
+
+ // flatten the log stack into the fields
+ nd_logger_merge_log_stack_to_thread_fields();
+
+ // set the common fields that are automatically set by the logging subsystem
+
+ if(likely(!thread_log_fields[NDF_INVOCATION_ID].entry.set))
+ thread_log_fields[NDF_INVOCATION_ID].entry = ND_LOG_FIELD_UUID(NDF_INVOCATION_ID, &nd_log.invocation_id);
+
+ if(likely(!thread_log_fields[NDF_LOG_SOURCE].entry.set))
+ thread_log_fields[NDF_LOG_SOURCE].entry = ND_LOG_FIELD_TXT(NDF_LOG_SOURCE, nd_log_id2source(source));
+ else {
+ ND_LOG_SOURCES src = source;
+
+ if(thread_log_fields[NDF_LOG_SOURCE].entry.type == NDFT_TXT)
+ src = nd_log_source2id(thread_log_fields[NDF_LOG_SOURCE].entry.txt, source);
+ else if(thread_log_fields[NDF_LOG_SOURCE].entry.type == NDFT_U64)
+ src = thread_log_fields[NDF_LOG_SOURCE].entry.u64;
+
+ if(src != source && src < _NDLS_MAX) {
+ source = src;
+ output = nd_logger_select_output(source, &fp, &spinlock);
+ if(output != NDLM_FILE && output != NDLM_JOURNAL && output != NDLM_SYSLOG)
+ return;
+ }
+ }
+
+ if(likely(!thread_log_fields[NDF_SYSLOG_IDENTIFIER].entry.set))
+ thread_log_fields[NDF_SYSLOG_IDENTIFIER].entry = ND_LOG_FIELD_TXT(NDF_SYSLOG_IDENTIFIER, program_name);
+
+ if(likely(!thread_log_fields[NDF_LINE].entry.set)) {
+ thread_log_fields[NDF_LINE].entry = ND_LOG_FIELD_U64(NDF_LINE, line);
+ thread_log_fields[NDF_FILE].entry = ND_LOG_FIELD_TXT(NDF_FILE, file);
+ thread_log_fields[NDF_FUNC].entry = ND_LOG_FIELD_TXT(NDF_FUNC, function);
+ }
+
+ if(likely(!thread_log_fields[NDF_PRIORITY].entry.set)) {
+ thread_log_fields[NDF_PRIORITY].entry = ND_LOG_FIELD_U64(NDF_PRIORITY, priority);
+ }
+
+ if(likely(!thread_log_fields[NDF_TID].entry.set))
+ thread_log_fields[NDF_TID].entry = ND_LOG_FIELD_U64(NDF_TID, gettid_cached());
+
+ if(likely(!thread_log_fields[NDF_THREAD_TAG].entry.set)) {
+ const char *thread_tag = nd_thread_tag();
+ thread_log_fields[NDF_THREAD_TAG].entry = ND_LOG_FIELD_TXT(NDF_THREAD_TAG, thread_tag);
+
+ // TODO: fix the ND_MODULE in logging by setting proper module name in threads
+// if(!thread_log_fields[NDF_MODULE].entry.set)
+// thread_log_fields[NDF_MODULE].entry = ND_LOG_FIELD_CB(NDF_MODULE, thread_tag_to_module, (void *)thread_tag);
+ }
+
+ if(likely(!thread_log_fields[NDF_TIMESTAMP_REALTIME_USEC].entry.set))
+ thread_log_fields[NDF_TIMESTAMP_REALTIME_USEC].entry = ND_LOG_FIELD_U64(NDF_TIMESTAMP_REALTIME_USEC, now_realtime_usec());
+
+ if(saved_errno != 0 && !thread_log_fields[NDF_ERRNO].entry.set)
+ thread_log_fields[NDF_ERRNO].entry = ND_LOG_FIELD_I64(NDF_ERRNO, saved_errno);
+
+ if(saved_winerror != 0 && !thread_log_fields[NDF_WINERROR].entry.set)
+ thread_log_fields[NDF_WINERROR].entry = ND_LOG_FIELD_U64(NDF_WINERROR, saved_winerror);
+
+ CLEAN_BUFFER *wb = NULL;
+ if(fmt && !thread_log_fields[NDF_MESSAGE].entry.set) {
+ wb = buffer_create(1024, NULL);
+ buffer_vsprintf(wb, fmt, ap);
+ thread_log_fields[NDF_MESSAGE].entry = ND_LOG_FIELD_TXT(NDF_MESSAGE, buffer_tostring(wb));
+ }
+
+ nd_logger_log_fields(spinlock, fp, limit, priority, output, &nd_log.sources[source],
+ thread_log_fields, THREAD_FIELDS_MAX);
+
+ if(nd_log.sources[source].pending_msg) {
+ // log a pending message
+
+ nd_logger_unset_all_thread_fields();
+
+ thread_log_fields[NDF_TIMESTAMP_REALTIME_USEC].entry = (struct log_stack_entry){
+ .set = true,
+ .type = NDFT_U64,
+ .u64 = now_realtime_usec(),
+ };
+
+ thread_log_fields[NDF_LOG_SOURCE].entry = (struct log_stack_entry){
+ .set = true,
+ .type = NDFT_TXT,
+ .txt = nd_log_id2source(source),
+ };
+
+ thread_log_fields[NDF_SYSLOG_IDENTIFIER].entry = (struct log_stack_entry){
+ .set = true,
+ .type = NDFT_TXT,
+ .txt = program_name,
+ };
+
+ thread_log_fields[NDF_MESSAGE].entry = (struct log_stack_entry){
+ .set = true,
+ .type = NDFT_TXT,
+ .txt = nd_log.sources[source].pending_msg,
+ };
+
+ nd_logger_log_fields(spinlock, fp, false, priority, output,
+ &nd_log.sources[source],
+ thread_log_fields, THREAD_FIELDS_MAX);
+
+ freez((void *)nd_log.sources[source].pending_msg);
+ nd_log.sources[source].pending_msg = NULL;
+ }
+
+ errno_clear();
+}
+
+static ND_LOG_SOURCES nd_log_validate_source(ND_LOG_SOURCES source) {
+ if(source >= _NDLS_MAX)
+ source = NDLS_DAEMON;
+
+ if(nd_log.overwrite_process_source)
+ source = nd_log.overwrite_process_source;
+
+ return source;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// public API for loggers
+
+void netdata_logger(ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, const char *file, const char *function, unsigned long line, const char *fmt, ... )
+{
+ int saved_errno = errno;
+
+ size_t saved_winerror = 0;
+#if defined(OS_WINDOWS)
+ saved_winerror = GetLastError();
+#endif
+
+ source = nd_log_validate_source(source);
+
+ if (source != NDLS_DEBUG && priority > nd_log.sources[source].min_priority)
+ return;
+
+ va_list args;
+ va_start(args, fmt);
+ nd_logger(file, function, line, source, priority,
+ source == NDLS_DAEMON || source == NDLS_COLLECTORS,
+ saved_errno, saved_winerror, fmt, args);
+ va_end(args);
+}
+
+void netdata_logger_with_limit(ERROR_LIMIT *erl, ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, const char *fmt, ... ) {
+ int saved_errno = errno;
+
+ size_t saved_winerror = 0;
+#if defined(OS_WINDOWS)
+ saved_winerror = GetLastError();
+#endif
+
+ source = nd_log_validate_source(source);
+
+ if (source != NDLS_DEBUG && priority > nd_log.sources[source].min_priority)
+ return;
+
+ if(erl->sleep_ut)
+ sleep_usec(erl->sleep_ut);
+
+ spinlock_lock(&erl->spinlock);
+
+ erl->count++;
+ time_t now = now_boottime_sec();
+ if(now - erl->last_logged < erl->log_every) {
+ spinlock_unlock(&erl->spinlock);
+ return;
+ }
+
+ spinlock_unlock(&erl->spinlock);
+
+ va_list args;
+ va_start(args, fmt);
+ nd_logger(file, function, line, source, priority,
+ source == NDLS_DAEMON || source == NDLS_COLLECTORS,
+ saved_errno, saved_winerror, fmt, args);
+ va_end(args);
+ erl->last_logged = now;
+ erl->count = 0;
+}
+
+void netdata_logger_fatal( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) {
+ int saved_errno = errno;
+
+ size_t saved_winerror = 0;
+#if defined(OS_WINDOWS)
+ saved_winerror = GetLastError();
+#endif
+
+ ND_LOG_SOURCES source = NDLS_DAEMON;
+ source = nd_log_validate_source(source);
+
+ va_list args;
+ va_start(args, fmt);
+ nd_logger(file, function, line, source, NDLP_ALERT, true, saved_errno, saved_winerror, fmt, args);
+ va_end(args);
+
+ char date[LOG_DATE_LENGTH];
+ log_date(date, LOG_DATE_LENGTH, now_realtime_sec());
+
+ char action_data[70+1];
+ snprintfz(action_data, 70, "%04lu@%-10.10s:%-15.15s/%d", line, file, function, saved_errno);
+
+ const char *thread_tag = nd_thread_tag();
+ const char *tag_to_send = thread_tag;
+
+ // anonymize thread names
+ if(strncmp(thread_tag, THREAD_TAG_STREAM_RECEIVER, strlen(THREAD_TAG_STREAM_RECEIVER)) == 0)
+ tag_to_send = THREAD_TAG_STREAM_RECEIVER;
+ if(strncmp(thread_tag, THREAD_TAG_STREAM_SENDER, strlen(THREAD_TAG_STREAM_SENDER)) == 0)
+ tag_to_send = THREAD_TAG_STREAM_SENDER;
+
+ char action_result[60+1];
+ snprintfz(action_result, 60, "%s:%s", program_name, tag_to_send);
+
+#if !defined(ENABLE_SENTRY) && defined(HAVE_BACKTRACE)
+ int fd = nd_log.sources[NDLS_DAEMON].fd;
+ if(fd == -1)
+ fd = STDERR_FILENO;
+
+ int nptrs;
+ void *buffer[10000];
+
+ nptrs = backtrace(buffer, sizeof(buffer));
+ if(nptrs)
+ backtrace_symbols_fd(buffer, nptrs, fd);
+#endif
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ abort();
+#endif
+
+ netdata_cleanup_and_exit(1, "FATAL", action_result, action_data);
+}
+
diff --git a/src/libnetdata/log/log.h b/src/libnetdata/log/nd_log.h
index 015c02eb6..1fefbe328 100644
--- a/src/libnetdata/log/log.h
+++ b/src/libnetdata/log/nd_log.h
@@ -1,150 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#ifndef NETDATA_LOG_H
-#define NETDATA_LOG_H 1
+#ifndef NETDATA_ND_LOG_H
+#define NETDATA_ND_LOG_H 1
# ifdef __cplusplus
extern "C" {
# endif
#include "../libnetdata.h"
+#include "nd_log-common.h"
#define ND_LOG_DEFAULT_THROTTLE_LOGS 1000
#define ND_LOG_DEFAULT_THROTTLE_PERIOD 60
-typedef enum __attribute__((__packed__)) {
- NDLS_UNSET = 0, // internal use only
- NDLS_ACCESS, // access.log
- NDLS_ACLK, // aclk.log
- NDLS_COLLECTORS, // collectors.log
- NDLS_DAEMON, // error.log
- NDLS_HEALTH, // health.log
- NDLS_DEBUG, // debug.log
-
- // terminator
- _NDLS_MAX,
-} ND_LOG_SOURCES;
-
-typedef enum __attribute__((__packed__)) {
- NDLP_EMERG = LOG_EMERG,
- NDLP_ALERT = LOG_ALERT,
- NDLP_CRIT = LOG_CRIT,
- NDLP_ERR = LOG_ERR,
- NDLP_WARNING = LOG_WARNING,
- NDLP_NOTICE = LOG_NOTICE,
- NDLP_INFO = LOG_INFO,
- NDLP_DEBUG = LOG_DEBUG,
-} ND_LOG_FIELD_PRIORITY;
-
-typedef enum __attribute__((__packed__)) {
- // KEEP THESE IN THE SAME ORDER AS in thread_log_fields (log.c)
- // so that it easy to audit for missing fields
-
- NDF_STOP = 0,
- NDF_TIMESTAMP_REALTIME_USEC, // the timestamp of the log message - added automatically
- NDF_SYSLOG_IDENTIFIER, // the syslog identifier of the application - added automatically
- NDF_LOG_SOURCE, // DAEMON, COLLECTORS, HEALTH, ACCESS, ACLK - set at the log call
- NDF_PRIORITY, // the syslog priority (severity) - set at the log call
- NDF_ERRNO, // the ERRNO at the time of the log call - added automatically
-#if defined(OS_WINDOWS)
- NDF_WINERROR, // Windows GetLastError()
-#endif
- NDF_INVOCATION_ID, // the INVOCATION_ID of Netdata - added automatically
- NDF_LINE, // the source code file line number - added automatically
- NDF_FILE, // the source code filename - added automatically
- NDF_FUNC, // the source code function - added automatically
- NDF_TID, // the thread ID of the thread logging - added automatically
- NDF_THREAD_TAG, // the thread tag of the thread logging - added automatically
- NDF_MESSAGE_ID, // for specific events
- NDF_MODULE, // for internal plugin module, all other get the NDF_THREAD_TAG
-
- NDF_NIDL_NODE, // the node / rrdhost currently being worked
- NDF_NIDL_INSTANCE, // the instance / rrdset currently being worked
- NDF_NIDL_CONTEXT, // the context of the instance currently being worked
- NDF_NIDL_DIMENSION, // the dimension / rrddim currently being worked
-
- // web server, aclk and stream receiver
- NDF_SRC_TRANSPORT, // the transport we received the request, one of: http, https, pluginsd
-
- // Netdata Cloud Related
- NDF_ACCOUNT_ID,
- NDF_USER_NAME,
- NDF_USER_ROLE,
- NDF_USER_ACCESS,
-
- // web server and stream receiver
- NDF_SRC_IP, // the streaming / web server source IP
- NDF_SRC_PORT, // the streaming / web server source Port
- NDF_SRC_FORWARDED_HOST,
- NDF_SRC_FORWARDED_FOR,
- NDF_SRC_CAPABILITIES, // the stream receiver capabilities
-
- // stream sender (established links)
- NDF_DST_TRANSPORT, // the transport we send the request, one of: http, https
- NDF_DST_IP, // the destination streaming IP
- NDF_DST_PORT, // the destination streaming Port
- NDF_DST_CAPABILITIES, // the destination streaming capabilities
-
- // web server, aclk and stream receiver
- NDF_REQUEST_METHOD, // for http like requests, the http request method
- NDF_RESPONSE_CODE, // for http like requests, the http response code, otherwise a status string
-
- // web server (all), aclk (queries)
- NDF_CONNECTION_ID, // the web server connection ID
- NDF_TRANSACTION_ID, // the web server and API transaction ID
- NDF_RESPONSE_SENT_BYTES, // for http like requests, the response bytes
- NDF_RESPONSE_SIZE_BYTES, // for http like requests, the uncompressed response size
- NDF_RESPONSE_PREPARATION_TIME_USEC, // for http like requests, the preparation time
- NDF_RESPONSE_SENT_TIME_USEC, // for http like requests, the time to send the response back
- NDF_RESPONSE_TOTAL_TIME_USEC, // for http like requests, the total time to complete the response
-
- // health alerts
- NDF_ALERT_ID,
- NDF_ALERT_UNIQUE_ID,
- NDF_ALERT_EVENT_ID,
- NDF_ALERT_TRANSITION_ID,
- NDF_ALERT_CONFIG_HASH,
- NDF_ALERT_NAME,
- NDF_ALERT_CLASS,
- NDF_ALERT_COMPONENT,
- NDF_ALERT_TYPE,
- NDF_ALERT_EXEC,
- NDF_ALERT_RECIPIENT,
- NDF_ALERT_DURATION,
- NDF_ALERT_VALUE,
- NDF_ALERT_VALUE_OLD,
- NDF_ALERT_STATUS,
- NDF_ALERT_STATUS_OLD,
- NDF_ALERT_SOURCE,
- NDF_ALERT_UNITS,
- NDF_ALERT_SUMMARY,
- NDF_ALERT_INFO,
- NDF_ALERT_NOTIFICATION_REALTIME_USEC,
- // NDF_ALERT_FLAGS,
-
- // put new items here
- // leave the request URL and the message last
-
- NDF_REQUEST, // the request we are currently working on
- NDF_MESSAGE, // the log message, if any
-
- // terminator
- _NDF_MAX,
-} ND_LOG_FIELD_ID;
-
-typedef enum __attribute__((__packed__)) {
- NDFT_UNSET = 0,
- NDFT_TXT,
- NDFT_STR,
- NDFT_BFR,
- NDFT_U64,
- NDFT_I64,
- NDFT_DBL,
- NDFT_UUID,
- NDFT_CALLBACK,
-} ND_LOG_STACK_FIELD_TYPE;
-
void errno_clear(void);
+int nd_log_systemd_journal_fd(void);
void nd_log_set_user_settings(ND_LOG_SOURCES source, const char *setting);
void nd_log_set_facility(const char *facility);
void nd_log_set_priority_level(const char *setting);
@@ -154,9 +24,9 @@ void chown_open_file(int fd, uid_t uid, gid_t gid);
void nd_log_chown_log_files(uid_t uid, gid_t gid);
void nd_log_set_flood_protection(size_t logs, time_t period);
void nd_log_initialize_for_external_plugins(const char *name);
-void nd_log_reopen_log_files_for_spawn_server(void);
+void nd_log_reopen_log_files_for_spawn_server(const char *name);
bool nd_log_journal_socket_available(void);
-ND_LOG_FIELD_ID nd_log_field_id_by_name(const char *field, size_t len);
+ND_LOG_FIELD_ID nd_log_field_id_by_journal_name(const char *field, size_t len);
int nd_log_priority2id(const char *priority);
const char *nd_log_id2priority(ND_LOG_FIELD_PRIORITY priority);
const char *nd_log_method_for_external_plugins(const char *s);
@@ -238,12 +108,8 @@ void log_stack_push(struct log_stack_entry *lgs);
#define D_SYSTEM 0x8000000000000000
extern uint64_t debug_flags;
-
extern const char *program_name;
-
-#ifdef ENABLE_ACLK
extern int aclklog_enabled;
-#endif
#define LOG_DATE_LENGTH 26
void log_date(char *buffer, size_t len, time_t now);
@@ -310,4 +176,4 @@ void netdata_logger_fatal( const char *file, const char *function, unsigned long
}
# endif
-#endif /* NETDATA_LOG_H */
+#endif /* NETDATA_ND_LOG_H */
diff --git a/src/libnetdata/log/nd_log_limit.c b/src/libnetdata/log/nd_log_limit.c
new file mode 100644
index 000000000..272138196
--- /dev/null
+++ b/src/libnetdata/log/nd_log_limit.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "nd_log_limit.h"
+
+void nd_log_limits_reset(void) {
+ usec_t now_ut = now_monotonic_usec();
+
+ spinlock_lock(&nd_log.std_output.spinlock);
+ spinlock_lock(&nd_log.std_error.spinlock);
+
+ for(size_t i = 0; i < _NDLS_MAX ;i++) {
+ spinlock_lock(&nd_log.sources[i].spinlock);
+ nd_log.sources[i].limits.prevented = 0;
+ nd_log.sources[i].limits.counter = 0;
+ nd_log.sources[i].limits.started_monotonic_ut = now_ut;
+ nd_log.sources[i].limits.logs_per_period = nd_log.sources[i].limits.logs_per_period_backup;
+ spinlock_unlock(&nd_log.sources[i].spinlock);
+ }
+
+ spinlock_unlock(&nd_log.std_output.spinlock);
+ spinlock_unlock(&nd_log.std_error.spinlock);
+}
+
+void nd_log_limits_unlimited(void) {
+ nd_log_limits_reset();
+ for(size_t i = 0; i < _NDLS_MAX ;i++) {
+ nd_log.sources[i].limits.logs_per_period = 0;
+ }
+}
+
+bool nd_log_limit_reached(struct nd_log_source *source) {
+ if(source->limits.throttle_period == 0 || source->limits.logs_per_period == 0)
+ return false;
+
+ usec_t now_ut = now_monotonic_usec();
+ if(!source->limits.started_monotonic_ut)
+ source->limits.started_monotonic_ut = now_ut;
+
+ source->limits.counter++;
+
+ if(now_ut - source->limits.started_monotonic_ut > (usec_t)source->limits.throttle_period) {
+ if(source->limits.prevented) {
+ BUFFER *wb = buffer_create(1024, NULL);
+ buffer_sprintf(wb,
+ "LOG FLOOD PROTECTION: resuming logging "
+ "(prevented %"PRIu32" logs in the last %"PRIu32" seconds).",
+ source->limits.prevented,
+ source->limits.throttle_period);
+
+ if(source->pending_msg)
+ freez((void *)source->pending_msg);
+
+ source->pending_msg = strdupz(buffer_tostring(wb));
+
+ buffer_free(wb);
+ }
+
+ // restart the period accounting
+ source->limits.started_monotonic_ut = now_ut;
+ source->limits.counter = 1;
+ source->limits.prevented = 0;
+
+ // log this error
+ return false;
+ }
+
+ if(source->limits.counter > source->limits.logs_per_period) {
+ if(!source->limits.prevented) {
+ BUFFER *wb = buffer_create(1024, NULL);
+ buffer_sprintf(wb,
+ "LOG FLOOD PROTECTION: too many logs (%"PRIu32" logs in %"PRId64" seconds, threshold is set to %"PRIu32" logs "
+ "in %"PRIu32" seconds). Preventing more logs from process '%s' for %"PRId64" seconds.",
+ source->limits.counter,
+ (int64_t)((now_ut - source->limits.started_monotonic_ut) / USEC_PER_SEC),
+ source->limits.logs_per_period,
+ source->limits.throttle_period,
+ program_name,
+ (int64_t)(((source->limits.started_monotonic_ut + (source->limits.throttle_period * USEC_PER_SEC) - now_ut)) / USEC_PER_SEC)
+ );
+
+ if(source->pending_msg)
+ freez((void *)source->pending_msg);
+
+ source->pending_msg = strdupz(buffer_tostring(wb));
+
+ buffer_free(wb);
+ }
+
+ source->limits.prevented++;
+
+ // prevent logging this error
+#ifdef NETDATA_INTERNAL_CHECKS
+ return false;
+#else
+ return true;
+#endif
+ }
+
+ return false;
+}
diff --git a/src/libnetdata/log/nd_log_limit.h b/src/libnetdata/log/nd_log_limit.h
new file mode 100644
index 000000000..5486abde9
--- /dev/null
+++ b/src/libnetdata/log/nd_log_limit.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_ND_LOG_LIMIT_H
+#define NETDATA_ND_LOG_LIMIT_H
+
+#include "../libnetdata.h"
+
+struct nd_log_source;
+bool nd_log_limit_reached(struct nd_log_source *source);
+
+struct nd_log_limit {
+ usec_t started_monotonic_ut;
+ uint32_t counter;
+ uint32_t prevented;
+
+ uint32_t throttle_period;
+ uint32_t logs_per_period;
+ uint32_t logs_per_period_backup;
+};
+
+#define ND_LOG_LIMITS_DEFAULT (struct nd_log_limit){ .logs_per_period = ND_LOG_DEFAULT_THROTTLE_LOGS, .logs_per_period_backup = ND_LOG_DEFAULT_THROTTLE_LOGS, .throttle_period = ND_LOG_DEFAULT_THROTTLE_PERIOD, }
+#define ND_LOG_LIMITS_UNLIMITED (struct nd_log_limit){ .logs_per_period = 0, .logs_per_period_backup = 0, .throttle_period = 0, }
+
+#include "nd_log-internals.h"
+
+#endif //NETDATA_ND_LOG_LIMIT_H
diff --git a/src/libnetdata/log/nd_wevents_manifest.xml b/src/libnetdata/log/nd_wevents_manifest.xml
new file mode 100644
index 000000000..9e326c1cb
--- /dev/null
+++ b/src/libnetdata/log/nd_wevents_manifest.xml
@@ -0,0 +1,295 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<instrumentationManifest
+ xmlns="http://schemas.microsoft.com/win/2004/08/events"
+ xmlns:win="http://manifests.microsoft.com/win/2004/08/windows/events"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <instrumentation>
+ <events>
+
+ <provider name="Netdata"
+ guid="{96c5ca72-9bd8-4634-81e5-000014e7da7a}"
+ symbol="ND_PROVIDER_NAME"
+ messageFileName="%SystemRoot%\System32\nd_wevents.dll"
+ resourceFileName="%SystemRoot%\System32\nd_wevents.dll"
+ parameterFileName="%SystemRoot%\System32\nd_wevents.dll"
+ message="$(string.ND_PROVIDER_NAME)">
+
+ <!-- Define the channels -->
+ <channels>
+ <channel name="Netdata/Daemon"
+ symbol="ND_CHANNEL_DAEMON"
+ type="Operational"/>
+
+ <channel name="Netdata/Collectors"
+ symbol="ND_CHANNEL_COLLECTORS"
+ type="Operational"/>
+
+ <channel name="Netdata/Access"
+ symbol="ND_CHANNEL_ACCESS"
+ type="Operational"/>
+
+ <channel symbol="ND_CHANNEL_HEALTH"
+ name="Netdata/Alerts"
+ type="Operational"/>
+
+ <channel name="Netdata/ACLK"
+ symbol="ND_CHANNEL_ACLK"
+ type="Operational"/>
+ </channels>
+
+ <levels>
+ </levels>
+
+ <opcodes>
+ </opcodes>
+
+ <tasks>
+ <task name="Daemon" value="1" eventGUID="{00000000-0000-0000-0000-000000000000}" message="$(string.Task.Daemon)"/>
+ <task name="Collector" value="2" eventGUID="{00000000-0000-0000-0000-000000000000}" message="$(string.Task.Collector)"/>
+ <task name="Access" value="3" eventGUID="{00000000-0000-0000-0000-000000000000}" message="$(string.Task.Access)"/>
+ <task name="Health" value="4" eventGUID="{00000000-0000-0000-0000-000000000000}" message="$(string.Task.Health)"/>
+ <task name="Aclk" value="5" eventGUID="{00000000-0000-0000-0000-000000000000}" message="$(string.Task.Aclk)"/>
+ </tasks>
+
+ <templates>
+ <template tid="NetdataLogTemplate">
+ <!-- 0 (NDF_STOP) should not be here %1 is Timestamp, %64 is the Message -->
+ <data name="Timestamp" inType="win:UnicodeString"/> <!-- 1 (NDF_TIMESTAMP_REALTIME_USEC) -->
+ <data name="Program" inType="win:UnicodeString"/> <!-- 2 (NDF_SYSLOG_IDENTIFIER) -->
+ <data name="NetdataLogSource" inType="win:UnicodeString"/> <!-- 3 (NDF_LOG_SOURCE) -->
+ <data name="Level" inType="win:UnicodeString"/> <!-- 4 (NDF_PRIORITY) -->
+ <data name="UnixErrno" inType="win:UnicodeString"/> <!-- 5 (NDF_ERRNO) -->
+ <data name="WindowsLastError" inType="win:UnicodeString"/> <!-- 6 (NDF_WINERROR) -->
+ <data name="InvocationID" inType="win:UnicodeString"/> <!-- 7 (NDF_INVOCATION_ID) -->
+ <data name="CodeLine" inType="win:UInt32"/> <!-- 8 (NDF_LINE) -->
+ <data name="CodeFile" inType="win:UnicodeString"/> <!-- 9 (NDF_FILE) -->
+ <data name="CodeFunction" inType="win:UnicodeString"/> <!-- 10 (NDF_FUNC) -->
+ <data name="ThreadID" inType="win:UInt32"/> <!-- 11 (NDF_TID) -->
+ <data name="ThreadName" inType="win:UnicodeString"/> <!-- 12 (NDF_THREAD_TAG) -->
+ <data name="MessageID" inType="win:UnicodeString"/> <!-- 13 (NDF_MESSAGE_ID) -->
+ <data name="Module" inType="win:UnicodeString"/> <!-- 14 (NDF_MODULE) -->
+ <data name="Node" inType="win:UnicodeString"/> <!-- 15 (NDF_NIDL_NODE) -->
+ <data name="Instance" inType="win:UnicodeString"/> <!-- 16 (NDF_NIDL_INSTANCE) -->
+ <data name="Context" inType="win:UnicodeString"/> <!-- 17 (NDF_NIDL_CONTEXT) -->
+ <data name="Dimension" inType="win:UnicodeString"/> <!-- 18 (NDF_NIDL_DIMENSION) -->
+ <data name="SourceTransport" inType="win:UnicodeString"/> <!-- 19 (NDF_SRC_TRANSPORT) -->
+ <data name="AccountID" inType="win:UnicodeString"/> <!-- 20 (NDF_ACCOUNT_ID) -->
+ <data name="UserName" inType="win:UnicodeString"/> <!-- 21 (NDF_USER_NAME) -->
+ <data name="UserRole" inType="win:UnicodeString"/> <!-- 22 (NDF_USER_ROLE) -->
+ <data name="UserPermissions" inType="win:UnicodeString"/> <!-- 23 (NDF_USER_ACCESS) -->
+ <data name="SourceIP" inType="win:UnicodeString"/> <!-- 24 (NDF_SRC_IP) -->
+ <data name="SourceForwardedHost" inType="win:UnicodeString"/> <!-- 25 (NDF_SRC_PORT) -->
+ <data name="SourceForwardedFor" inType="win:UnicodeString"/> <!-- 26 (NDF_SRC_FORWARDED_HOST) -->
+ <data name="SourcePort" inType="win:UInt32"/> <!-- 27 (NDF_SRC_FORWARDED_FOR) -->
+ <data name="SourceCapabilities" inType="win:UnicodeString"/> <!-- 28 (NDF_SRC_CAPABILITIES) -->
+ <data name="DestinationTransport" inType="win:UnicodeString"/> <!-- 29 (NDF_DST_TRANSPORT) -->
+ <data name="DestinationIP" inType="win:UnicodeString"/> <!-- 30 (NDF_DST_IP) -->
+ <data name="DestinationPort" inType="win:UInt32"/> <!-- 31 (NDF_DST_PORT) -->
+ <data name="DestinationCapabilities" inType="win:UnicodeString"/> <!-- 32 (NDF_DST_CAPABILITIES) -->
+ <data name="RequestMethod" inType="win:UnicodeString"/> <!-- 33 (NDF_REQUEST_METHOD) -->
+ <data name="ResponseCode" inType="win:UInt32"/> <!-- 34 (NDF_RESPONSE_CODE) -->
+ <data name="ConnectionID" inType="win:UnicodeString"/> <!-- 35 (NDF_CONNECTION_ID) -->
+ <data name="TransactionID" inType="win:UnicodeString"/> <!-- 36 (NDF_TRANSACTION_ID) -->
+ <data name="ResponseSentBytes" inType="win:UInt64"/> <!-- 37 (NDF_RESPONSE_SENT_BYTES) -->
+ <data name="ResponseSizeBytes" inType="win:UInt64"/> <!-- 38 (NDF_RESPONSE_SIZE_BYTES) -->
+ <data name="ResponsePreparationTimeUsec" inType="win:UInt64"/> <!-- 39 (NDF_RESPONSE_PREPARATION_TIME_USEC) -->
+ <data name="ResponseSentTimeUsec" inType="win:UInt64"/> <!-- 40 (NDF_RESPONSE_SENT_TIME_USEC) -->
+ <data name="ResponseTotalTimeUsec" inType="win:UInt64"/> <!-- 41 (NDF_RESPONSE_TOTAL_TIME_USEC) -->
+ <data name="AlertID" inType="win:UnicodeString"/> <!-- 42 (NDF_ALERT_ID) -->
+ <data name="AlertUniqueID" inType="win:UnicodeString"/> <!-- 43 (NDF_ALERT_UNIQUE_ID) -->
+ <data name="AlertTransitionID" inType="win:UnicodeString"/> <!-- 44 (NDF_ALERT_TRANSITION_ID) -->
+ <data name="AlertEventID" inType="win:UnicodeString"/> <!-- 45 (NDF_ALERT_EVENT_ID) -->
+ <data name="AlertConfig" inType="win:UnicodeString"/> <!-- 46 (NDF_ALERT_CONFIG_HASH) -->
+ <data name="AlertName" inType="win:UnicodeString"/> <!-- 47 (NDF_ALERT_NAME) -->
+ <data name="AlertClass" inType="win:UnicodeString"/> <!-- 48 (NDF_ALERT_CLASS) -->
+ <data name="AlertComponent" inType="win:UnicodeString"/> <!-- 49 (NDF_ALERT_COMPONENT) -->
+ <data name="AlertType" inType="win:UnicodeString"/> <!-- 50 (NDF_ALERT_TYPE) -->
+ <data name="AlertExec" inType="win:UnicodeString"/> <!-- 51 (NDF_ALERT_EXEC) -->
+ <data name="AlertRecipient" inType="win:UnicodeString"/> <!-- 52 (NDF_ALERT_RECIPIENT) -->
+ <data name="AlertDuration" inType="win:UInt64"/> <!-- 53 (NDF_ALERT_DURATION) -->
+ <data name="AlertValue" inType="win:Double"/> <!-- 54 (NDF_ALERT_VALUE) -->
+ <data name="AlertOldValue" inType="win:Double"/> <!-- 55 (NDF_ALERT_VALUE_OLD) -->
+ <data name="AlertStatus" inType="win:UnicodeString"/> <!-- 56 (NDF_ALERT_STATUS) -->
+ <data name="AlertOldStatus" inType="win:UnicodeString"/> <!-- 57 (NDF_ALERT_STATUS_OLD) -->
+ <data name="Source" inType="win:UnicodeString"/> <!-- 58 (NDF_ALERT_SOURCE) -->
+ <data name="AlertUnits" inType="win:UnicodeString"/> <!-- 59 (NDF_ALERT_UNITS) -->
+ <data name="AlertSummary" inType="win:UnicodeString"/> <!-- 60 (NDF_ALERT_SUMMARY) -->
+ <data name="AlertInfo" inType="win:UnicodeString"/> <!-- 61 (NDF_ALERT_INFO) -->
+ <data name="AlertNotificationTime" inType="win:UInt64"/> <!-- 62 (NDF_ALERT_NOTIFICATION_REALTIME_USEC) -->
+ <data name="Request" inType="win:UnicodeString"/> <!-- 63 (NDF_REQUEST) -->
+ <data name="Message" inType="win:UnicodeString"/> <!-- 64 (NDF_MESSAGE) -->
+ </template>
+ </templates>
+
+ <events>
+ <!-- Daemon Events -->
+ <event symbol="ND_EVENT_DAEMON_INFO"
+ value="0x1000"
+ message="$(string.ND_GENERIC_LOG_MESSAGE)"
+ channel="Netdata/Daemon"
+ level="win:Informational"
+ task="Daemon"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <event symbol="ND_EVENT_DAEMON_WARNING"
+ value="0x1001"
+ message="$(string.ND_GENERIC_LOG_MESSAGE)"
+ channel="Netdata/Daemon"
+ level="win:Warning"
+ task="Daemon"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <event symbol="ND_EVENT_DAEMON_ERROR"
+ value="0x1002"
+ message="$(string.ND_GENERIC_LOG_MESSAGE)"
+ channel="Netdata/Daemon"
+ level="win:Error"
+ task="Daemon"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <!-- Collector Events -->
+ <event symbol="ND_EVENT_COLLECTOR_INFO"
+ value="0x2000"
+ message="$(string.ND_GENERIC_LOG_MESSAGE)"
+ channel="Netdata/Collectors"
+ level="win:Informational"
+ task="Collector"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <event symbol="ND_EVENT_COLLECTOR_WARNING"
+ value="0x2001"
+ message="$(string.ND_GENERIC_LOG_MESSAGE)"
+ channel="Netdata/Collectors"
+ level="win:Warning"
+ task="Collector"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <event symbol="ND_EVENT_COLLECTOR_ERROR"
+ value="0x2002"
+ message="$(string.ND_GENERIC_LOG_MESSAGE)"
+ channel="Netdata/Collectors"
+ level="win:Error"
+ task="Collector"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <!-- Access Events -->
+ <event symbol="ND_EVENT_ACCESS_INFO"
+ value="0x3000"
+ message="$(string.ND_ACCESS_EVENT_MESSAGE)"
+ channel="Netdata/Access"
+ level="win:Informational"
+ task="Access"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <event symbol="ND_EVENT_ACCESS_WARNING"
+ value="0x3001"
+ message="$(string.ND_ACCESS_EVENT_MESSAGE)"
+ channel="Netdata/Access"
+ level="win:Warning"
+ task="Access"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <event symbol="ND_EVENT_ACCESS_ERROR"
+ value="0x3002"
+ message="$(string.ND_ACCESS_EVENT_MESSAGE)"
+ channel="Netdata/Access"
+ level="win:Error"
+ task="Access"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <!-- Health Events -->
+ <event symbol="ND_EVENT_HEALTH_INFO"
+ value="0x4000"
+ message="$(string.ND_HEALTH_EVENT_MESSAGE)"
+ channel="Netdata/Alerts"
+ level="win:Informational"
+ task="Health"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <event symbol="ND_EVENT_HEALTH_WARNING"
+ value="0x4001"
+ message="$(string.ND_HEALTH_EVENT_MESSAGE)"
+ channel="Netdata/Alerts"
+ level="win:Warning"
+ task="Health"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <event symbol="ND_EVENT_HEALTH_ERROR"
+ value="0x4002"
+ message="$(string.ND_HEALTH_EVENT_MESSAGE)"
+ channel="Netdata/Alerts"
+ level="win:Error"
+ task="Health"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <!-- ACLK Events -->
+ <event symbol="ND_EVENT_ACLK_INFO"
+ value="0x5000"
+ message="$(string.ND_GENERIC_LOG_MESSAGE)"
+ channel="Netdata/ACLK"
+ level="win:Informational"
+ task="Aclk"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <event symbol="ND_EVENT_ACLK_WARNING"
+ value="0x5001"
+ message="$(string.ND_GENERIC_LOG_MESSAGE)"
+ channel="Netdata/ACLK"
+ level="win:Warning"
+ task="Aclk"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ <event symbol="ND_EVENT_ACLK_ERROR"
+ value="0x5002"
+ message="$(string.ND_GENERIC_LOG_MESSAGE)"
+ channel="Netdata/ACLK"
+ level="win:Error"
+ task="Aclk"
+ opcode="win:Info"
+ template="NetdataLogTemplate"/>
+
+ </events>
+ </provider>
+ </events>
+ </instrumentation>
+
+ <localization>
+ <resources culture="en-US">
+ <stringTable>
+ <string id="Task.Daemon" value="ND Daemon Log"/>
+ <string id="Task.Collector" value="ND Collector Log"/>
+ <string id="Task.Access" value="ND Access Log"/>
+ <string id="Task.Health" value="ND Health Log"/>
+ <string id="Task.Aclk" value="ND ACLK Log"/>
+
+ <string id="ND_PROVIDER_NAME" value="Netdata"/>
+ <string id="ND_GENERIC_LOG_MESSAGE" value="%64"/>
+ <string id="ND_ACCESS_EVENT_MESSAGE"
+ value="Transaction %36, method: %33, path: %63
+
+ Source IP : %24, Forwarded-For: %27
+ User : %21, role: %22, permissions: %23
+ Timings (usec): prep %39, sent %40, total %41
+ Response Size : sent %37, uncompressed %38
+ Response Code : %34
+"/>
+ <string id="ND_HEALTH_EVENT_MESSAGE"
+ value="Alert '%47' of instance '%16' on node '%15', transitioned from %57 to %56"/>
+ </stringTable>
+ </resources>
+ </localization>
+</instrumentationManifest>
diff --git a/src/libnetdata/log/systemd-cat-native.c b/src/libnetdata/log/systemd-cat-native.c
index 74d3728a3..2e4f55e97 100644
--- a/src/libnetdata/log/systemd-cat-native.c
+++ b/src/libnetdata/log/systemd-cat-native.c
@@ -11,7 +11,9 @@
#include <machine/endian.h>
#endif
-static inline void log_message_to_stderr(BUFFER *msg) {
+bool verbose = false;
+
+static inline void log_message_to_stderr(BUFFER *msg, const char *scope) {
CLEAN_BUFFER *tmp = buffer_create(0, NULL);
for(size_t i = 0; i < msg->len ;i++) {
@@ -24,13 +26,13 @@ static inline void log_message_to_stderr(BUFFER *msg) {
}
}
- fprintf(stderr, "SENDING: %s\n", buffer_tostring(tmp));
+ fprintf(stderr, "SENDING %s: %s\n", scope, buffer_tostring(tmp));
}
static inline buffered_reader_ret_t get_next_line(struct buffered_reader *reader, BUFFER *line, int timeout_ms) {
while(true) {
if(unlikely(!buffered_reader_next_line(reader, line))) {
- buffered_reader_ret_t ret = buffered_reader_read_timeout(reader, STDIN_FILENO, timeout_ms, false);
+ buffered_reader_ret_t ret = buffered_reader_read_timeout(reader, STDIN_FILENO, timeout_ms, verbose);
if(unlikely(ret != BUFFERED_READER_READ_OK))
return ret;
@@ -126,7 +128,7 @@ static inline void buffer_memcat_replacing_newlines(BUFFER *wb, const char *src,
// ----------------------------------------------------------------------------
// log to a systemd-journal-remote
-#ifdef HAVE_CURL
+#ifdef HAVE_LIBCURL
#include <curl/curl.h>
#ifndef HOST_NAME_MAX
@@ -203,8 +205,8 @@ static void journal_remote_complete_event(BUFFER *msg, usec_t *monotonic_ut) {
buffer_sprintf(msg,
""
- "__REALTIME_TIMESTAMP=%llu\n"
- "__MONOTONIC_TIMESTAMP=%llu\n"
+ "__REALTIME_TIMESTAMP=%"PRIu64"\n"
+ "__MONOTONIC_TIMESTAMP=%"PRIu64"\n"
"_MACHINE_ID=%s\n"
"_BOOT_ID=%s\n"
"_HOSTNAME=%s\n"
@@ -226,7 +228,8 @@ static void journal_remote_complete_event(BUFFER *msg, usec_t *monotonic_ut) {
static CURLcode journal_remote_send_buffer(CURL* curl, BUFFER *msg) {
- // log_message_to_stderr(msg);
+ if(verbose)
+ log_message_to_stderr(msg, "REMOTE");
struct upload_data upload = {0};
@@ -260,8 +263,8 @@ static log_to_journal_remote_ret_t log_input_to_journal_remote(const char *url,
global_boot_id[0] = '\0';
char buffer[1024];
- if(read_file(BOOT_ID_PATH, buffer, sizeof(buffer)) == 0) {
- uuid_t uuid;
+ if(read_txt_file(BOOT_ID_PATH, buffer, sizeof(buffer)) == 0) {
+ nd_uuid_t uuid;
if(uuid_parse_flexi(buffer, uuid) == 0)
uuid_unparse_lower_compact(uuid, global_boot_id);
else
@@ -270,13 +273,13 @@ static log_to_journal_remote_ret_t log_input_to_journal_remote(const char *url,
if(global_boot_id[0] == '\0') {
fprintf(stderr, "WARNING: cannot read '%s'. Will generate a random _BOOT_ID.\n", BOOT_ID_PATH);
- uuid_t uuid;
+ nd_uuid_t uuid;
uuid_generate_random(uuid);
uuid_unparse_lower_compact(uuid, global_boot_id);
}
- if(read_file(MACHINE_ID_PATH, buffer, sizeof(buffer)) == 0) {
- uuid_t uuid;
+ if(read_txt_file(MACHINE_ID_PATH, buffer, sizeof(buffer)) == 0) {
+ nd_uuid_t uuid;
if(uuid_parse_flexi(buffer, uuid) == 0)
uuid_unparse_lower_compact(uuid, global_machine_id);
else
@@ -285,13 +288,13 @@ static log_to_journal_remote_ret_t log_input_to_journal_remote(const char *url,
if(global_machine_id[0] == '\0') {
fprintf(stderr, "WARNING: cannot read '%s'. Will generate a random _MACHINE_ID.\n", MACHINE_ID_PATH);
- uuid_t uuid;
+ nd_uuid_t uuid;
uuid_generate_random(uuid);
uuid_unparse_lower_compact(uuid, global_boot_id);
}
if(global_stream_id[0] == '\0') {
- uuid_t uuid;
+ nd_uuid_t uuid;
uuid_generate_random(uuid);
uuid_unparse_lower_compact(uuid, global_stream_id);
}
@@ -456,10 +459,11 @@ static int help(void) {
"Usage:\n"
"\n"
" %s\n"
+ " [--verbose|-v]\n"
" [--newline=STRING]\n"
" [--log-as-netdata|-N]\n"
" [--namespace=NAMESPACE] [--socket=PATH]\n"
-#ifdef HAVE_CURL
+#ifdef HAVE_LIBCURL
" [--url=URL [--key=FILENAME] [--cert=FILENAME] [--trust=FILENAME|all]]\n"
#endif
"\n"
@@ -488,7 +492,7 @@ static int help(void) {
" the log destination. Only log fields defined by Netdata are accepted.\n"
" If the environment variables expected by Netdata are not found, it\n"
" falls back to stderr logging in logfmt format.\n"
-#ifdef HAVE_CURL
+#ifdef HAVE_LIBCURL
"\n"
" * Log to a systemd-journal-remote TCP socket, enabled with --url=URL\n"
"\n"
@@ -585,15 +589,16 @@ static int log_input_as_netdata(const char *newline, int timeout_ms) {
ND_LOG_STACK_PUSH(lgs);
lgs_reset(lgs);
+ ND_LOG_SOURCES source = NDLS_HEALTH;
+ ND_LOG_FIELD_PRIORITY priority = NDLP_INFO;
size_t fields_added = 0;
size_t messages_logged = 0;
- ND_LOG_FIELD_PRIORITY priority = NDLP_INFO;
while(get_next_line(&reader, line, timeout_ms) == BUFFERED_READER_READ_OK) {
if(!line->len) {
// an empty line - we are done for this message
- nd_log(NDLS_HEALTH, priority,
+ nd_log(source, priority,
"added %zu fields", // if the user supplied a MESSAGE, this will be ignored
fields_added);
@@ -606,7 +611,7 @@ static int log_input_as_netdata(const char *newline, int timeout_ms) {
if(equal) {
const char *field = line->buffer;
size_t field_len = equal - line->buffer;
- ND_LOG_FIELD_ID id = nd_log_field_id_by_name(field, field_len);
+ ND_LOG_FIELD_ID id = nd_log_field_id_by_journal_name(field, field_len);
if(id != NDF_STOP) {
const char *value = ++equal;
@@ -625,7 +630,7 @@ static int log_input_as_netdata(const char *newline, int timeout_ms) {
struct log_stack_entry backup = lgs[NDF_MESSAGE];
lgs[NDF_MESSAGE] = ND_LOG_FIELD_TXT(NDF_MESSAGE, NULL);
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ nd_log(source, NDLP_ERR,
"Field '%.*s' is not a Netdata field. Ignoring it.",
(int)field_len, field);
@@ -636,7 +641,7 @@ static int log_input_as_netdata(const char *newline, int timeout_ms) {
struct log_stack_entry backup = lgs[NDF_MESSAGE];
lgs[NDF_MESSAGE] = ND_LOG_FIELD_TXT(NDF_MESSAGE, NULL);
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ nd_log(source, NDLP_ERR,
"Line does not contain an = sign; ignoring it: %s",
line->buffer);
@@ -648,7 +653,7 @@ static int log_input_as_netdata(const char *newline, int timeout_ms) {
}
if(fields_added) {
- nd_log(NDLS_HEALTH, priority, "added %zu fields", fields_added);
+ nd_log(source, priority, "added %zu fields", fields_added);
messages_logged++;
}
@@ -659,7 +664,8 @@ static int log_input_as_netdata(const char *newline, int timeout_ms) {
// log to a local systemd-journald
static bool journal_local_send_buffer(int fd, BUFFER *msg) {
- // log_message_to_stderr(msg);
+ if(verbose)
+ log_message_to_stderr(msg, "LOCAL");
bool ret = journal_direct_send(fd, msg->buffer, msg->len);
if (!ret)
@@ -720,19 +726,25 @@ static int log_input_to_journal(const char *socket, const char *namespace, const
}
cleanup:
+ if(verbose) {
+ if(failed_messages)
+ fprintf(stderr, "%zu messages failed to be logged\n", failed_messages);
+ if(!messages_logged)
+ fprintf(stderr, "No messages were logged!\n");
+ }
+
return !failed_messages && messages_logged ? 0 : 1;
}
int main(int argc, char *argv[]) {
- clocks_init();
nd_log_initialize_for_external_plugins(argv[0]);
- int timeout_ms = -1; // wait forever
+ int timeout_ms = 0; // wait forever
bool log_as_netdata = false;
const char *newline = NULL;
const char *namespace = NULL;
const char *socket = getenv("NETDATA_SYSTEMD_JOURNAL_PATH");
-#ifdef HAVE_CURL
+#ifdef HAVE_LIBCURL
const char *url = NULL;
const char *key = NULL;
const char *cert = NULL;
@@ -746,6 +758,9 @@ int main(int argc, char *argv[]) {
if(strcmp(k, "--help") == 0 || strcmp(k, "-h") == 0)
return help();
+ else if(strcmp(k, "--verbose") == 0 || strcmp(k, "-v") == 0)
+ verbose = true;
+
else if(strcmp(k, "--log-as-netdata") == 0 || strcmp(k, "-N") == 0)
log_as_netdata = true;
@@ -758,7 +773,7 @@ int main(int argc, char *argv[]) {
else if(strncmp(k, "--newline=", 10) == 0)
newline = &k[10];
-#ifdef HAVE_CURL
+#ifdef HAVE_LIBCURL
else if (strncmp(k, "--url=", 6) == 0)
url = &k[6];
@@ -780,7 +795,7 @@ int main(int argc, char *argv[]) {
}
}
-#ifdef HAVE_CURL
+#ifdef HAVE_LIBCURL
if(log_as_netdata && url) {
fprintf(stderr, "Cannot log to a systemd-journal-remote URL as Netdata. "
"Please either give --url or --log-as-netdata, not both.\n");
@@ -804,7 +819,7 @@ int main(int argc, char *argv[]) {
if(log_as_netdata)
return log_input_as_netdata(newline, timeout_ms);
-#ifdef HAVE_CURL
+#ifdef HAVE_LIBCURL
if(url) {
if(url && namespace && *namespace)
snprintfz(global_namespace, sizeof(global_namespace), "_NAMESPACE=%s\n", namespace);
diff --git a/src/libnetdata/log/journal.c b/src/libnetdata/log/systemd-journal-helpers.c
index 2182212f6..24553364b 100644
--- a/src/libnetdata/log/journal.c
+++ b/src/libnetdata/log/systemd-journal-helpers.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "journal.h"
+#include "systemd-journal-helpers.h"
bool is_path_unix_socket(const char *path) {
// Check if the path is valid
diff --git a/src/libnetdata/log/journal.h b/src/libnetdata/log/systemd-journal-helpers.h
index df8ece18b..a85f8e85a 100644
--- a/src/libnetdata/log/journal.h
+++ b/src/libnetdata/log/systemd-journal-helpers.h
@@ -2,8 +2,8 @@
#include "../libnetdata.h"
-#ifndef NETDATA_LOG_JOURNAL_H
-#define NETDATA_LOG_JOURNAL_H
+#ifndef NETDATA_LOG_SYSTEMD_JOURNAL_HELPERS_H
+#define NETDATA_LOG_SYSTEMD_JOURNAL_HELPERS_H
#define JOURNAL_DIRECT_SOCKET "/run/systemd/journal/socket"
@@ -15,4 +15,4 @@ bool journal_direct_send(int fd, const char *msg, size_t msg_len);
bool is_path_unix_socket(const char *path);
bool is_stderr_connected_to_journal(void);
-#endif //NETDATA_LOG_JOURNAL_H
+#endif // NETDATA_LOG_SYSTEMD_JOURNAL_HELPERS_H
diff --git a/src/libnetdata/log/wevt_netdata_compile.bat b/src/libnetdata/log/wevt_netdata_compile.bat
new file mode 100644
index 000000000..279b6c31b
--- /dev/null
+++ b/src/libnetdata/log/wevt_netdata_compile.bat
@@ -0,0 +1,121 @@
+@echo off
+setlocal enabledelayedexpansion
+
+echo PATH=%PATH%
+
+if "%~1"=="" (
+ echo Error: Missing .mc file path.
+ goto :usage
+)
+if "%~2"=="" (
+ echo Error: Missing destination directory.
+ goto :usage
+)
+
+REM Set variables
+set "SRC_DIR=%~1"
+set "BIN_DIR=%~2"
+set "MC_FILE=%BIN_DIR%\wevt_netdata.mc"
+set "MAN_FILE=%BIN_DIR%\wevt_netdata_manifest.xml"
+set "BASE_NAME=wevt_netdata"
+set "SDK_PATH=C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64"
+set "VS_PATH=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.39.33519\bin\Hostx64\x64"
+
+if not exist "%SRC_DIR%" (
+ echo Error: Source directory does not exist.
+ exit /b 1
+)
+
+if not exist "%BIN_DIR%" (
+ echo Error: Destination directory does not exist.
+ exit /b 1
+)
+
+if not exist "%MC_FILE%" (
+ echo Error: %MC_FILE% not found.
+ exit /b 1
+)
+
+if not exist "%MAN_FILE%" (
+ echo Error: %MAN_FILE% not found.
+ exit /b 1
+)
+
+REM Add SDK paths to PATH
+set "PATH=C:\Windows\System32;%SDK_PATH%;%VS_PATH%;%PATH%"
+
+REM Check if commands are available
+where mc >nul 2>nul
+if %errorlevel% neq 0 (
+ echo Error: mc.exe not found in PATH.
+ exit /b 1
+)
+where rc >nul 2>nul
+if %errorlevel% neq 0 (
+ echo Error: rc.exe not found in PATH.
+ exit /b 1
+)
+where link >nul 2>nul
+if %errorlevel% neq 0 (
+ echo Error: link.exe not found in PATH.
+ exit /b 1
+)
+where wevtutil >nul 2>nul
+if %errorlevel% neq 0 (
+ echo Error: wevtutil.exe not found in PATH.
+ exit /b 1
+)
+
+REM Change to the destination directory
+cd /d "%BIN_DIR%"
+
+echo.
+echo Running mc.exe...
+mc -v -b -U "%MC_FILE%" "%MAN_FILE%"
+if %errorlevel% neq 0 (
+ echo Error: mc.exe failed on messages.
+ exit /b 1
+)
+
+if not exist "%BASE_NAME%.rc" (
+ echo Error: %BASE_NAME%.rc not found.
+ exit /b 1
+)
+
+echo.
+echo Modifying %BASE_NAME%.rc to include the manifest...
+copy "%MAN_FILE%" %BASE_NAME%_manifest.man
+echo 1 2004 "%BASE_NAME%_manifest.man" >> %BASE_NAME%.rc
+
+echo.
+echo %BASE_NAME%.rc contents:
+type %BASE_NAME%.rc
+
+echo.
+echo Running rc.exe...
+rc /v /fo %BASE_NAME%.res %BASE_NAME%.rc
+if %errorlevel% neq 0 (
+ echo Error: rc.exe failed.
+ exit /b 1
+)
+
+if not exist "%BASE_NAME%.res" (
+ echo Error: %BASE_NAME%.res not found.
+ exit /b 1
+)
+
+echo.
+echo Running link.exe...
+link /dll /noentry /machine:x64 /out:%BASE_NAME%.dll %BASE_NAME%.res
+if %errorlevel% neq 0 (
+ echo Error: link.exe failed.
+ exit /b 1
+)
+
+echo.
+echo Process completed successfully.
+exit /b 0
+
+:usage
+echo Usage: %~nx0 [path_to_mc_file] [destination_directory]
+exit /b 1
diff --git a/src/libnetdata/log/wevt_netdata_compile.sh b/src/libnetdata/log/wevt_netdata_compile.sh
new file mode 100644
index 000000000..eae510645
--- /dev/null
+++ b/src/libnetdata/log/wevt_netdata_compile.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+mylocation=$(dirname "${0}")
+
+# Check if both parameters are provided
+if [ $# -ne 2 ]; then
+ echo "Error: Incorrect number of parameters."
+ echo "Usage: $0 <source_directory> <destination_directory>"
+ exit 1
+fi
+
+# Get the parameters
+src_dir="$1"
+dest_dir="$2"
+
+# Get the directory of this script
+SCRIPT_DIR="$(dirname "$0")"
+
+# Create a temporary batch file
+temp_bat=$(mktemp --suffix=.bat)
+
+# Write the contents to the temporary batch file
+# Use cygpath directly within the heredoc
+cat << EOF > "$temp_bat"
+@echo off
+set "PATH=%SYSTEMROOT%;$("${mylocation}/../../../packaging/windows/find-sdk-path.sh" --sdk -w);$("${mylocation}/../../../packaging/windows/find-sdk-path.sh" --visualstudio -w)"
+call "$(cygpath -w -a "$SCRIPT_DIR/wevt_netdata_compile.bat")" "$(cygpath -w -a "$src_dir")" "$(cygpath -w -a "$dest_dir")"
+EOF
+
+# Execute the temporary batch file
+echo
+echo "Executing Windows Batch File..."
+echo
+cat "$temp_bat"
+cmd.exe //c "$(cygpath -w -a "$temp_bat")"
+exit_status=$?
+
+# Remove the temporary batch file
+rm "$temp_bat"
+
+# Check the exit status
+if [ $exit_status -eq 0 ]; then
+ echo "nd_wevents_compile.bat executed successfully."
+else
+ echo "nd_wevents_compile.bat failed with exit status $exit_status."
+fi
+
+exit $exit_status
diff --git a/src/libnetdata/log/wevt_netdata_install.bat b/src/libnetdata/log/wevt_netdata_install.bat
new file mode 100644
index 000000000..515607592
--- /dev/null
+++ b/src/libnetdata/log/wevt_netdata_install.bat
@@ -0,0 +1,52 @@
+@echo off
+setlocal enabledelayedexpansion
+
+set "MAN_SRC=%~dp0wevt_netdata_manifest.xml"
+set "DLL_SRC=%~dp0wevt_netdata.dll"
+set "DLL_DST=%SystemRoot%\System32\wevt_netdata.dll"
+
+where wevtutil >nul 2>nul
+if %errorlevel% neq 0 (
+ echo Error: wevtutil.exe not found in PATH.
+ exit /b 1
+)
+
+echo.
+echo Uninstalling previous manifest (if any)...
+wevtutil um "%MAN_SRC%"
+
+echo.
+echo Copying %DLL_SRC% to %DLL_DST%
+copy /y "%DLL_SRC%" "%DLL_DST%"
+if %errorlevel% neq 0 (
+ echo Error: Failed to copy %DLL_SRC% to %DLL_DST%
+ exit /b 1
+)
+
+echo.
+echo Granting access to %DLL_DST% for Windows Event Logging...
+icacls "%DLL_DST%" /grant "NT SERVICE\EventLog":R
+if %errorlevel% neq 0 (
+ echo Error: Failed to grant access to %DLL_DST%.
+ exit /b 1
+)
+
+echo.
+echo Importing the manifest...
+wevtutil im "%MAN_SRC%" /rf:"%DLL_DST%" /mf:"%DLL_DST%"
+if %errorlevel% neq 0 (
+ echo Error: Failed to import the manifest.
+ exit /b 1
+)
+
+echo.
+echo Verifying Netdata Publisher for Event Tracing for Windows (ETW)...
+wevtutil gp "Netdata"
+if %errorlevel% neq 0 (
+ echo Error: Failed to get publisher Netdata.
+ exit /b 1
+)
+
+echo.
+echo Netdata Event Tracing for Windows manifest installed successfully.
+exit /b 0
diff --git a/src/libnetdata/log/wevt_netdata_mc_generate.c b/src/libnetdata/log/wevt_netdata_mc_generate.c
new file mode 100644
index 000000000..5ab2bdf17
--- /dev/null
+++ b/src/libnetdata/log/wevt_netdata_mc_generate.c
@@ -0,0 +1,518 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <string.h>
+
+// from winnt.h
+#define EVENTLOG_SUCCESS 0x0000
+#define EVENTLOG_ERROR_TYPE 0x0001
+#define EVENTLOG_WARNING_TYPE 0x0002
+#define EVENTLOG_INFORMATION_TYPE 0x0004
+#define EVENTLOG_AUDIT_SUCCESS 0x0008
+#define EVENTLOG_AUDIT_FAILURE 0x0010
+
+// the severities we define in .mc file
+#define STATUS_SEVERITY_INFORMATIONAL 0x1
+#define STATUS_SEVERITY_WARNING 0x2
+#define STATUS_SEVERITY_ERROR 0x3
+
+#define FACILITY_APPLICATION 0x0fff
+
+#include "nd_log-common.h"
+#include "nd_log-to-windows-common.h"
+
+const char *get_msg_symbol(MESSAGE_ID msg) {
+ switch(msg) {
+ case MSGID_MESSAGE_ONLY:
+ return "MESSAGE_ONLY";
+
+ case MSGID_MESSAGE_ERRNO:
+ return "MESSAGE_ERRNO";
+
+ case MSGID_REQUEST_ONLY:
+ return "REQUEST_ONLY";
+
+ case MSGID_ACCESS_MESSAGE:
+ return "ACCESS_MESSAGE";
+
+ case MSGID_ACCESS_MESSAGE_REQUEST:
+ return "ACCESS_MESSAGE_REQUEST";
+
+ case MSGID_ACCESS_MESSAGE_USER:
+ return "ACCESS_MESSAGE_USER";
+
+ case MSGID_ACCESS:
+ return "ACCESS";
+
+ case MSGID_ACCESS_USER:
+ return "ACCESS_USER";
+
+ case MSGID_ACCESS_FORWARDER:
+ return "ACCESS_FORWARDER";
+
+ case MSGID_ACCESS_FORWARDER_USER:
+ return "ACCESS_FORWARDER_USER";
+
+ case MSGID_ALERT_TRANSITION:
+ return "ALERT_TRANSITION";
+
+ default:
+ fprintf(stderr, "\n\nInvalid message id %d!\n\n\n", msg);
+ exit(1);
+ }
+}
+
+const char *get_msg_format(MESSAGE_ID msg) {
+ switch(msg) {
+ case MSGID_MESSAGE_ONLY:
+ return "%2(%12): %64\r\n";
+
+ case MSGID_MESSAGE_ERRNO:
+ return "%2(%12): %64%n\r\n"
+ "%n\r\n"
+ " Unix Errno : %5%n\r\n"
+ " Windows Error: %6%n\r\n"
+ ;
+
+ case MSGID_REQUEST_ONLY:
+ return "%2(%12): %63\r\n";
+
+ case MSGID_ACCESS_MESSAGE:
+ return "%64\r\n";
+
+ case MSGID_ACCESS_MESSAGE_REQUEST:
+ return "%64%n\r\n"
+ "%n\r\n"
+ " Request: %63%n\r\n"
+ ;
+
+ case MSGID_ACCESS_MESSAGE_USER:
+ return "%64%n\r\n"
+ "%n\r\n"
+ " User: %21, role: %22, permissions: %23%n\r\n"
+ ;
+
+ case MSGID_ACCESS:
+ return "%33 %63%n\r\n"
+ "%n\r\n"
+ " Response Code : %34%n\r\n"
+ " Transaction ID: %36%n\r\n"
+ " Source IP : %24%n\r\n"
+ ;
+
+ case MSGID_ACCESS_USER:
+ return "%33 %63%n\r\n"
+ "%n\r\n"
+ " Response Code : %34%n\r\n"
+ " Transaction ID: %36%n\r\n"
+ " Source IP : %24%n\r\n"
+ " User : %21, role: %22, permissions: %23%n\r\n"
+ ;
+
+ case MSGID_ACCESS_FORWARDER:
+ return "%33 %63%n\r\n"
+ "%n\r\n"
+ " Response Code : %34%n\r\n"
+ " Transaction ID: %36%n\r\n"
+ " Source IP : %24, For %27%n\r\n"
+ ;
+
+ case MSGID_ACCESS_FORWARDER_USER:
+ return "%33 %63%n\r\n"
+ "%n\r\n"
+ " Response Code : %34%n\r\n"
+ " Transaction ID: %36%n\r\n"
+ " Source IP : %24, For %27%n\r\n"
+ " User : %21, role: %22, permissions: %23%n\r\n"
+ ;
+
+ case MSGID_ALERT_TRANSITION:
+ return "Alert '%47' of instance '%16' on node '%15' transitioned from %57 to %56\r\n";
+
+ default:
+ fprintf(stderr, "\n\nInvalid message id %d!\n\n\n", msg);
+ exit(1);
+ }
+}
+
+int main(int argc, const char **argv) {
+ (void)argc; (void)argv;
+
+ const char *header = NULL, *footer = NULL, *s_header = NULL, *s_footer = NULL;
+
+ bool manifest = false;
+ if(argc == 2 && strcmp(argv[1], "--manifest") == 0) {
+ manifest = true;
+
+ header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
+ "<!--\r\n"
+ "\r\n"
+ " THIS FILE IS AUTOMATICALLY GENERATED - DO NOT EDIT\r\n"
+ "\r\n"
+ " This XML file can be verified by running mc.exe (the MS tool) with this manifest as param.\r\n"
+ "\r\n"
+ " \"c:\\Program Files (x86)\\Windows Kits\\10\\bin\\10.0.26100.0\\x64\\mc.exe\" wevt_netdata_manifest.xml wevt_netdata.mc\r\n"
+ "\r\n"
+ " -->\r\n"
+ "<instrumentationManifest\r\n"
+ " xmlns=\"http://schemas.microsoft.com/win/2004/08/events\"\r\n"
+ " xmlns:win=\"http://manifests.microsoft.com/win/2004/08/windows/events\"\r\n"
+ " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\r\n"
+ " <instrumentation>\r\n"
+ " <events>\r\n"
+ "\r\n"
+ " <provider name=\"" NETDATA_ETW_PROVIDER_NAME "\"\r\n"
+ " guid=\"" NETDATA_ETW_PROVIDER_GUID_STR "\"\r\n"
+ " symbol=\"NETDATA_ETW_PROVIDER_GUID\"\r\n"
+ " messageFileName=\"%SystemRoot%\\System32\\wevt_netdata.dll\"\r\n"
+ " resourceFileName=\"%SystemRoot%\\System32\\wevt_netdata.dll\"\r\n"
+ " message=\"$(string.ND_PROVIDER_NAME)\">\r\n"
+ "\r\n"
+ " <!-- Define the provider sub-channels -->\r\n"
+ " <channels>\r\n"
+ " <channel name=\"" NETDATA_ETW_CHANNEL_NAME "/" NETDATA_ETW_SUBCHANNEL_DAEMON "\"\r\n"
+ " symbol=\"CHANNEL_DAEMON\"\r\n"
+ " type=\"Operational\"\r\n"
+ " message=\"$(string.Channel.Daemon)\"\r\n"
+ " enabled=\"true\"\r\n"
+ " />\r\n"
+ "\r\n"
+ " <channel name=\"" NETDATA_ETW_CHANNEL_NAME "/" NETDATA_ETW_SUBCHANNEL_COLLECTORS "\"\r\n"
+ " symbol=\"CHANNEL_COLLECTORS\"\r\n"
+ " type=\"Operational\"\r\n"
+ " message=\"$(string.Channel.Collectors)\"\r\n"
+ " enabled=\"true\"\r\n"
+ " />\r\n"
+ "\r\n"
+ " <channel name=\"" NETDATA_ETW_CHANNEL_NAME "/" NETDATA_ETW_SUBCHANNEL_ACCESS "\"\r\n"
+ " symbol=\"CHANNEL_ACCESS\"\r\n"
+ " type=\"Operational\"\r\n"
+ " message=\"$(string.Channel.Access)\"\r\n"
+ " enabled=\"true\"\r\n"
+ " />\r\n"
+ "\r\n"
+ " <channel name=\"" NETDATA_ETW_CHANNEL_NAME "/" NETDATA_ETW_SUBCHANNEL_HEALTH "\"\r\n"
+ " symbol=\"CHANNEL_HEALTH\"\r\n"
+ " type=\"Operational\"\r\n"
+ " message=\"$(string.Channel.Health)\"\r\n"
+ " enabled=\"true\"\r\n"
+ " />\r\n"
+ "\r\n"
+ " <channel name=\"" NETDATA_ETW_CHANNEL_NAME "/" NETDATA_ETW_SUBCHANNEL_ACLK "\"\r\n"
+ " symbol=\"CHANNEL_ACLK\"\r\n"
+ " type=\"Operational\"\r\n"
+ " message=\"$(string.Channel.Aclk)\"\r\n"
+ " enabled=\"true\"\r\n"
+ " />\r\n"
+ " </channels>\r\n"
+ "\r\n"
+ " <levels>\r\n"
+ " </levels>\r\n"
+ "\r\n"
+ " <opcodes>\r\n"
+ " </opcodes>\r\n"
+ "\r\n"
+ " <tasks>\r\n"
+ " </tasks>\r\n"
+ "\r\n"
+ " <templates>\r\n"
+ " <template tid=\"AllFieldsTemplate\">\r\n"
+ " <!-- 0 (NDF_STOP) should not be here %1 is Timestamp, %64 is the Message -->\r\n"
+ " <data name=\"Timestamp\" inType=\"win:UnicodeString\"/> <!-- 1 (NDF_TIMESTAMP_REALTIME_USEC) -->\r\n"
+ " <data name=\"Program\" inType=\"win:UnicodeString\"/> <!-- 2 (NDF_SYSLOG_IDENTIFIER) -->\r\n"
+ " <data name=\"NetdataLogSource\" inType=\"win:UnicodeString\"/> <!-- 3 (NDF_LOG_SOURCE) -->\r\n"
+ " <data name=\"Level\" inType=\"win:UnicodeString\"/> <!-- 4 (NDF_PRIORITY) -->\r\n"
+ " <data name=\"UnixErrno\" inType=\"win:UnicodeString\"/> <!-- 5 (NDF_ERRNO) -->\r\n"
+ " <data name=\"WindowsLastError\" inType=\"win:UnicodeString\"/> <!-- 6 (NDF_WINERROR) -->\r\n"
+ " <data name=\"InvocationID\" inType=\"win:UnicodeString\"/> <!-- 7 (NDF_INVOCATION_ID) -->\r\n"
+ " <data name=\"CodeLine\" inType=\"win:UnicodeString\"/> <!-- 8 (NDF_LINE) -->\r\n"
+ " <data name=\"CodeFile\" inType=\"win:UnicodeString\"/> <!-- 9 (NDF_FILE) -->\r\n"
+ " <data name=\"CodeFunction\" inType=\"win:UnicodeString\"/> <!-- 10 (NDF_FUNC) -->\r\n"
+ " <data name=\"ThreadID\" inType=\"win:UnicodeString\"/> <!-- 11 (NDF_TID) -->\r\n"
+ " <data name=\"ThreadName\" inType=\"win:UnicodeString\"/> <!-- 12 (NDF_THREAD_TAG) -->\r\n"
+ " <data name=\"MessageID\" inType=\"win:UnicodeString\"/> <!-- 13 (NDF_MESSAGE_ID) -->\r\n"
+ " <data name=\"Module\" inType=\"win:UnicodeString\"/> <!-- 14 (NDF_MODULE) -->\r\n"
+ " <data name=\"Node\" inType=\"win:UnicodeString\"/> <!-- 15 (NDF_NIDL_NODE) -->\r\n"
+ " <data name=\"Instance\" inType=\"win:UnicodeString\"/> <!-- 16 (NDF_NIDL_INSTANCE) -->\r\n"
+ " <data name=\"Context\" inType=\"win:UnicodeString\"/> <!-- 17 (NDF_NIDL_CONTEXT) -->\r\n"
+ " <data name=\"Dimension\" inType=\"win:UnicodeString\"/> <!-- 18 (NDF_NIDL_DIMENSION) -->\r\n"
+ " <data name=\"SourceTransport\" inType=\"win:UnicodeString\"/> <!-- 19 (NDF_SRC_TRANSPORT) -->\r\n"
+ " <data name=\"AccountID\" inType=\"win:UnicodeString\"/> <!-- 20 (NDF_ACCOUNT_ID) -->\r\n"
+ " <data name=\"UserName\" inType=\"win:UnicodeString\"/> <!-- 21 (NDF_USER_NAME) -->\r\n"
+ " <data name=\"UserRole\" inType=\"win:UnicodeString\"/> <!-- 22 (NDF_USER_ROLE) -->\r\n"
+ " <data name=\"UserPermissions\" inType=\"win:UnicodeString\"/> <!-- 23 (NDF_USER_ACCESS) -->\r\n"
+ " <data name=\"SourceIP\" inType=\"win:UnicodeString\"/> <!-- 24 (NDF_SRC_IP) -->\r\n"
+ " <data name=\"SourceForwardedHost\" inType=\"win:UnicodeString\"/> <!-- 25 (NDF_SRC_PORT) -->\r\n"
+ " <data name=\"SourceForwardedFor\" inType=\"win:UnicodeString\"/> <!-- 26 (NDF_SRC_FORWARDED_HOST) -->\r\n"
+ " <data name=\"SourcePort\" inType=\"win:UnicodeString\"/> <!-- 27 (NDF_SRC_FORWARDED_FOR) -->\r\n"
+ " <data name=\"SourceCapabilities\" inType=\"win:UnicodeString\"/> <!-- 28 (NDF_SRC_CAPABILITIES) -->\r\n"
+ " <data name=\"DestinationTransport\" inType=\"win:UnicodeString\"/> <!-- 29 (NDF_DST_TRANSPORT) -->\r\n"
+ " <data name=\"DestinationIP\" inType=\"win:UnicodeString\"/> <!-- 30 (NDF_DST_IP) -->\r\n"
+ " <data name=\"DestinationPort\" inType=\"win:UnicodeString\"/> <!-- 31 (NDF_DST_PORT) -->\r\n"
+ " <data name=\"DestinationCapabilities\" inType=\"win:UnicodeString\"/> <!-- 32 (NDF_DST_CAPABILITIES) -->\r\n"
+ " <data name=\"RequestMethod\" inType=\"win:UnicodeString\"/> <!-- 33 (NDF_REQUEST_METHOD) -->\r\n"
+ " <data name=\"ResponseCode\" inType=\"win:UnicodeString\"/> <!-- 34 (NDF_RESPONSE_CODE) -->\r\n"
+ " <data name=\"ConnectionID\" inType=\"win:UnicodeString\"/> <!-- 35 (NDF_CONNECTION_ID) -->\r\n"
+ " <data name=\"TransactionID\" inType=\"win:UnicodeString\"/> <!-- 36 (NDF_TRANSACTION_ID) -->\r\n"
+ " <data name=\"ResponseSentBytes\" inType=\"win:UnicodeString\"/> <!-- 37 (NDF_RESPONSE_SENT_BYTES) -->\r\n"
+ " <data name=\"ResponseSizeBytes\" inType=\"win:UnicodeString\"/> <!-- 38 (NDF_RESPONSE_SIZE_BYTES) -->\r\n"
+ " <data name=\"ResponsePreparationTimeUsec\" inType=\"win:UnicodeString\"/> <!-- 39 (NDF_RESPONSE_PREPARATION_TIME_USEC) -->\r\n"
+ " <data name=\"ResponseSentTimeUsec\" inType=\"win:UnicodeString\"/> <!-- 40 (NDF_RESPONSE_SENT_TIME_USEC) -->\r\n"
+ " <data name=\"ResponseTotalTimeUsec\" inType=\"win:UnicodeString\"/> <!-- 41 (NDF_RESPONSE_TOTAL_TIME_USEC) -->\r\n"
+ " <data name=\"AlertID\" inType=\"win:UnicodeString\"/> <!-- 42 (NDF_ALERT_ID) -->\r\n"
+ " <data name=\"AlertUniqueID\" inType=\"win:UnicodeString\"/> <!-- 43 (NDF_ALERT_UNIQUE_ID) -->\r\n"
+ " <data name=\"AlertTransitionID\" inType=\"win:UnicodeString\"/> <!-- 44 (NDF_ALERT_TRANSITION_ID) -->\r\n"
+ " <data name=\"AlertEventID\" inType=\"win:UnicodeString\"/> <!-- 45 (NDF_ALERT_EVENT_ID) -->\r\n"
+ " <data name=\"AlertConfig\" inType=\"win:UnicodeString\"/> <!-- 46 (NDF_ALERT_CONFIG_HASH) -->\r\n"
+ " <data name=\"AlertName\" inType=\"win:UnicodeString\"/> <!-- 47 (NDF_ALERT_NAME) -->\r\n"
+ " <data name=\"AlertClass\" inType=\"win:UnicodeString\"/> <!-- 48 (NDF_ALERT_CLASS) -->\r\n"
+ " <data name=\"AlertComponent\" inType=\"win:UnicodeString\"/> <!-- 49 (NDF_ALERT_COMPONENT) -->\r\n"
+ " <data name=\"AlertType\" inType=\"win:UnicodeString\"/> <!-- 50 (NDF_ALERT_TYPE) -->\r\n"
+ " <data name=\"AlertExec\" inType=\"win:UnicodeString\"/> <!-- 51 (NDF_ALERT_EXEC) -->\r\n"
+ " <data name=\"AlertRecipient\" inType=\"win:UnicodeString\"/> <!-- 52 (NDF_ALERT_RECIPIENT) -->\r\n"
+ " <data name=\"AlertDuration\" inType=\"win:UnicodeString\"/> <!-- 53 (NDF_ALERT_DURATION) -->\r\n"
+ " <data name=\"AlertValue\" inType=\"win:UnicodeString\"/> <!-- 54 (NDF_ALERT_VALUE) -->\r\n"
+ " <data name=\"AlertOldValue\" inType=\"win:UnicodeString\"/> <!-- 55 (NDF_ALERT_VALUE_OLD) -->\r\n"
+ " <data name=\"AlertStatus\" inType=\"win:UnicodeString\"/> <!-- 56 (NDF_ALERT_STATUS) -->\r\n"
+ " <data name=\"AlertOldStatus\" inType=\"win:UnicodeString\"/> <!-- 57 (NDF_ALERT_STATUS_OLD) -->\r\n"
+ " <data name=\"Source\" inType=\"win:UnicodeString\"/> <!-- 58 (NDF_ALERT_SOURCE) -->\r\n"
+ " <data name=\"AlertUnits\" inType=\"win:UnicodeString\"/> <!-- 59 (NDF_ALERT_UNITS) -->\r\n"
+ " <data name=\"AlertSummary\" inType=\"win:UnicodeString\"/> <!-- 60 (NDF_ALERT_SUMMARY) -->\r\n"
+ " <data name=\"AlertInfo\" inType=\"win:UnicodeString\"/> <!-- 61 (NDF_ALERT_INFO) -->\r\n"
+ " <data name=\"AlertNotificationTime\" inType=\"win:UnicodeString\"/> <!-- 62 (NDF_ALERT_NOTIFICATION_REALTIME_USEC) -->\r\n"
+ " <data name=\"Request\" inType=\"win:UnicodeString\"/> <!-- 63 (NDF_REQUEST) -->\r\n"
+ " <data name=\"Message\" inType=\"win:UnicodeString\"/> <!-- 64 (NDF_MESSAGE) -->\r\n"
+ " </template>\r\n"
+ " </templates>\r\n"
+ "\r\n"
+ " <events>\r\n"
+ ;
+
+ footer = " </events>\r\n"
+ " </provider>\r\n"
+ " </events>\r\n"
+ " </instrumentation>\r\n"
+ ;
+
+ s_header = " <localization>\r\n"
+ " <resources culture=\"en-US\">\r\n"
+ " <stringTable>\r\n"
+ " <string id=\"ND_PROVIDER_NAME\" value=\"" NETDATA_ETW_PROVIDER_NAME "\"/>\r\n"
+ "\r\n"
+ " <string id=\"Channel.Daemon\" value=\"Daemon\"/>\r\n"
+ " <string id=\"Channel.Collectors\" value=\"Collectors\"/>\r\n"
+ " <string id=\"Channel.Access\" value=\"Access\"/>\r\n"
+ " <string id=\"Channel.Health\" value=\"Health\"/>\r\n"
+ " <string id=\"Channel.Aclk\" value=\"Aclk\"/>\r\n"
+ "\r\n"
+ ;
+
+ s_footer = " </stringTable>\r\n"
+ " </resources>\r\n"
+ " </localization>\r\n"
+ "</instrumentationManifest>\r\n"
+ ;
+ }
+ else {
+ header = ";// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT EDIT\r\n"
+ "\r\n"
+ "MessageIdTypedef=DWORD\r\n"
+ "\r\n"
+ "SeverityNames=(\r\n"
+ " Informational=0x1:STATUS_SEVERITY_INFORMATIONAL\r\n"
+ " Warning=0x2:STATUS_SEVERITY_WARNING\r\n"
+ " Error=0x3:STATUS_SEVERITY_ERROR\r\n"
+ " )\r\n"
+ "\r\n"
+ "FacilityNames=(\r\n"
+ " " NETDATA_CHANNEL_NAME "=0x0FFF:FACILITY_NETDATA\r\n"
+ " )\r\n"
+ "\r\n"
+ "LanguageNames=(\r\n"
+ " English=0x409:MSG00409\r\n"
+ " )\r\n"
+ "\r\n"
+ ;
+
+ footer = "";
+ }
+
+ bool done[UINT16_MAX] = { 0 };
+ char symbol[1024];
+
+ printf("%s", header);
+ for(size_t src = 1; src < _NDLS_MAX ;src++) {
+ for(size_t pri = 0; pri < _NDLP_MAX ;pri++) {
+ uint8_t severity = get_severity_from_priority(pri);
+
+ for(size_t msg = 1; msg < _MSGID_MAX ;msg++) {
+
+ if(src >= 16) {
+ fprintf(stderr, "\n\nSource %zu is bigger than 4 bits!\n\n", src);
+ return 1;
+ }
+
+ if(pri >= 16) {
+ fprintf(stderr, "\n\nPriority %zu is bigger than 4 bits!\n\n", pri);
+ return 1;
+ }
+
+ if(msg >= 256) {
+ fprintf(stderr, "\n\nMessageID %zu is bigger than 8 bits!\n\n", msg);
+ return 1;
+ }
+
+ uint16_t eventID = construct_event_code(src, pri, msg);
+ if((eventID & 0xFFFF) != eventID) {
+ fprintf(stderr, "\n\nEventID 0x%x is bigger than 16 bits!\n\n", eventID);
+ return 1;
+ }
+
+ if(done[eventID]) continue;
+ done[eventID] = true;
+
+ const char *level = get_level_from_priority_str(pri);
+ const char *pri_txt;
+ switch(pri) {
+ case NDLP_EMERG:
+ pri_txt = "EMERG";
+ break;
+
+ case NDLP_CRIT:
+ pri_txt = "CRIT";
+ break;
+
+ case NDLP_ALERT:
+ pri_txt = "ALERT";
+ break;
+
+ case NDLP_ERR:
+ pri_txt = "ERR";
+ break;
+
+ case NDLP_WARNING:
+ pri_txt = "WARN";
+ break;
+
+ case NDLP_INFO:
+ pri_txt = "INFO";
+ break;
+
+ case NDLP_NOTICE:
+ pri_txt = "NOTICE";
+ break;
+
+ case NDLP_DEBUG:
+ pri_txt = "DEBUG";
+ break;
+
+ default:
+ fprintf(stderr, "\n\nInvalid priority %zu!\n\n\n", pri);
+ return 1;
+ }
+
+ const char *channel;
+ const char *src_txt;
+ switch(src) {
+ case NDLS_COLLECTORS:
+ src_txt = "COLLECTORS";
+ channel = NETDATA_ETW_CHANNEL_NAME "/" NETDATA_ETW_SUBCHANNEL_COLLECTORS;
+ break;
+
+ case NDLS_ACCESS:
+ src_txt = "ACCESS";
+ channel = NETDATA_ETW_CHANNEL_NAME "/" NETDATA_ETW_SUBCHANNEL_ACCESS;
+ break;
+
+ case NDLS_HEALTH:
+ src_txt = "HEALTH";
+ channel = NETDATA_ETW_CHANNEL_NAME "/" NETDATA_ETW_SUBCHANNEL_HEALTH;
+ break;
+
+ case NDLS_DEBUG:
+ src_txt = "DEBUG";
+ channel = NETDATA_ETW_CHANNEL_NAME "/" NETDATA_ETW_SUBCHANNEL_DAEMON;
+ break;
+
+ case NDLS_DAEMON:
+ src_txt = "DAEMON";
+ channel = NETDATA_ETW_CHANNEL_NAME "/" NETDATA_ETW_SUBCHANNEL_DAEMON;
+ break;
+
+ case NDLS_ACLK:
+ src_txt = "ACLK";
+ channel = NETDATA_ETW_CHANNEL_NAME "/" NETDATA_ETW_SUBCHANNEL_ACLK;
+ break;
+
+ default:
+ fprintf(stderr, "\n\nInvalid source %zu!\n\n\n", src);
+ return 1;
+ }
+
+ const char *msg_txt = get_msg_symbol(msg);
+ const char *format = get_msg_format(msg);
+
+ const char *severity_txt;
+ switch (severity) {
+ case STATUS_SEVERITY_INFORMATIONAL:
+ severity_txt = "Informational";
+ break;
+
+ case STATUS_SEVERITY_ERROR:
+ severity_txt = "Error";
+ break;
+
+ case STATUS_SEVERITY_WARNING:
+ severity_txt = "Warning";
+ break;
+
+ default:
+ fprintf(stderr, "\n\nInvalid severity id %u!\n\n\n", severity);
+ return 1;
+ }
+
+ if(manifest)
+ snprintf(symbol, sizeof(symbol), "ED_%s_%s_%s", src_txt, pri_txt, msg_txt);
+ else
+ snprintf(symbol, sizeof(symbol), "MC_%s_%s_%s", src_txt, pri_txt, msg_txt);
+
+ if(manifest)
+ printf(" <event symbol=\"%s\"\r\n"
+ " value=\"0x%x\"\r\n"
+ " message=\"$(string.msg.MAN_%s)\"\r\n"
+ " channel=\"%s\"\r\n"
+ " level=\"%s\"\r\n"
+ " task=\"win:None\"\r\n"
+ " opcode=\"win:Info\"\r\n"
+ " template=\"AllFieldsTemplate\"/>\r\n\r\n",
+ symbol, eventID, msg_txt, channel, level);
+ else
+ printf("MessageId=0x%x\r\n"
+ "Severity=%s\r\n"
+ "Facility=" NETDATA_CHANNEL_NAME "\r\n"
+ "SymbolicName=%s\r\n"
+ "Language=English\r\n"
+ "%s"
+ ".\r\n"
+ "\r\n",
+ eventID, severity_txt, symbol, format);
+ }
+ }
+ }
+ printf("%s", footer);
+
+ if(s_header) {
+ printf("%s", s_header);
+
+ for(size_t msg = 1; msg < _MSGID_MAX ;msg++) {
+ const char *msg_txt = get_msg_symbol(msg);
+ const char *format = get_msg_format(msg);
+ printf(" <string id=\"msg.MAN_%s\" value=\"%s\"/>\r\n", msg_txt, format);
+ }
+
+ printf("%s", s_footer);
+ }
+}
+