-
Notifications
You must be signed in to change notification settings - Fork 3
Feat/adding optional journal logging #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
fab331c
e341451
89852e3
51d8901
7421da5
7a58468
71e856a
68a0eb4
1bdf64d
b59cc27
739f154
e472347
be35c7d
677c7de
87f2399
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -40,9 +40,9 @@ Comprehensive guide to the bash-logger test suite, including how to run tests, w | |||||||||||||||||
|
|
||||||||||||||||||
| ## Overview | ||||||||||||||||||
|
|
||||||||||||||||||
| The bash-logger project includes a comprehensive test suite with 103 tests across 6 test | ||||||||||||||||||
| suites, validating all functionality of the logging module. The test framework is built | ||||||||||||||||||
| in pure Bash and designed to be: | ||||||||||||||||||
| The bash-logger project includes a comprehensive test suite with 23 runnable test suites | ||||||||||||||||||
| and 466 total tests (snapshot from `bash tests/run_tests.sh` on 2026-03-11). | ||||||||||||||||||
| The test framework is built in pure Bash and designed to be: | ||||||||||||||||||
GingerGraham marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||
|
|
||||||||||||||||||
| * **Self-contained**: No external test frameworks required | ||||||||||||||||||
| * **CI-friendly**: Clear exit codes and non-interactive | ||||||||||||||||||
|
|
@@ -124,12 +124,29 @@ cd tests | |||||||||||||||||
|
|
||||||||||||||||||
| Available test suites: | ||||||||||||||||||
|
|
||||||||||||||||||
| * `test_ansi_injection` - ANSI escape sanitization and related security tests | ||||||||||||||||||
| * `test_concurrent_access` - Concurrency and parallel logging behavior | ||||||||||||||||||
| * `test_config` - Configuration file parsing and behavior | ||||||||||||||||||
| * `test_config_security` - Security hardening for configuration input | ||||||||||||||||||
| * `test_edge_cases` - Boundary and unusual input handling | ||||||||||||||||||
| * `test_environment_security` - Environment-based security checks | ||||||||||||||||||
| * `test_error_conditions` - Error handling and defensive behavior | ||||||||||||||||||
| * `test_format` - Message format templates and formatting behavior | ||||||||||||||||||
| * `test_fuzzing` - Fuzz-style robustness checks | ||||||||||||||||||
| * `test_initialization` - Logger initialization behavior | ||||||||||||||||||
| * `test_install` - Installation and setup scripts | ||||||||||||||||||
| * `test_journal_logging` - Journal-specific behavior and forced journal logging | ||||||||||||||||||
| * `test_junit_output` - JUnit XML report generation | ||||||||||||||||||
| * `test_log_levels` - Log level functionality | ||||||||||||||||||
| * `test_initialization` - Logger initialization | ||||||||||||||||||
| * `test_output` - Output routing and formatting | ||||||||||||||||||
| * `test_format` - Message format templates | ||||||||||||||||||
| * `test_config` - Configuration file parsing | ||||||||||||||||||
| * `test_mixed_sanitization_modes` - Combined sanitization mode behavior | ||||||||||||||||||
| * `test_output` - Output routing and stream behavior | ||||||||||||||||||
| * `test_path_traversal` - Path traversal protections | ||||||||||||||||||
| * `test_resource_limits` - Resource and size limit behavior | ||||||||||||||||||
| * `test_runtime_config` - Runtime configuration changes | ||||||||||||||||||
| * `test_script_name_sanitization` - Script and tag name sanitization | ||||||||||||||||||
| * `test_sensitive_data` - Sensitive data handling protections | ||||||||||||||||||
| * `test_toctou_protection` - TOCTOU/race-condition protections | ||||||||||||||||||
| * `test_unsafe_newlines` - Unsafe newline mode behavior | ||||||||||||||||||
GingerGraham marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
|
||||||||||||||||||
| ### Understanding Test Output | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -151,9 +168,10 @@ Running test_log_levels... | |||||||||||||||||
| Test Summary | ||||||||||||||||||
| ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||||||||||||||||||
|
|
||||||||||||||||||
| Total Tests: 103 | ||||||||||||||||||
| Passed: 103 | ||||||||||||||||||
| Total Tests: 466 | ||||||||||||||||||
| Passed: 461 | ||||||||||||||||||
| Failed: 0 | ||||||||||||||||||
| Skipped: 5 | ||||||||||||||||||
|
Comment on lines
+171
to
+174
|
||||||||||||||||||
| Total Tests: 466 | |
| Passed: 461 | |
| Failed: 0 | |
| Skipped: 5 | |
| Total Tests: <total> | |
| Passed: <passed> | |
| Failed: <failed> | |
| Skipped: <skipped> |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |||||||||||||
| # - log_warn, log_error, log_critical | ||||||||||||||
| # - log_alert, log_emergency, log_fatal | ||||||||||||||
| # - log_init, log_sensitive : Special purpose logging | ||||||||||||||
| # - log_to_journal <level> <message> : Force a single message to the journal | ||||||||||||||
| # | ||||||||||||||
| # Runtime Configuration: | ||||||||||||||
| # - set_log_level <level> : Change log level dynamically | ||||||||||||||
|
|
@@ -227,6 +228,11 @@ _should_use_stderr() { | |||||||||||||
| if ! readonly -p 2>/dev/null | grep -q "declare -[^ ]*r[^ ]* LOGGER_PATH="; then | ||||||||||||||
| LOGGER_PATH="" | ||||||||||||||
| fi | ||||||||||||||
| # Internal flag: set to "true" by _find_and_validate_logger on every exit path | ||||||||||||||
| # (success and failure alike). Allows log_to_journal to skip discovery when | ||||||||||||||
| # LOGGER_PATH is empty due to a failed/untrusted lookup rather than no lookup at all. | ||||||||||||||
| # Not readonly — resetting to "false" on re-source is correct (new context must re-validate). | ||||||||||||||
| _LOGGER_DISCOVERY_DONE="false" | ||||||||||||||
GingerGraham marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
|
||||||||||||||
| # Find and validate the logger command to prevent PATH manipulation attacks | ||||||||||||||
| # This function finds the logger executable and validates it's in a safe system location | ||||||||||||||
|
|
@@ -238,6 +244,7 @@ _find_and_validate_logger() { | |||||||||||||
|
|
||||||||||||||
| if [[ -z "$logger_candidate" ]]; then | ||||||||||||||
| USE_JOURNAL="false" | ||||||||||||||
| _LOGGER_DISCOVERY_DONE="true" | ||||||||||||||
| return 1 | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -254,17 +261,20 @@ _find_and_validate_logger() { | |||||||||||||
| # This preserves immutability while still allowing repeat availability checks. | ||||||||||||||
| if readonly -p 2>/dev/null | grep -q "declare -[^ ]*r[^ ]* LOGGER_PATH="; then | ||||||||||||||
| if [[ "$LOGGER_PATH" == "$logger_candidate" ]]; then | ||||||||||||||
| _LOGGER_DISCOVERY_DONE="true" | ||||||||||||||
| return 0 | ||||||||||||||
| fi | ||||||||||||||
| echo "Warning: logger path changed after validation: $logger_candidate" >&2 | ||||||||||||||
| echo " Locked logger path is: $LOGGER_PATH" >&2 | ||||||||||||||
| echo " Journal logging disabled for security" >&2 | ||||||||||||||
| USE_JOURNAL="false" | ||||||||||||||
| _LOGGER_DISCOVERY_DONE="true" | ||||||||||||||
| return 1 | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| LOGGER_PATH="$logger_candidate" | ||||||||||||||
| readonly LOGGER_PATH | ||||||||||||||
| _LOGGER_DISCOVERY_DONE="true" | ||||||||||||||
| return 0 | ||||||||||||||
| ;; | ||||||||||||||
| *) | ||||||||||||||
|
|
@@ -273,6 +283,7 @@ _find_and_validate_logger() { | |||||||||||||
| echo " Expected: /bin, /usr/bin, /usr/local/bin, /sbin, or /usr/sbin" >&2 | ||||||||||||||
| echo " Journal logging disabled for security" >&2 | ||||||||||||||
| USE_JOURNAL="false" | ||||||||||||||
| _LOGGER_DISCOVERY_DONE="true" | ||||||||||||||
| return 1 | ||||||||||||||
| ;; | ||||||||||||||
| esac | ||||||||||||||
|
|
@@ -1590,6 +1601,7 @@ _log_message() { | |||||||||||||
| local message="$3" | ||||||||||||||
| local skip_file="${4:-false}" | ||||||||||||||
| local skip_journal="${5:-false}" | ||||||||||||||
| local force_journal="${6:-false}" | ||||||||||||||
|
|
||||||||||||||
| # Skip logging if message level is more verbose than current log level | ||||||||||||||
| # With syslog-style levels, HIGHER values are LESS severe (more verbose) | ||||||||||||||
|
|
@@ -1629,9 +1641,9 @@ _log_message() { | |||||||||||||
| } | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| # If journal logging is enabled and logger path is already validated, log to the system journal | ||||||||||||||
| # Skip journal logging if skip_journal is true | ||||||||||||||
| if [[ "$USE_JOURNAL" == "true" && "$skip_journal" != "true" ]]; then | ||||||||||||||
| # If journal logging is enabled or force_journal is true, log to the system journal. | ||||||||||||||
| # skip_journal takes precedence — it overrides force_journal when true. | ||||||||||||||
| if [[ ( "$USE_JOURNAL" == "true" || "$force_journal" == "true" ) && "$skip_journal" != "true" ]]; then | ||||||||||||||
| # Map our log level to syslog priority | ||||||||||||||
| local syslog_priority | ||||||||||||||
| syslog_priority=$(_get_syslog_priority "$level_value") | ||||||||||||||
|
|
@@ -1642,7 +1654,14 @@ _log_message() { | |||||||||||||
| journal_message=$(_truncate_log_message "$sanitized_message" "$LOG_MAX_JOURNAL_LENGTH") | ||||||||||||||
| local plain_message | ||||||||||||||
| plain_message=$(_strip_ansi_codes "$journal_message") | ||||||||||||||
| _write_to_journal "$syslog_priority" "${JOURNAL_TAG:-$SCRIPT_NAME}" "$plain_message" | ||||||||||||||
|
|
||||||||||||||
| # Pass force_when_disabled=true when force_journal=true and USE_JOURNAL=false so | ||||||||||||||
| # _write_to_journal does not short-circuit the write. | ||||||||||||||
| local write_forced="false" | ||||||||||||||
| if [[ "$force_journal" == "true" && "$USE_JOURNAL" != "true" ]]; then | ||||||||||||||
| write_forced="true" | ||||||||||||||
| fi | ||||||||||||||
| _write_to_journal "$syslog_priority" "${JOURNAL_TAG:-$SCRIPT_NAME}" "$plain_message" "$write_forced" | ||||||||||||||
| fi | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -1693,6 +1712,70 @@ log_sensitive() { | |||||||||||||
| _log_message "SENSITIVE" $LOG_LEVEL_INFO "$1" "true" "true" | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| # Log a single message directly to the system journal, regardless of USE_JOURNAL state. | ||||||||||||||
| # Respects the current log level, sanitisation, and truncation rules. | ||||||||||||||
GingerGraham marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||
| # If the logger command is not available, emits a warning to stderr. | ||||||||||||||
| # | ||||||||||||||
| # Usage: log_to_journal LEVEL MESSAGE | ||||||||||||||
| # | ||||||||||||||
| # Parameters: | ||||||||||||||
| # LEVEL - Log level name (DEBUG, INFO, NOTICE, WARN, ERROR, CRITICAL, ALERT, EMERGENCY) | ||||||||||||||
| # MESSAGE - The message to log | ||||||||||||||
| # | ||||||||||||||
| # Returns: | ||||||||||||||
| # 0 - Success | ||||||||||||||
| # 1 - Invalid level or logger not available | ||||||||||||||
| log_to_journal() { | ||||||||||||||
| if [[ $# -ne 2 ]]; then | ||||||||||||||
| echo "Usage: log_to_journal LEVEL MESSAGE" >&2 | ||||||||||||||
| return 1 | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| local level_name="$1" | ||||||||||||||
| local message="$2" | ||||||||||||||
GingerGraham marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||
|
|
||||||||||||||
| # Validate and normalise the level name to the canonical form used by _log_message | ||||||||||||||
| local canonical_level | ||||||||||||||
| case "${level_name^^}" in | ||||||||||||||
| DEBUG) canonical_level="DEBUG" ;; | ||||||||||||||
| INFO) canonical_level="INFO" ;; | ||||||||||||||
| NOTICE) canonical_level="NOTICE" ;; | ||||||||||||||
| WARN|WARNING) canonical_level="WARN" ;; | ||||||||||||||
| ERROR|ERR) canonical_level="ERROR" ;; | ||||||||||||||
| CRITICAL|CRIT) canonical_level="CRITICAL" ;; | ||||||||||||||
| ALERT) canonical_level="ALERT" ;; | ||||||||||||||
| EMERGENCY|EMERG|FATAL) canonical_level="EMERGENCY" ;; | ||||||||||||||
| [0-7]) | ||||||||||||||
| # Numeric syslog level — resolve to its canonical name | ||||||||||||||
| canonical_level=$(_get_log_level_name "${level_name}") | ||||||||||||||
| ;; | ||||||||||||||
| *) | ||||||||||||||
| echo "Error: log_to_journal: unrecognised level '$level_name'" >&2 | ||||||||||||||
| echo " Valid levels: DEBUG, INFO, NOTICE, WARN, ERROR, CRITICAL, ALERT, EMERGENCY (or 0-7)" >&2 | ||||||||||||||
| return 1 | ||||||||||||||
| ;; | ||||||||||||||
| esac | ||||||||||||||
|
|
||||||||||||||
| local level_value | ||||||||||||||
| level_value=$(_get_log_level_value "$canonical_level") | ||||||||||||||
|
|
||||||||||||||
| # Fast-path: if discovery has already run (successfully or not), skip it. | ||||||||||||||
| # _LOGGER_DISCOVERY_DONE is set on every exit path of _find_and_validate_logger, | ||||||||||||||
| # so an empty LOGGER_PATH with the flag set means logger is absent or untrusted — | ||||||||||||||
| # no point repeating command -v, symlink resolution, and path validation. | ||||||||||||||
| if [[ "$_LOGGER_DISCOVERY_DONE" != "true" ]]; then | ||||||||||||||
| check_logger_available | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| # Abort before any writes if logger is still unavailable after attempted discovery. | ||||||||||||||
| if [[ -z "$LOGGER_PATH" || ! -x "$LOGGER_PATH" ]]; then | ||||||||||||||
| echo "WARNING: log_to_journal called but logger command is not available" >&2 | ||||||||||||||
GingerGraham marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
GingerGraham marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
GingerGraham marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||
| return 1 | ||||||||||||||
| fi | ||||||||||||||
GingerGraham marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+1777
to
+1786
|
||||||||||||||
|
|
||||||||||||||
| _log_message "$canonical_level" "$level_value" "$message" "false" "false" "true" | ||||||||||||||
|
||||||||||||||
| _log_message "$canonical_level" "$level_value" "$message" "false" "false" "true" | |
| if ! _log_message "$canonical_level" "$level_value" "$message" "false" "false" "true"; then | |
| return 1 | |
| fi | |
| return 0 |
Uh oh!
There was an error while loading. Please reload this page.