10 Commits

Author SHA1 Message Date
asif 08a5c823d5 refactor(log): Use CLEF for JSON logs
Use Compact Log Event Format (CLEF) for JSON logs along with custom properties.
2026-07-24 04:41:23 -06:00
asif eafba0d67d perf(log): Use compact JSON for logging 2026-07-24 04:36:26 -06:00
asif e25bd9fb4d feat(func): Add JSON logging func
Update central logging steering function to reference this new JSON logging backend function.
2026-07-24 04:02:21 -06:00
asif bff59f5c1f chore(ide): Save custom dictionary 2026-07-24 04:01:35 -06:00
asif 539b80e4ec feat(script): Use HTTPS ip discovery links 2026-07-24 03:52:30 -06:00
asif 58520b3567 refactor(log): Remove redundant debug log handling
Debug-level logging is handled by the central logging steering function. Remove redundant code in backend plaint-text logging function.
2026-07-24 03:28:49 -06:00
asif 51bd6160f3 feat(func): Use centralized log func
Create single logging function that can be called and then forwards the request to the appropriate logging backend function for completion.
2026-07-24 03:25:29 -06:00
asif 8d3489ba00 fix(vars): Add missing LOG_PROGRAM_NAME var 2026-07-24 02:48:26 -06:00
asif 3b97d112ad feat(log): Add CF-ERR to operation attr
Update 'listCFErrors' function so that 'CF-ERR' is appended to the 'operation' field of the error log.
2026-07-24 02:44:22 -06:00
asif 1506d77ee9 refactor(func): Remove no-colourize functions 2026-07-24 02:17:06 -06:00
2 changed files with 77 additions and 50 deletions
+4
View File
@@ -0,0 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=cfddns/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mydomain/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=myserver/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+73 -50
View File
@@ -35,18 +35,18 @@ badParam() {
if [ "$1" = "null" ]; then
printf "\n%sERROR: '%s' cannot have a NULL (empty) value.\n" "$err" "$2"
printf "%sPlease use '--help' for assistance.%s\n\n" "$cyan" "$norm"
([ "$logToConsole" -eq 0 ]) && writePlainTextLog \
([ "$logToConsole" -eq 0 ]) && writeLog \
"${2} cannot have a NULL (empty) value" "critical" "fail" "preexec" 1
exit 1
elif [ "$1" = "dne" ]; then
printf "\n%sERROR: '%s %s'\n" "$err" "$2" "$3"
printf "file or directory does not exist or is empty.%s\n\n" "$norm"
([ "$logToConsole" -eq 0 ]) && writePlainTextLog \
([ "$logToConsole" -eq 0 ]) && writeLog \
"${3}: file or directory does not exist or is empty" "critical" "fail" "preexec" 1
exit 1
elif [ "$1" = "errMsg" ]; then
printf "\n%sERROR: %s%s\n\n" "$err" "$2" "$norm"
([ "$logToConsole" -eq 0 ]) && writePlainTextLog \
([ "$logToConsole" -eq 0 ]) && writeLog \
"${2%%\Exit*}" "fatal" "fail" "preexec" 1
exit 1
fi
@@ -85,7 +85,7 @@ listCFErrors() {
messages="${messages#*_}"
# output to log
writePlainTextLog "${message}" "err" "fail" "$2" "$code"
writeLog "${message}" "err" "fail" "$2:CF-ERR" "$code"
done
}
@@ -113,6 +113,31 @@ standardizeLogLevels() {
esac
}
# $1: message, $2: level, $3: status, [$4: operation], [$5: code]
writeJsonLog() {
# not enough information to generate a meaningful log message
if [ -z "$1" ] || [ $# -lt 3 ]; then
return
fi
JSON_PROPS="$(
jq --null-input -c \
--arg timestamp "$(getTimeStamp)" \
--arg hostname "$(getHostname)" \
--arg pName "$LOG_PROGRAM_NAME" \
--arg pid "$$" \
--arg level "$(standardizeLogLevels "$2")" \
--arg status "$(uppercase "$3")" \
--arg message "$1" \
--arg operation "${4:-UNSPECIFIED}" \
--arg code "${5:-0}" \
'{"@t":$timestamp, "@m":$message, "@l":$level, "hostname":$hostname, "programName":$pName, "pid":$pid, "status":$status, "operation":$operation, "code":$code}' \
)"
printf "%s\n" "$JSON_PROPS" >>"$logFile"
return
}
# modified syslog fmt: <ISO-8601 timestamp> <hostname> <tag>[pid]: [LEVEL:$2] [STATUS:$3] <MESSAGE:$1> (<OPERATION:$4>:<CODE:$5>)
writePlainTextLog() {
# not enough information to generate a meaningful log message
@@ -120,16 +145,23 @@ writePlainTextLog() {
return
fi
PT_LOG_LEVEL="$(standardizeLogLevels "$2")"
([ "$logDebug" -eq 0 ] && [ "$PT_LOG_LEVEL" = "DEBUG" ]) && return
printf "%s %s %s[%s]: [%s] [%s] %s (%s:%s)\n" \
"$(getTimeStamp)" "$(getHostname)" "$LOG_PROGRAM_NAME" "$$" \
"$PT_LOG_LEVEL" "$(uppercase "$3")" "$1" "${4:-UNSPECIFIED}" "${5:-0}" \
"$(standardizeLogLevels "$2")" "$(uppercase "$3")" "$1" "${4:-UNSPECIFIED}" "${5:-0}" \
>>"$logFile"
}
writeLog() {
([ "$logToNone" -eq 1 ]) && return
PT_LOG_LEVEL="$(standardizeLogLevels "$2")"
([ "$logDebug" -eq 0 ] && [ "$PT_LOG_LEVEL" = "DEBUG" ]) && return
([ "$logToJournal" -eq 1 ]) && return # TODO: replace this with function when implemented
([ "$useJsonLogFmt" -eq 1 ]) && writeJsonLog "$@"
([ "$useSyslogLogFmt" -eq 1 ]) && writePlainTextLog "$@"
}
scriptExamples() {
newline
printf "Update Cloudflare DNS host A/AAAA records with current IP address.\n"
@@ -241,23 +273,24 @@ textBlockSwitches() {
}
### default variable values
LOG_PROGRAM_NAME="CFDDNS"
scriptPath="$(CDPATH='' \cd -- "$(dirname -- "$0")" && pwd -P)"
scriptName="$(basename "$0")"
logFile="$scriptPath/${scriptName%.*}.log"
logToJournal=0
logToConsole=0
logToNone=0
logDebug=0
useJsonLogFmt=1
useSyslogLogFmt=0
accountFile="$scriptPath/cloudflare.credentials"
colourizeLogFile=1
dnsRecords=""
dnsSeparator=","
ipAddress=""
ip4=1
ip6=0
ip4DetectionSvc="http://ipv4.icanhazip.com"
ip6DetectionSvc="http://ipv6.icanhazip.com"
ip4DetectionSvc="https://ipv4.icanhazip.com"
ip6DetectionSvc="https://ipv6.icanhazip.com"
invalidDomainCount=0
failedHostCount=0
@@ -296,6 +329,7 @@ while [ $# -gt 0 ]; do
--log-none)
# do not log anything
logFile="/dev/null"
logToNone=1
;;
--log-debug)
# log debugging messages
@@ -355,7 +389,7 @@ while [ $# -gt 0 ]; do
*)
printf "\n%sUnknown option: %s\n" "$err" "$1"
printf "%sUse '--help' for valid options.%s\n\n" "$cyan" "$norm"
([ "$logToConsole" -eq 0 ]) && writePlainTextLog \
([ "$logToConsole" -eq 0 ]) && writeLog \
"Unknown script parameter: '${1}'" "critical" "fail" "preexec" 1
exit 1
;;
@@ -366,12 +400,12 @@ done
### pre-flight checks
if ! command -v curl >/dev/null; then
printf "\n%sThis script requires that 'curl' is installed and accessible. Exiting.%s\n\n" "$err" "$norm"
([ "$logToConsole" -eq 0 ]) && writePlainTextLog "'curl' must be installed and accessible" "fatal" "fail" "preexec" 2
([ "$logToConsole" -eq 0 ]) && writeLog "'curl' must be installed and accessible" "fatal" "fail" "preexec" 2
exit 2
fi
if ! command -v jq >/dev/null; then
printf "\n%sThis script requires that 'jq' is installed and accessible. Exiting.%s\n\n" "$err" "$norm"
([ "$logToConsole" -eq 0 ]) && writePlainTextLog "'jq' must be installed and accessible" "fatal" "fail" "preexec" 2
([ "$logToConsole" -eq 0 ]) && writeLog "'jq' must be installed and accessible" "fatal" "fail" "preexec" 2
exit 2
fi
if [ -z "$dnsRecords" ]; then
@@ -385,27 +419,16 @@ if [ "$logToJournal" -eq 1 ] && ! command -v logger >/dev/null 2>&1; then
printf "\n%sThis script requires that 'logger' is installed to write journald entries. Exiting.%s\n\n" "$err" "$norm"
exit 2
fi
# turn off log file colourization if parameter is set
if [ "$colourizeLogFile" -eq 0 ]; then
bold=""
cyan=""
err=""
magenta=""
norm=""
ok=""
warn=""
yellow=""
fi
### initial log entries
writePlainTextLog "starting '${scriptName}'" "info" "ok" "script"
writePlainTextLog "script path: ${scriptPath}/${scriptName}" "debug" "ok" "startup"
writePlainTextLog "credentials file: ${accountFile}" "debug" "ok" "startup"
writeLog "starting '${scriptName}'" "info" "ok" "script"
writeLog "script path: ${scriptPath}/${scriptName}" "debug" "ok" "startup"
writeLog "credentials file: ${accountFile}" "debug" "ok" "startup"
if [ "$ip4" -eq 1 ]; then
writePlainTextLog "mode: IPv4" "debug" "ok" "startup"
writeLog "mode: IPv4" "debug" "ok" "startup"
elif [ "$ip6" -eq 1 ]; then
writePlainTextLog "mode: IPv6" "debug" "ok" "startup"
writeLog "mode: IPv6" "debug" "ok" "startup"
fi
# detect and report IP address
@@ -413,21 +436,21 @@ if [ -z "$ipAddress" ]; then
# detect public ip address
if [ "$ip4" -eq 1 ]; then
if ! ipAddress="$(curl -s $ip4DetectionSvc)"; then
writePlainTextLog \
writeLog \
"Unable to auto-detect this machine's public IP address; try again later or supply the IP address to be used" "err" "fail" "detectip" 10
exit 10
fi
fi
if [ "$ip6" -eq 1 ]; then
if ! ipAddress="$(curl -s $ip6DetectionSvc)"; then
writePlainTextLog \
writeLog \
"Unable to auto-detect this machine's public IP address; try again later or supply the IP address to be used" "err" "fail" "detectip" 10
exit 10
fi
fi
writePlainTextLog "DDNS IP address (detected): $ipAddress" "info" "ok" "startup"
writeLog "DDNS IP address (detected): $ipAddress" "info" "ok" "startup"
else
writePlainTextLog "DDNS IP address (supplied): $ipAddress" "info" "ok" "startup"
writeLog "DDNS IP address (supplied): $ipAddress" "info" "ok" "startup"
fi
# iterate DNS records to update
@@ -439,7 +462,7 @@ while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator"
if [ -z "$record" ]; then
continue
fi
writePlainTextLog "DNS host record '${record}' queued for update" "info" "ok" "startup"
writeLog "DNS host record '${record}' queued for update" "info" "ok" "startup"
done
### read Cloudflare credentials
@@ -456,14 +479,14 @@ case "$accountFile" in
;;
esac
if [ -z "$cfKey" ]; then
writePlainTextLog "Cloudflare authorized API key (cfKey) is either null or undefined; please check your Cloudflare credentials file" "err" "fail" "creds" 21
writeLog "Cloudflare authorized API key (cfKey) is either null or undefined; please check your Cloudflare credentials file" "err" "fail" "creds" 21
exit 21
fi
if [ -z "$cfZoneId" ]; then
writePlainTextLog "Cloudflare zone id (cfZoneId) is either null or undefined; please check your Cloudflare credentials file" "err" "fail" "creds" 22
writeLog "Cloudflare zone id (cfZoneId) is either null or undefined; please check your Cloudflare credentials file" "err" "fail" "creds" 22
exit 22
fi
writePlainTextLog "Cloudflare credentials file read successfully" "debug" "ok" "creds"
writeLog "Cloudflare credentials file read successfully" "debug" "ok" "creds"
### connect to Cloudflare and do what needs to be done!
dnsRecordsToUpdate="$dnsRecords$dnsSeparator"
@@ -488,7 +511,7 @@ while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator"
-H "Authorization: Bearer ${cfKey}" \
-H "Content-Type: application/json"
)"; then
writePlainTextLog "Unable to connect to Cloudflare servers; please try again later." "err" "fail" "cflogin" 3
writeLog "Unable to connect to Cloudflare servers; please try again later." "err" "fail" "cflogin" 3
exit 3
fi
@@ -497,7 +520,7 @@ while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator"
cfSuccess="$(printf "%s" "$cfLookup" | jq -r '.success')"
if [ "$cfSuccess" = "false" ]; then
listCFErrors "$cfLookup" "cflogin"
writePlainTextLog "Cloudflare API error; review any previously logged 'CF-ERR:' lines for details." "err" "fail" "cflogin" 25
writeLog "Cloudflare API error; review any previously logged 'CF-ERR:' lines for details." "err" "fail" "cflogin" 25
exit 25
fi
@@ -505,18 +528,18 @@ while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator"
# skip to the next host if an existing host record cannot be found (this script *updates* only, does not create!)
if [ "$resultCount" = "0" ]; then
writePlainTextLog "Cannot find an existing record matching '${record}' to update" "warn" "warn" "ddns"
writeLog "Cannot find an existing record matching '${record}' to update" "warn" "warn" "ddns"
invalidDomainCount=$((invalidDomainCount + 1))
continue
fi
objectId=$(printf "%s" "$cfLookup" | jq -r '.result | .[] | .id')
currentIpAddr=$(printf "%s" "$cfLookup" | jq -r '.result | .[] | .content')
writePlainTextLog "The current IP address for '${record}' is ${currentIpAddr}" "debug" "ok" "ddns"
writeLog "The current IP address for '${record}' is ${currentIpAddr}" "debug" "ok" "ddns"
# skip to next hostname if record already up to date
if [ "$currentIpAddr" = "$ipAddress" ]; then
writePlainTextLog "The IP address for '${record}' is already up to date" "info" "ok" "ddns"
writeLog "The IP address for '${record}' is already up to date" "info" "ok" "ddns"
continue
fi
@@ -530,17 +553,17 @@ while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator"
-H "Content-Type: application/json" \
--data "${updateJSON}"
)"; then
writePlainTextLog "Unable to connect to Cloudflare servers; please try again later." "err" "fail" "ddns" 3
writeLog "Unable to connect to Cloudflare servers; please try again later." "err" "fail" "ddns" 3
exit 3
fi
# note update success or failure
cfSuccess="$(printf "%s" "$cfResult" | jq '.success')"
if [ "$cfSuccess" = "true" ]; then
writePlainTextLog "The IP address for '${record}' successfully updated" "info" "ok" "ddns"
writeLog "The IP address for '${record}' successfully updated" "info" "ok" "ddns"
else
listCFErrors "$cfResult" "ddns"
writePlainTextLog "Unable to update the IP address for '${record}'" "err" "fail" "ddns"
writeLog "Unable to update the IP address for '${record}'" "err" "fail" "ddns"
# do not exit with error, API error here is probably an update issue specific to this host
# increment counter and note it after all processing finished
failedHostCount=$((failedHostCount + 1))
@@ -549,14 +572,14 @@ done
# exit
if [ "$invalidDomainCount" -ne 0 ]; then
writePlainTextLog "${invalidDomainCount} invalid host(s) were supplied for updating" "warn" "warn" "ddns"
writeLog "${invalidDomainCount} invalid host(s) were supplied for updating" "warn" "warn" "ddns"
fi
if [ "$failedHostCount" -ne 0 ]; then
writePlainTextLog \
writeLog \
"${failedHostCount} host update(s) failed; review 'CF-ERR:' lines in this log to help determine what may have gone wrong" "err" "fail" "ddns" 26
exit 26
else
writePlainTextLog "${scriptName} completed successfully" "info" "ok" "script"
writeLog "${scriptName} completed successfully" "info" "ok" "script"
fi
### exit return codes