From 51b2b455a262f2b2f16cf18861c2202eca040a6a Mon Sep 17 00:00:00 2001 From: Asif Bacchus Date: Thu, 23 Jul 2026 23:16:12 -0600 Subject: [PATCH] feat(log): Use plain-text logging for all messages Convert all logging output to plain-text structured messages using new function. --- cfddns.sh | 179 +++++++++++++++++++++++++++--------------------------- 1 file changed, 88 insertions(+), 91 deletions(-) diff --git a/cfddns.sh b/cfddns.sh index 79e8cdf..7ce4f08 100644 --- a/cfddns.sh +++ b/cfddns.sh @@ -139,23 +139,24 @@ exitOK() { exit 0 } +# 1: cloudflare error object, 2: operation identifier listCFErrors() { # extract error codes and messages in separate variables, replace newlines with underscores codes="$(printf "%s" "$1" | jq -r '.errors | .[] | .code' | tr '\n' '_')" messages="$(printf "%s" "$1" | jq -r '.errors | .[] | .message' | tr '\n' '_')" - # iterate codes and messages and assemble into coherent messages in log + # iterate codes and messages, assemble into a coherent log message while [ -n "$codes" ] && [ -n "$messages" ]; do - # get first code and message in respective sets + # get the first code and message in respective sets code="${codes%%_*}" message="${messages%%_*}" - # update codes and messages sets by removing first item in each set + # update sets of codes and messages by removing the first item (above) in each set codes="${codes#*_}" messages="${messages#*_}" # output to log - writeLog cf "$message" "$code" + writePlainTextLog "${message}" "err" "fail" "$2" "$code" done } @@ -415,18 +416,24 @@ done ### pre-flight checks if ! command -v curl >/dev/null; then printf "\n%sThis script requires 'curl' be installed and accessible. Exiting.%s\n\n" "$err" "$norm" + writePlainTextLog "'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 'jq' be installed and accessible. Exiting.%s\n\n" "$err" "$norm" + writePlainTextLog "'jq' must be installed and accessible" "fatal" "fail" "preexec" 2 exit 2 fi -[ -z "$dnsRecords" ] && badParam errMsg "You must specify at least one DNS record to update. Exiting." -# verify credentials file exists and is not empty (default check) -if [ ! -f "$accountFile" ] || [ ! -s "$accountFile" ]; then - badParam errMsg "Cannot find Cloudflare credentials file (${accountFile}). Exiting." +if [ -z "$dnsRecords" ]; then + badParam errMsg "You must specify at least one DNS record to update. Exiting." + writePlainTextLog "At least one DNS record to update must be specified" "fatal" "fail" "params" 1 fi -if ! command -v logger >/dev/null 2>&1; then +# verify the credentials file exists and is not empty (default check) +if [ ! -f "$accountFile" ] || [ ! -s "$accountFile" ]; then + badParam errMsg "Cannot find the Cloudflare credentials file (${accountFile}). Exiting." + writePlainTextLog "Cannot find the specified Cloudflare credentials file (${accountFile})" "fatal" "fail" "params" 1 +fi +if [ "$logToJournal" -eq 1 ] && ! command -v logger >/dev/null 2>&1; then printf "\n%sThis script requires 'logger' be installed to write entries to your journaling system. Exiting.%s\n\n" "$err" "$norm" exit 2 fi @@ -443,56 +450,51 @@ if [ "$colourizeLogFile" -eq 0 ]; then 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" - printf "%s[%s] -- Cloudflare DDNS update-script: starting --%s\n" "$ok" "$(stamp)" "$norm" - printf "Parameters:\n" - printf "script path: %s\n" "$scriptPath/$scriptName" - printf "credentials file: %s\n" "$accountFile" +if [ "$ip4" -eq 1 ]; then + writePlainTextLog "mode: IPv4" "debug" "ok" "startup" +elif [ "$ip6" -eq 1 ]; then + writePlainTextLog "mode: IPv6" "debug" "ok" "startup" +fi +# detect and report IP address +if [ -z "$ipAddress" ]; then + # detect public ip address if [ "$ip4" -eq 1 ]; then - printf "mode: IP4\n" - elif [ "$ip6" -eq 1 ]; then - printf "mode: IP6\n" + if ! ipAddress="$(curl -s $ip4DetectionSvc)"; then + writePlainTextLog \ + "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 - - # detect and report IP address - if [ -z "$ipAddress" ]; then - # detect public ip address - if [ "$ip4" -eq 1 ]; then - if ! ipAddress="$(curl -s $ip4DetectionSvc)"; then - printf "ddns ip address:%s ERROR%s\n" "$err" "$norm" - exitError 10 - fi + if [ "$ip6" -eq 1 ]; then + if ! ipAddress="$(curl -s $ip6DetectionSvc)"; then + writePlainTextLog \ + "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 - if [ "$ip6" -eq 1 ]; then - if ! ipAddress="$(curl -s $ip6DetectionSvc)"; then - printf "ddns ip address:%s ERROR%s\n" "$err" "$norm" - exitError 10 - fi - fi - printf "ddns ip address (detected): %s\n" "$ipAddress" - else - printf "ddns ip address (supplied): %s\n" "$ipAddress" fi + writePlainTextLog "DDNS IP address (detected): $ipAddress" "info" "ok" "startup" +else + writePlainTextLog "DDNS IP address (supplied): $ipAddress" "info" "ok" "startup" +fi - # iterate DNS records to update - dnsRecordsToUpdate="$(printf '%s' "${dnsRecords}" | sed "s/${dnsSeparator}*$//")$dnsSeparator" - while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator" ]; do - record="${dnsRecordsToUpdate%%${dnsSeparator}*}" - dnsRecordsToUpdate="${dnsRecordsToUpdate#*${dnsSeparator}}" +# iterate DNS records to update +dnsRecordsToUpdate="$(printf '%s' "${dnsRecords}" | sed "s/${dnsSeparator}*$//")$dnsSeparator" +while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator" ]; do + record="${dnsRecordsToUpdate%%${dnsSeparator}*}" + dnsRecordsToUpdate="${dnsRecordsToUpdate#*${dnsSeparator}}" - if [ -z "$record" ]; then - continue - fi - printf "updating record: %s\n" "$record" - done - - printf "(end of parameter list)\n" -} >>"$logFile" + if [ -z "$record" ]; then + continue + fi + writePlainTextLog "DNS host record '${record}' queued for update" "info" "ok" "startup" +done ### read Cloudflare credentials -writeLog process "Reading Cloudflare credentials" case "$accountFile" in /*) # absolute path, use as-is @@ -506,14 +508,14 @@ case "$accountFile" in ;; esac if [ -z "$cfKey" ]; then - writeLog process-error - exitError 21 + writePlainTextLog "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 - writeLog process-error - exitError 22 + writePlainTextLog "Cloudflare zone id (cfZoneId) is either null or undefined; please check your Cloudflare credentials file" "err" "fail" "creds" 22 + exit 22 fi -writeLog process-done "DONE" +writePlainTextLog "Cloudflare credentials file read successfully" "debug" "ok" "creds" ### connect to Cloudflare and do what needs to be done! dnsRecordsToUpdate="$dnsRecords$dnsSeparator" @@ -531,7 +533,6 @@ while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator" if [ -z "$record" ]; then continue fi - writeLog process "Processing ${record}" # exit if curl/network error if ! cfLookup="$( @@ -539,43 +540,39 @@ while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator" -H "Authorization: Bearer ${cfKey}" \ -H "Content-Type: application/json" )"; then - writeLog process-error - exitError 3 + writePlainTextLog "Unable to connect to Cloudflare servers; please try again later." "err" "fail" "cflogin" 3 + exit 3 fi - # exit if API error - # exit here since API errors on GET request probably indicates authentication error which would affect all remaining operations - # no reason to continue processing other hosts and pile-up errors which might look like a DoS attempt + # an API error on this GET request likely indicates an authentication error that would affect all remaining operations + # no reason to continue processing other dns records and pile-up errors which might look like a DoS attempt cfSuccess="$(printf "%s" "$cfLookup" | jq -r '.success')" if [ "$cfSuccess" = "false" ]; then - writeLog process-error - listCFErrors "$cfLookup" - exitError 25 + listCFErrors "$cfLookup" "cflogin" + writePlainTextLog "Cloudflare API error; review any previously logged 'CF-ERR:' lines for details." "err" "fail" "cflogin" 25 + exit 25 fi resultCount="$(printf "%s" "$cfLookup" | jq '.result_info.count')" - # skip to next host if cannot find existing host record (this script *updates* only, does not create!) + # skip to the next host if an existing host record cannot be found (this script *updates* only, does not create!) if [ "$resultCount" = "0" ]; then - # warn if record of host not found - writeLog process-warning "NOT FOUND" - writeLog warn "Cannot find existing record to update for DNS entry: ${record}" + writePlainTextLog "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') - writeLog process-done "FOUND: IP = ${currentIpAddr}" + writePlainTextLog "The current IP address for '${record}' is ${currentIpAddr}" "debug" "ok" "ddns" - # skip to next hostname if record already up-to-date + # skip to next hostname if record already up to date if [ "$currentIpAddr" = "$ipAddress" ]; then - writeLog stamped "IP address for ${record} is already up-to-date" + writePlainTextLog "The IP address for '${record}' is already up to date" "info" "ok" "ddns" continue fi # update record - writeLog process "Updating IP address for ${record}" updateJSON="$(jq -n --arg key0 content --arg value0 "${ipAddress}" '{($key0):$value0}')" # exit if curl/network error @@ -585,19 +582,17 @@ while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator" -H "Content-Type: application/json" \ --data "${updateJSON}" )"; then - writeLog process-error - exitError 3 + writePlainTextLog "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 - writeLog process-done "DONE" - writeLog success "IP address for ${record} updated." + writePlainTextLog "The IP address for '${record}' successfully updated" "info" "ok" "ddns" else - writeLog process-error - listCFErrors "$cfResult" - writeLog err "Unable to update IP address for ${record}" + listCFErrors "$cfResult" "ddns" + writePlainTextLog "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)) @@ -606,22 +601,24 @@ done # exit if [ "$invalidDomainCount" -ne 0 ]; then - writeLog warning "${invalidDomainCount} invalid host(s) supplied for updating." + writePlainTextLog "${invalidDomainCount} invalid host(s) were supplied for updating" "warn" "warn" "ddns" fi if [ "$failedHostCount" -ne 0 ]; then - exitError 26 + writePlainTextLog \ + "${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 - exitOK + writePlainTextLog "${scriptName} completed successfully" "info" "ok" "script" fi ### exit return codes -# 0: normal exit, no errors -# 1: invalid or unknown parameter -# 2: cannot find or access required external program(s) -# 3: curl error (probably connection) -# 10: cannot auto-detect IP address -# 21: accountFile has a null or missing cfKey variable -# 22: accountFile has a null or missing cfZoneId variable -# 25: Cloudflare API error -# 26: one or more updates failed -# 99: unspecified error occurred +# 0: normal exit, no errors +# 1: invalid or unknown parameter +# 2: cannot find or access required external program(s) +# 3: curl error (probably connection) +# 10: cannot auto-detect IP address +# 21: accountFile has a null or missing cfKey variable +# 22: accountFile has a null or missing cfZoneId variable +# 25: Cloudflare API error +# 26: one or more updates failed +# 99: unspecified error occurred