discourse/spec/lib/discourse_logstash_logger_spec.rb
Alan Guo Xiang Tan 2d59795e28
DEV: Log sidekiq job opts as string instead of hash (#28012)
This ensures that elasticsearch doesn't parse it as an object. There are
too many combination of job opts so we don't want elasticsearch to be
parsing and indexing this field as an object.
2024-07-23 06:27:43 +08:00

210 lines
6.9 KiB
Ruby

# frozen_string_literal: true
RSpec.describe DiscourseLogstashLogger do
let(:lograge_logstash_formatter_formatted_message) do
"{\"method\":\"GET\",\"path\":\"/\",\"format\":\"html\",\"controller\":\"ListController\",\"action\":\"latest\",\"status\":200,\"allocations\":242307,\"duration\":178.2,\"view\":78.36,\"db\":0.0,\"params\":\"\",\"ip\":\"127.0.0.1\",\"username\":null,\"@timestamp\":\"2024-07-01T07:51:11.283Z\",\"@version\":\"1\",\"message\":\"[200] GET / (ListController#latest)\"}"
end
let(:output) { StringIO.new }
let(:logger) { described_class.logger(logdev: output, type: "test") }
describe "#add" do
it "logs a JSON string with the right fields" do
logger.add(Logger::INFO, lograge_logstash_formatter_formatted_message)
output.rewind
expect(output.read.chomp).to eq(
{
"message" => "[200] GET / (ListController#latest)",
"severity" => 1,
"severity_name" => "INFO",
"pid" => described_class::PROCESS_PID,
"type" => "test",
"host" => described_class::HOST,
"git_version" => described_class::GIT_VERSION,
"method" => "GET",
"path" => "/",
"format" => "html",
"controller" => "ListController",
"action" => "latest",
"status" => 200,
"allocations" => 242_307,
"duration" => 178.2,
"view" => 78.36,
"db" => 0.0,
"params" => "",
"ip" => "127.0.0.1",
"username" => nil,
"@timestamp" => "2024-07-01T07:51:11.283Z",
"@version" => "1",
}.to_json,
)
end
it "accepts an error object as the message" do
logger = described_class.logger(logdev: output, type: "test")
logger.add(Logger::ERROR, StandardError.new("error message"))
output.rewind
parsed = JSON.parse(output.read.chomp)
expect(parsed["message"]).to eq("error message")
end
context "when `progname` is `sidekiq-exception`" do
it "logs a JSON string with the `exception.class`, `exception.message`, `job.class`, `job.opts` and `job.problem_db` fields" do
logger = described_class.logger(logdev: output, type: "test")
logger.add_with_opts(
Logger::ERROR,
"Job exception: some job error message",
"sidekiq-exception",
exception_class: "Some::StandardError",
exception_message: "some job error message",
context: {
opts: {
user_id: 1,
},
problem_db: "some_db",
job: "SomeJob",
},
)
output.rewind
parsed = JSON.parse(output.read.chomp)
expect(parsed["exception.class"]).to eq("Some::StandardError")
expect(parsed["exception.message"]).to eq("some job error message")
expect(parsed["job.class"]).to eq("SomeJob")
expect(parsed["job.opts"]).to eq("{\"user_id\"=>1}")
expect(parsed["job.problem_db"]).to eq("some_db")
end
end
context "when `progname` is `web-exception`" do
it "logs a JSON string with the `exception.class` and `exception.message` fields" do
logger = described_class.logger(logdev: output, type: "test")
logger.add(
Logger::ERROR,
"Some::StandardError (this is a normal message)\ntest",
"web-exception",
)
output.rewind
parsed = JSON.parse(output.read.chomp)
expect(parsed["exception.class"]).to eq("Some::StandardError")
expect(parsed["exception.message"]).to eq("this is a normal message")
end
it "logs a JSON string with the `exception_class` and `exception_message` fields when the exception message contains newlines" do
logger = described_class.logger(logdev: output, type: "test")
logger.add(
Logger::ERROR,
"Some::StandardError (\n\nsome error message\n\nsomething else\n\n)\ntest",
"web-exception",
)
output.rewind
parsed = JSON.parse(output.read.chomp)
expect(parsed["exception.class"]).to eq("Some::StandardError")
expect(parsed["exception.message"]).to eq("some error message\n\nsomething else")
end
described_class::ALLOWED_HEADERS_FROM_ENV.each do |header|
it "includes `#{header}` from `env` keyword argument in the logged JSON string" do
logger.add(
Logger::ERROR,
lograge_logstash_formatter_formatted_message,
"web-exception",
env: {
header => "header",
},
)
output.rewind
parsed = JSON.parse(output.read.chomp)
expect(parsed["request.headers.#{header.downcase}"]).to eq("header")
end
end
it "does not include keys from `env` keyword argument in the logged JSOn string which are not in the allow list" do
logger.add(
Logger::ERROR,
lograge_logstash_formatter_formatted_message,
"web-exception",
env: {
"SOME_RANDOM_HEADER" => "header",
},
)
output.rewind
parsed = JSON.parse(output.read.chomp)
expect(parsed).not_to have_key("request.headers.some_random_header")
end
end
it "logs a JSON string with the right fields when `customize_event` attribute is set" do
logger =
described_class.logger(
logdev: output,
type: "test",
customize_event: ->(event) { event["custom"] = "custom" },
)
logger.add(Logger::INFO, lograge_logstash_formatter_formatted_message)
output.rewind
parsed = JSON.parse(output.read.chomp)
expect(parsed["custom"]).to eq("custom")
end
it "does not log a JSON string with the `backtrace` field when severity is less than `Logger::WARN`" do
logger.add(
Logger::INFO,
lograge_logstash_formatter_formatted_message,
nil,
backtrace: "backtrace",
)
output.rewind
parsed = JSON.parse(output.read.chomp)
expect(parsed).not_to have_key("backtrace")
end
it "logs a JSON string with the `backtrace` field when severity is at least `Logger::WARN`" do
logger.add(
Logger::ERROR,
lograge_logstash_formatter_formatted_message,
nil,
backtrace: "backtrace",
)
output.rewind
parsed = JSON.parse(output.read.chomp)
expect(parsed["backtrace"]).to eq("backtrace")
end
it "does not log the event if message matches a pattern configured by `Logster.store.ignore`" do
original_logster_store_ignore = Logster.store.ignore
Logster.store.ignore = [/^Some::StandardError/]
logger.add(
Logger::ERROR,
"Some::StandardError (this is a normal message)\ntest",
"web-exception",
)
output.rewind
expect(output.read.chomp).to be_empty
ensure
Logster.store.ignore = original_logster_store_ignore
end
end
end