Skip to content

Commit d822236

Browse files
do not assume event field values as mutable and code cleanups and proper timestamp handling
do not assume event field values as mutable and code cleanups and proper timestamp handling missing encoding header remove unnecessary inject_field specs cleanups
1 parent b78bd89 commit d822236

File tree

2 files changed

+90
-58
lines changed

2 files changed

+90
-58
lines changed

lib/logstash/filters/json.rb

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -46,54 +46,64 @@ class LogStash::Filters::Json < LogStash::Filters::Base
4646
# NOTE: if the `target` field already exists, it will be overwritten!
4747
config :target, :validate => :string
4848

49-
public
49+
JSONPARSEFAILURE_TAG = "_jsonparsefailure"
50+
5051
def register
5152
# Nothing to do here
52-
end # def register
53+
end
5354

54-
public
5555
def filter(event)
56-
57-
58-
@logger.debug("Running json filter", :event => event)
59-
60-
return unless event.include?(@source)
61-
62-
# TODO(colin) this field merging stuff below should be handled in Event.
56+
@logger.debug? && @logger.debug("Running json filter", :event => event)
6357

6458
source = event[@source]
59+
return unless source
6560

6661
begin
6762
parsed = LogStash::Json.load(source)
68-
# If your parsed JSON is an array, we can't merge, so you must specify a
69-
# destination to store the JSON, so you will get an exception about
70-
if parsed.kind_of?(Array) && @target.nil?
71-
raise('Parsed JSON arrays must have a destination in the configuration')
72-
elsif @target.nil?
73-
event.to_hash.merge! parsed
74-
else
75-
event[@target] = parsed
63+
rescue => e
64+
event.tag(JSONPARSEFAILURE_TAG)
65+
@logger.warn("Error parsing json", :source => @source, :raw => source, :exception => e)
66+
return
67+
end
68+
69+
if @target
70+
event[@target] = parsed
71+
else
72+
unless parsed.is_a?(Hash)
73+
event.tag(JSONPARSEFAILURE_TAG)
74+
@logger.warn("Parsed JSON object/hash requires a target configuration option", :source => @source, :raw => source)
75+
return
7676
end
7777

78-
# If no target, we target the root of the event object. This can allow
79-
# you to overwrite @timestamp and this will typically happen for json
80-
# LogStash Event deserialized here.
81-
if !@target && event.timestamp.is_a?(String)
82-
event.timestamp = LogStash::Timestamp.parse_iso8601(event.timestamp)
78+
# TODO: (colin) the timestamp initialization should be DRY'ed but exposing the similar code
79+
# in the Event#init_timestamp method. See https://github.com/elastic/logstash/issues/4293
80+
81+
# a) since the parsed hash will be set in the event root, first extract any @timestamp field to properly initialized it
82+
parsed_timestamp = parsed.delete(LogStash::Event::TIMESTAMP)
83+
begin
84+
timestamp = parsed_timestamp ? LogStash::Timestamp.coerce(parsed_timestamp) : nil
85+
rescue LogStash::TimestampParserError => e
86+
timestamp = nil
8387
end
8488

85-
filter_matched(event)
86-
rescue => e
87-
tag = "_jsonparsefailure"
88-
event["tags"] ||= []
89-
event["tags"] << tag unless event["tags"].include?(tag)
90-
@logger.warn("Trouble parsing json", :source => @source,
91-
:raw => event[@source], :exception => e)
92-
return
89+
# b) then set all parsed fields in the event
90+
parsed.each{|k, v| event[k] = v}
91+
92+
# c) finally re-inject proper @timestamp
93+
if parsed_timestamp
94+
if timestamp
95+
event.timestamp = timestamp
96+
else
97+
event.timestamp = LogStash::Timestamp.new
98+
@logger.warn("Unrecognized #{LogStash::Event::TIMESTAMP} value, setting current time to #{LogStash::Event::TIMESTAMP}, original in #{LogStash::Event::TIMESTAMP_FAILURE_FIELD} field", :value => parsed_timestamp.inspect)
99+
event.tag(LogStash::Event::TIMESTAMP_FAILURE_TAG)
100+
event[LogStash::Event::TIMESTAMP_FAILURE_FIELD] = parsed_timestamp.to_s
101+
end
102+
end
93103
end
94104

95-
@logger.debug("Event after json filter", :event => event)
96-
97-
end # def filter
105+
filter_matched(event)
98106

99-
end # class LogStash::Filters::Json
107+
@logger.debug? && @logger.debug("Event after json filter", :event => event)
108+
end
109+
end

spec/filters/json_spec.rb

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# encoding: utf-8
2+
13
require "logstash/devutils/rspec/spec_helper"
24
require "logstash/filters/json"
35
require "logstash/timestamp"
@@ -103,52 +105,72 @@
103105
end
104106
end
105107

106-
context "when json could not be parsed" do
108+
context "using message field source" do
107109

108110
subject(:filter) { LogStash::Filters::Json.new(config) }
109111

110-
let(:message) { "random_message" }
111-
let(:config) { {"source" => "message"} }
112-
let(:event) { LogStash::Event.new("message" => message) }
112+
let(:config) { {"source" => "message"} }
113+
let(:event) { LogStash::Event.new("message" => message) }
113114

114115
before(:each) do
115116
filter.register
116117
filter.filter(event)
117118
end
118119

119-
it "add the failure tag" do
120-
expect(event).to include "tags"
121-
end
120+
context "when json could not be parsed" do
121+
let(:message) { "random_message" }
122122

123-
it "uses an array to store the tags" do
124-
expect(event['tags']).to be_a Array
125-
end
123+
it "add the failure tag" do
124+
expect(event).to include("tags")
125+
end
126126

127-
it "add a json parser failure tag" do
128-
expect(event['tags']).to include "_jsonparsefailure"
129-
end
127+
it "uses an array to store the tags" do
128+
expect(event['tags']).to be_a(Array)
129+
end
130+
131+
it "add a json parser failure tag" do
132+
expect(event['tags']).to include("_jsonparsefailure")
133+
end
130134

131-
context "there are two different errors added" do
135+
context "there are two different errors added" do
132136

133-
let(:event) { LogStash::Event.new("message" => message, "tags" => ["_anotherkinfoffailure"] ) }
137+
let(:event) { LogStash::Event.new("message" => message, "tags" => ["_anotherkinfoffailure"] ) }
134138

135-
it "pile the different error messages" do
136-
expect(event['tags']).to include "_jsonparsefailure"
137-
end
139+
it "pile the different error messages" do
140+
expect(event['tags']).to include("_jsonparsefailure")
141+
end
138142

139-
it "keep the former error messages on the list" do
140-
expect(event['tags']).to include "_anotherkinfoffailure"
143+
it "keep the former error messages on the list" do
144+
expect(event['tags']).to include("_anotherkinfoffailure")
145+
end
141146
end
142147
end
143148

144149
context "the JSON is an ArrayList" do
145-
146-
let(:message) { "[1, 2, 3]" }
150+
let(:message) { "[1, 2, 3]" }
147151

148152
it "adds the failure tag" do
149-
expect(event['tags']).to include "_jsonparsefailure"
153+
expect(event['tags']).to include("_jsonparsefailure")
154+
end
155+
end
156+
157+
context "json contains valid timestamp" do
158+
let(:message) { "{\"foo\":\"bar\", \"@timestamp\":\"2015-12-02T17:40:00.666Z\"}" }
159+
160+
it "should set json timestamp" do
161+
expect(event.timestamp).to be_a(LogStash::Timestamp)
162+
expect(event.timestamp.to_s).to eq("2015-12-02T17:40:00.666Z")
150163
end
151164
end
152165

166+
context "json contains invalid timestamp" do
167+
let(:message) { "{\"foo\":\"bar\", \"@timestamp\":\"foobar\"}" }
168+
169+
it "should set timestamp to current time" do
170+
expect(event.timestamp).to be_a(LogStash::Timestamp)
171+
expect(event["tags"]).to include(LogStash::Event::TIMESTAMP_FAILURE_TAG)
172+
expect(event[LogStash::Event::TIMESTAMP_FAILURE_FIELD]).to eq("foobar")
173+
end
174+
end
153175
end
154176
end

0 commit comments

Comments
 (0)