feat(log): merge feat/structured-logging

Merge structured logging feature branch into main.

Closes 2
This commit is contained in:
2026-07-25 00:20:04 -06:00
11 changed files with 759 additions and 447 deletions
+22 -3
View File
@@ -55,6 +55,23 @@
*.bat text eol=crlf
*.cmd text eol=crlf
# web frontend stack -- force LF so SRI hashes are always correct
*.html text eol=lf
*.htm text eol=lf
*.css text eol=lf
*.min.css text eol=lf
*.js text eol=lf
*.min.js text eol=lf
*.php text eol=lf
# Visual Studio projects (Rider also)
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Serialisation
*.json text
*.toml text
@@ -76,6 +93,8 @@
# Exclude files from exporting
#
.gitattributes export-ignore
.gitignore export-ignore
.gitkeep export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.gitkeep export-ignore
.idea export-ignore
.vscode export-ignore
+34
View File
@@ -1 +1,35 @@
#
# JetBrains exclusions
#
riderModule.iml
/_ReSharper.Caches/
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
.idea/**/GitCommitMessageStorage.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# modules
.idea_modules/
# Editor-based Rest Client
.idea/httpRequests
# project-specific exclusions
*.log
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeveloperToolsToolWindowSettingsV1" lastSelectedContentNodeId="base64-encoder-decoder" pluginVersion="9.0.0">
<developerToolsConfigurations />
</component>
</project>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="com.itcodebox.notebooks.projectservice.ProjectUIState">
<option name="selectedNotebookId" value="1" />
<option name="selectedChapterId" value="1" />
<option name="selectedNoteId" value="1" />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RiderProjectSettingsUpdater">
<option name="singleClickDiffPreview" value="1" />
<option name="unhandledExceptionsIgnoreList" value="1" />
<option name="vcsConfiguration" value="3" />
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+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>
+487 -444
View File
File diff suppressed because it is too large Load Diff
+161
View File
@@ -0,0 +1,161 @@
#!/bin/sh
getTimeStamp() {
(date -u +"%Y-%m-%dT%H:%M:%SZ")
}
getHostname() {
(hostname -s)
}
lowercase() {
echo "$1" | tr '[:upper:]' '[:lower:]'
}
uppercase() {
echo "$1" | tr '[:lower:]' '[:upper:]'
}
standardizeLogLevels() {
LEVEL_TO_STANDARDIZE="$(lowercase "$1")"
case "$LEVEL_TO_STANDARDIZE" in
"critical" | "crit" | "fatal")
echo "CRIT"
;;
"error" | "err")
echo "ERR"
;;
"warning" | "warn")
echo "WARN"
;;
"information" | "info")
echo "INFO"
;;
"debug" | "verbose")
echo "DEBUG"
;;
*)
echo "INFO"
;;
esac
}
translateLogLevelsToJournalD() {
LEVEL_TO_TRANSLATE="$(standardizeLogLevels "$1")"
case "$LEVEL_TO_TRANSLATE" in
"CRIT")
echo 2
;;
"ERR")
echo 3
;;
"WARN")
echo 4
;;
"INFO")
echo 6
;;
"DEBUG")
echo 7
;;
*)
echo 6
esac
}
# 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
if [ -z "$1" ] || [ $# -lt 3 ]; then
return
fi
printf "%s %s %s[%s]: [%s] [%s] %s (%s:%s)\n" \
"$(getTimeStamp)" "$(getHostname)" "$LOG_PROGRAM_NAME" "$$" \
"$(standardizeLogLevels "$2")" "$(uppercase "$3")" "$1" "${4:-UNSPECIFIED}" "${5:-999}"
}
# $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
PROPS="$(
jq --null-input \
--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:-999}" \
'{"timestamp":$timestamp, "hostname":$hostname, "programName":$pName, "pid":$pid, "level":$level, "status":$status, "message":$message, "operation":$operation, "code":$code}' \
)"
echo "$PROPS" | jq "."
return
}
# $1: message, $2: level, $3: status, $4: operation, $5: code
writeJournalDLog() {
# not enough information to generate a meaningful log message
if [ -z "$1" ] || [ $# -lt 3 ]; then
return
fi
J_TIMESTAMP="$(getTimeStamp)"
J_PID="$$"
J_PRIO="$(translateLogLevelsToJournalD "$2")"
J_STAT="$(uppercase "$3")"
logger --journald <<end
SYSLOG_TIMESTAMP=${J_TIMESTAMP}
SYSLOG_FACILITY=3
SYSLOG_IDENTIFIER=${LOG_PROGRAM_NAME}
SYSLOG_PID=${J_PID}
PRIORITY=${J_PRIO}
STATUS=${J_STAT}
MESSAGE=$1
OPERATION=${4:-UNSPECIFIED}
ERRNO=${5:-999}
end
return
}
LOG_PROGRAM_NAME="cfddns"
if [ "$1" = "text" ]; then
writePlainTextLog # this will do nothing
writePlainTextLog "Testing plain-text logging output" # this will also do nothing
writePlainTextLog "Informational plain-text log message" "information" "ok" # info message
writePlainTextLog "Plain-text message forced to info level" "whoops" "ok" # level translated to default
writePlainTextLog "Warning! This is plain-text." "warning" "flagged" # warning level with 'flagged' status
writePlainTextLog "Error sample! This is plain-text." "err" "error" # error level message
writePlainTextLog "Unable to continue, critical failure. (This is plain-text)" "fatal" "exit" "startup" "2" # critical level error with operation and error code
elif [ "$1" = "json" ]; then
writeJsonLog # this will do nothing
writeJsonLog "This is a test message" # this will also do nothing
writeJsonLog "Informational JSON log message" "information" "ok" # info message
writeJsonLog "JSON message forced to info level" "whoops" "ok" # level translated to default
writeJsonLog "Warning! This is JSON." "warning" "flagged" # warning level with 'flagged' status
writeJsonLog "Error sample! This is JSON." "err" "error" # error level message
writeJsonLog "Unable to continue, critical failure. (This is JSON)" "fatal" "exit" "startup" "2" # critical level error with operation and error code
elif [ "$1" = "journal" ]; then
writeJournalDLog # this will do nothing
writeJournalDLog "This is a test message" # this will also do nothing
writeJournalDLog "Informational JOURNALD log message" "information" "ok" # info message
writeJournalDLog "JOURNALD message forced to info level" "whoops" "ok" # level translated to default
writeJournalDLog "Warning! This is a JOURNALD message." "warning" "flagged" # warning level with 'flagged' status
writeJournalDLog "Error sample! This is a JOURNALD." "err" "error" # error level message
writeJournalDLog "Unable to continue, critical failure. (This is JOURNALD message)" "fatal" "exit" "startup" "2" # critical level error with operation and error code
else
printf "\nPlease specify either 'text', 'json', or 'journal' after the script name. Exiting.\n\n"
exit 1
fi
exit 0