Filter out Bandit HTTP/0.9 errors from port scanners

Added log filter to suppress noisy errors from:
- HTTP/0.9 requests (obsolete protocol from 1991)
- Invalid HTTP version errors from port scanners and bots
- Malformed HTTP requests from automated tools

These errors are expected on public-facing servers and don't indicate
application problems. The filter uses Elixir's logger handler filters
to suppress them at the logging layer.

Resolves sporadic 'Bandit.HTTPError: Invalid HTTP version: {0, 9}' errors
in production logs.
This commit is contained in:
Graham McIntire 2026-01-15 10:00:53 -06:00
parent c9e4e8d105
commit 0a6f30299b
No known key found for this signature in database
2 changed files with 56 additions and 0 deletions

View file

@ -22,6 +22,14 @@ config :logger, :default_formatter,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
# Filter out noisy errors from port scanners and bots
config :logger, :default_handler,
filters: [
# Suppress HTTP/0.9 and other invalid protocol errors from Bandit
# These are typically from port scanners and automated bots
bandit_invalid_http: {&Towerops.LogFilter.filter_bandit_errors/2, []}
]
# Register protobuf MIME type for agent API
config :mime, :types, %{
"application/x-protobuf" => ["protobuf"]

View file

@ -0,0 +1,48 @@
defmodule Towerops.LogFilter do
@moduledoc """
Filters out noisy log messages from external sources like port scanners and bots.
"""
require Logger
@doc """
Filters out Bandit HTTP errors from invalid requests.
This suppresses errors from:
- HTTP/0.9 requests (very old protocol, typically port scanners)
- Malformed HTTP requests from bots
- Invalid protocol versions
These errors are expected when running a public-facing server and don't
indicate application problems.
"""
def filter_bandit_errors(log_event, _opts) do
case log_event do
# Filter Bandit.HTTPError with Invalid HTTP version
%{level: :error, msg: {:string, msg}} when is_binary(msg) ->
filter_string_message(msg)
# Filter other Bandit connection errors
%{level: :error, msg: {:report, %{label: {:error_logger, :error_report}, report: report}}} ->
filter_error_report(report)
# Don't filter anything else
_ ->
:ignore
end
end
defp filter_string_message(msg) do
if bandit_invalid_http_error?(msg), do: :stop, else: :ignore
end
defp filter_error_report(message: msg) when is_binary(msg) do
if String.contains?(msg, "Bandit.HTTPError"), do: :stop, else: :ignore
end
defp filter_error_report(_), do: :ignore
defp bandit_invalid_http_error?(msg) do
String.contains?(msg, "Bandit.HTTPError") and String.contains?(msg, "Invalid HTTP version")
end
end