Skip to content

Task: Release v2.3.0 - Logging improvements q1-2026#69

Open
jonrandahl wants to merge 46 commits intomainfrom
spike/logging_improvements_q1-2026
Open

Task: Release v2.3.0 - Logging improvements q1-2026#69
jonrandahl wants to merge 46 commits intomainfrom
spike/logging_improvements_q1-2026

Conversation

@jonrandahl
Copy link
Contributor

@jonrandahl jonrandahl commented Feb 20, 2026

Release: json_rails_logger v2.3.0

Release Summary

Strengthens logging clarity and configurability with opt-in field control, HTTP status-derived levels, and improved JSON output ordering. Includes comprehensive test coverage improvements and unified local development workflows.

Primary consumers: All applications using json_rails_logger gem.

What's Changed

Logging & Observability

  • Made ignored log fields opt-in to reduce noise and support data minimisation
  • Derived log level from HTTP status and handled nil or unknown levels robustly
  • Reordered JSON output so timestamp and level appeared first for readability
  • Streamlined completion message format to emphasise controller/action context
  • Fixed request parameters mapping and normalised non-integer durations
  • Fixed query string handling to prevent errors when missing
  • Trimmed extraneous whitespace from log level field for consistency

Testing & Infrastructure

  • Added targeted tests for optional-field modes and nil severity handlers
  • Enabled coverage reporting with improved visibility across test suite
  • Introduced unified local hooks to lint, test and verify builds with clear, skippable conditions
  • Added YARD documentation workflow with versioned outputs for improved release documentation

Developer Experience & Tooling

  • Consolidated developer workflow targets for consistent build, lint, test and publish steps
  • Upgraded framework and libraries for compatibility with Rails 8.1.2
  • Removed outdated development dependency
  • Adopted Keep a Changelog format for improved release documentation

Version Update

  • Bumped version to 2.3.0 to reflect feature additions and tooling updates

Smoke Checks

  • Logging output correctly omits ignored fields when opt-in disabled
  • HTTP 4xx/5xx requests correctly derive error log levels
  • JSON output begins with timestamp and level fields in all scenarios
  • Nil or unknown HTTP status codes handled without raising errors
  • Request duration formats correctly as integer milliseconds
  • Unit tests pass with coverage >94%
  • Gem builds and publishes successfully

Checklist

  • Changelog updated
  • Tests passing locally
  • Coverage reporting enabled
  • Documentation generated locally and verified
  • Gem builds successfully

- Adopt a broad ignore template for Ruby and major OSes
- Exclude OS, editor and test artefacts from version control
- Prevent accidental commits of tokens and local configs
- Replace outdated minimal ignores to reduce repo noise
- coerce request_time to integer before splitting
- prevent errors when value arrives as string or float
- keep millisecond formatting stable in output
- expose request_params under accurate key name
- use blank? to safely check for empty query string
- include only when params present and query is empty
- include request path prominently in the message
- state controller and action in natural order
- improve readability of action/controller logs
- add level to the message schema and payload flow
- seed level from incoming severity for a default
- remove level from base payload to avoid duplication
- normalise level by HTTP status (info/warn/error)
- Expand task set: assets, help, checks, bundles, coverage, update, vars
- Standardise dependency install and test/lint via Bundler
- Auto-correct RuboCop offences safely to reduce noise
- Improve Ruby path resolution for better portability
- Add clearer output and AWS profile guidance for developers
- Add interactive dependency updates for JS and Ruby
- Keep publish default while making clean/build explicit
- Introduce maintenance group to track runtime deps with bundler
- Mirror runtime deps in Gemfile for tooling validation
- Enable 'bundle outdated --only-explicit' in update workflow
- Remove broken dev dependency on meta_request fork
- Add SimpleCov in dev/test to measure code coverage
- Bump json and refresh lockfile for consistency
- Adopt Rails 8.1.2 for bug fixes and stability
- Refresh transitive deps (Rack 3.2.5, Nokogiri 1.19)
- Pull in security patches and performance optimisations
- Update debugging tool to latest patch for bug fixes
- Support smoother debugging during logging work
- Update linter and AST parser to latest versions
- Improve support for current Ruby syntax and nodes
- Reduce false positives with refreshed lint rules
- Refresh Unicode width/emoji data for accuracy
- Improve editor LSP via language server updates
- Affect dev-only tooling; no runtime impact
- Use SimpleCov to collect code coverage during tests
- Exclude test files to keep coverage metrics accurate
- Make untested areas visible to guide upcoming work
- Adopt industry-standard format for clearer release notes
- Structure entries by Added/Changed to cut ambiguity
- Normalise headings and dates for quicker scanning
- Aid readability for devs and tooling that parses notes
- Replace ad hoc history with a structured, durable format
- Document automatic log level normalisation by status code
- Note inclusion of log level in required fields for consistency
- Update request completion message details for clarity
- Simplify payload build order to improve reliability
- Fix incorrect params key, time formatting, and query handling
- Add test coverage reporting via SimpleCov to track gaps
- Indicate a minor, backwards-compatible release
- Update dependency snapshot to match 2.3.0 release
- Ensure local development uses the latest logging changes
- Default to original level if status is missing or invalid
- Avoid downgrading to debug for non-HTTP or missing status
- Improve severity accuracy while retaining 5xx/4xx/1xx-3xx mappings
- Improve log readability by prioritising timestamp and level
- Ensure consistent key order across log entries
- Keep payload content unchanged; only field order resolved
- Collapse extra spaces in level for cleaner JSON output
- Prevent errors when severity is missing during format
- Set request id via thread-local storage to match runtime behaviour
- Assert against plain string id instead of a wrapped hash
- Ensure thread cleanup to avoid cross-test leakage
- Reduce verbosity by removing path and boilerplate text
- Standardise wording for easier scanning and parsing
- Capitalise controller name for consistent style
- Flag potential impact to parsers expecting old format
- Adjustments based on internal discussions
- Add configuration flag to include optional fields
- Merge optional data into structured logs when enabled
- Default to disabled to preserve existing behaviour
- Update tests to exercise the opt-in path
- Enable richer logs without breaking current consumers
- Assert optional fields are excluded by default for clean logs
- Verify inclusion of optional fields when explicitly enabled
- Ensure nil severity is handled safely without truncation errors
- Check `ts` and `level` appear first to keep output readable
- Confirm required fields stay consistent across configurations
- Add regression coverage using header-like messages
- Document optional fields toggle via initialiser (default off)
- Clarify payload order to prioritise timestamp and level
- Simplify completion message format with controller/action
- Note log level normalisation by HTTP status and required field
- Add test coverage notes incl. optional modes and SimpleCov
- Record fixes for nil severity, whitespace, params key, time, query
- Introduce shared utilities for branch, skipping, and colour
- Enforce RuboCop on staged Ruby files with auto-fix
- Re-add modified files and block commit on lint errors
- Run Rails tests post-commit; reset on failures to keep green
- Build gem on push and block if packaging fails
- Allow skipping via special branches or --no-verify
- Respect amend flows via reflog and parent process flags
- Document setup and usage for local hook path and permissions
- Ensure base class setup runs when constructing formatter
- Prevent missing setup that could break base behaviour
- Tidy stray whitespace for minor style consistency
@jonrandahl jonrandahl self-assigned this Feb 20, 2026
@jonrandahl jonrandahl requested a review from ajtucker February 20, 2026 10:05
@jonrandahl jonrandahl changed the title Spike: logging improvements q1-2026 Task: Release v2.3.0 - Logging improvements q1-2026 Feb 20, 2026
@jonrandahl jonrandahl requested review from joescottdave and removed request for ajtucker February 23, 2026 11:22
Copy link

@joescottdave joescottdave left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might need to convince me because I'm unsure about normalising the level here. I'm not sure that we should deprive individual applications of the ability to choose their own log levels.

The logger should be an honest broker of messages from the application and if we start down the road of side-effecting messages inside the logger it could eventually resemble a game of chinese whispers.

If this needs enforcing I think it should be at the application or developer process layer.

Comment on lines 329 to 344
def normalise_level(msg) # rubocop:disable Metrics/MethodLength
status = msg[:status] || msg['status']
msg.to_h do |k, v|
if k.to_s == 'level'
level = case status.to_i
when 500..599 then process_severity(Logger::ERROR)
when 400..499 then process_severity(Logger::WARN)
when 100..399 then process_severity(Logger::INFO)
# If the status code is not present or does not match any of the above ranges, keep the original level
else v
end
[:level, level]
else
[k, v]
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure about this. You could avoid it of course by not providing the status at all, but you're taking away a lot of room to choose what level certain statuses should be logged at on a per application basis, and it wouldn't be clear to the user of the interface that, that was going to happen.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is specific to logs we were receiving that were status: 5.. but the logs were set to INFO/DEBUG level, to ensure the two match.

You could avoid it of course by not providing the status at all, but you're taking away a lot of room to choose what level certain statuses should be logged at on a per application basis, and it wouldn't be clear to the user of the interface that, that was going to happen.

I'm not sure I agree with this though as the status code should denote the level of message?

Happy to discuss further though.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HTTP status (code) is a different thing to the severity level in a log, which is why we use separate fields in the log entry.

While it is often the case that a response of 5xx should be logged with "error" severity, we shouldn't really be deriving the severity level from the status.

The normal pattern is for the code that issues the log to explicitly state the severity, e.g. using logger.error, logger.info etc.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should trust that a formatter isn't going to interfere with the content of a message, and permit ourselves to make mistakes and fix them upstream

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Points taken, this was more of a falback to ensure the level of message was validated but I also agree that the consuming application should be responsible for the correction.

method and invocation removed.

- Apply pessimistic ranges to runtime logging deps
- Allow patches/minors, block breaking major upgrades
- Align runtime constraints across dependency managers
- Ensure support for Rails 8+ and stable logging behaviour
- Convert inline comments to YARD with params, returns, examples
- Clarify formatter behaviour, inputs, and error handling
- Standardise timestamp guidance and optional fields usage
- Help devs understand structured logging expectations
- Provide a make task to generate API docs with Yard
- Open the generated docs in a browser for quick review
- Improve developer experience and IDE autocompletion
- Reposition as a log formatter rather than a custom logger
- Streamline setup and testing via Makefile targets
- Add linting guidance and one-step check to catch issues early
- Document API docs generation and IDE hints using YARD
- Keep guidance provider-agnostic by removing Elasticsearch mention
- Use accurate terminology to reflect actual behaviour
- Align module comment wording with the intended role
- Reduce confusion for adopters and improve discoverability
- Keep duration in ms; only round request_time values
- Promote exception fields to always-on (not optional)
- Treat optional keys as string or symbol for robustness
- Improve completion message for namespaced controllers
- Move controller, db and params to optional by default
- Extend tests to cover edge cases and request scenarios
- Add specs for request id behaviour across environments
- Use X-Request-Id in production; Rails id in development
- Favour Rails id over header when both are present
- Verify thread-local value is cleared after each request
- Ensure cleanup occurs even if the app raises an error
- Confirm middleware returns the app response unchanged
- Save any existing Rails constant and restore after tests
- Avoid test pollution and flakiness when Rails is defined
- Harden teardown to safely remove or reinstate Rails
- Clarify naming to reflect fields are omitted by default
- Rename public flag to include_ignored_keys (from include_optional)
- Keep default false to preserve backward compatibility
- Remove Webpacker-specific debug level special-case
- test: update coverage for ignored-field inclusion modes
- docs: update README and changelog to new terminology
- Unify timing under request_time for consistent schema
- Normalise floats to integer milliseconds for stability
- Prevent duplicate "time taken" suffix in messages
- Improve stdlib compatibility by avoiding non-standard methods
- Rename helpers from duration to timing for clarity
- Update tests to cover new timing behaviour and edges
- Remove status-to-level mapping to avoid surprises
- Keep caller-set severity unchanged for all events
- Prevent misclassification of non-HTTP logs and custom flows
@jonrandahl jonrandahl requested a review from ajtucker February 25, 2026 14:35
- Automate docs build and deploy to keep documentation current
- Version output per release so multiple versions can coexist
- Allow manual runs and reuse from other workflows for flexibility
- Use Pages deploy with OIDC for safer, tokenless publishing
- Integrate into release pipeline after gem publication
- Document YARD workflow with versioned outputs to aid releases
- Note Railties 8.1.2 upgrade to reflect framework compatibility
- Improve line wrapping for clearer, more scannable entries
Copy link

@ajtucker ajtucker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good stuff! I'll leave this to others to approve as I haven't had chance to checkout and run things.

There are quite a few changes and I think required versions of dependencies imply this will now require Rails >= 8? As such I wonder whether it should be a major version bump?

# outdated --only-explicit` from the `make update` target.
spec.add_dependency 'json', '~> 2.3'
spec.add_dependency 'lograge', '~> 0.14'
spec.add_dependency 'railties', '~> 8.0'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that this Gem can no longer be used on Rails < 8?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Railties dependency was already on v8 but these version constraints are specific to resolve the following warning:

gem build json_rails_logger.gemspec
WARNING: open-ended dependency on json (>= 0) is not recommended
use a bounded requirement, such as "> x.y"
WARNING: open-ended dependency on lograge (>= 0) is not recommended
use a bounded requirement, such as "
> x.y"
WARNING: open-ended dependency on railties (>= 0) is not recommended
use a bounded requirement, such as "~> x.y"
WARNING: See https://guides.rubygems.org/specification-reference/ for help
Successfully built RubyGem
Name: json_rails_logger
Version: 2.3.0
File: json_rails_logger-2.3.0.gem
json_rails_logger-2.3.0.gem

CHANGELOG.md Outdated
parameter (defaults to `false` for backward compatibility).
- Implemented test coverage for ignored field inclusion modes (enabled/disabled
configurations).
- Implemented automatic log level normalisation based on HTTP status codes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did this get removed? It is also mentioned in the PR summary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, removed!

with:
path: doc/${{ inputs.version }}/

- name: Deploy to GitHub Pages

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this new? By uploading to GH pages, the docs become public AFAIK so we should at least mention this, e.g. CHANGELOG, README etc. Will the docs be at https://epimorphics.github.io/json-ruby-logger?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in person I've set the workflow_call to not run from the publish target as well as commented out the manual workflow_dispatch trigger.

I've also updated the README with clearer content describing the new features and functionality contained in this PR.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably discuss in this morning's meeting, but if we're not intending to use this yet, we should probably take this out of this PR until we have a plan for it

- Remove unimplemented log-level normalisation reference
- Prevent misleading release notes for upcoming release
- Align documentation with current logging behaviour
- Split gem publish into a dedicated job that needs build
- Surface build version as a job output for downstream steps
- Route version via job outputs to the reusable docs workflow
- Disable manual docs trigger; keep docs publish gated for now
- Prepare for later docs enablement pending setup discussion
- Expand overview to explain structured JSON logging goals
- Describe request context handling and consistency benefits
- Document YARD API generation and coverage for customisation
- Add note on including ignored keys via configuration option
- Improve onboarding and reduce guesswork integrating the gem
- Remove outdated guidance on including ignored keys
- Prevent confusion around configuration no longer recommended
- Keep docs focused on current logging behaviour
@jonrandahl jonrandahl requested a review from ajtucker February 26, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants