mirror of
https://github.com/meineerde/rackstash.git
synced 2025-10-17 14:01:01 +00:00
Add lograge encoder
This encoder formats logs similar to the key-value formatter of the lograge gem.
This commit is contained in:
parent
40aa74c5c0
commit
b101d7356a
@ -5,6 +5,7 @@
|
|||||||
# of the MIT license. See the LICENSE.txt file for details.
|
# of the MIT license. See the LICENSE.txt file for details.
|
||||||
|
|
||||||
require 'rackstash/encoders/json'
|
require 'rackstash/encoders/json'
|
||||||
|
require 'rackstash/encoders/lograge'
|
||||||
require 'rackstash/encoders/logstash'
|
require 'rackstash/encoders/logstash'
|
||||||
require 'rackstash/encoders/message'
|
require 'rackstash/encoders/message'
|
||||||
require 'rackstash/encoders/raw'
|
require 'rackstash/encoders/raw'
|
||||||
|
|||||||
139
lib/rackstash/encoders/lograge.rb
Normal file
139
lib/rackstash/encoders/lograge.rb
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
# Copyright 2017 Holger Just
|
||||||
|
#
|
||||||
|
# This software may be modified and distributed under the terms
|
||||||
|
# of the MIT license. See the LICENSE.txt file for details.
|
||||||
|
|
||||||
|
require 'rackstash/encoders/helpers/timestamp'
|
||||||
|
|
||||||
|
module Rackstash
|
||||||
|
module Encoders
|
||||||
|
# The Lograge encoder formats the log event in the original key-value format
|
||||||
|
# of the [lograge gem](https://github.com/roidrage/lograge).
|
||||||
|
#
|
||||||
|
# The fields of the passed event are serielized to simple `key=value` pairs,
|
||||||
|
# separated by a single space each. The following formatting rules apply:
|
||||||
|
#
|
||||||
|
# * Field names for nested Hashes and Arrays are separated by a dot to form
|
||||||
|
# a unique key name
|
||||||
|
# * Arrays are formatted the same as nested hashes, using the value's index
|
||||||
|
# in the array in the field name for each value.
|
||||||
|
# * Floats are formatted with 2 decimal digits
|
||||||
|
# * Newlines, dots, equals signs or spaces in keys and values are preserved
|
||||||
|
# and not escaped in any way (apart from the `"error"` field, see below).
|
||||||
|
# You might thus want to avoid any whitespace charactes in general and
|
||||||
|
# dots or equals signs in hash keys. You can use filters in your {Flow} to
|
||||||
|
# ensure suitable field names.
|
||||||
|
# * The `"message"` and `"error_trace"` fields are never included in the
|
||||||
|
# output.
|
||||||
|
# * If there is an error in the event hash, we will generate a quoted string
|
||||||
|
# from the `"error"` and `"error_message"` fields.
|
||||||
|
#
|
||||||
|
# Given the following event
|
||||||
|
#
|
||||||
|
# {
|
||||||
|
# "@timestamp" => Time.utc(2017, 4, 18, 23, 21, 58),
|
||||||
|
# "message" => ["This is ignored"],
|
||||||
|
# "foo" => ["bar", "baz"],
|
||||||
|
# "beep" => {
|
||||||
|
# "pling" => "plong",
|
||||||
|
# "toot" => "chirp"
|
||||||
|
# },
|
||||||
|
# "runtime" => 3.14159
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# the encoder will output the following log line:
|
||||||
|
#
|
||||||
|
# timestamp=2017-04-18T23:21:58.000000Z foo.0=bar foo.1=baz beep.pling=plong beep.toot=chirp runtime=3.14
|
||||||
|
#
|
||||||
|
# With an error in the event hash, e.g. like this
|
||||||
|
#
|
||||||
|
# {
|
||||||
|
# "@timestamp" => Time.utc(2017, 4, 18, 23, 21, 58),
|
||||||
|
# "message" => ["This is ignored"],
|
||||||
|
# "error" => "RuntimeError",
|
||||||
|
# "error_message" => "Something bad happened",
|
||||||
|
# "error_trace" => "my_lib.rb:5:in `broken_method'\nmy_lib.rb:10:in `<main>'"
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# the encoder will output the following log line:
|
||||||
|
#
|
||||||
|
# timestamp=2017-04-18T23:21:58.000000Z error='RuntimeError: Something bad happened'
|
||||||
|
#
|
||||||
|
class Lograge
|
||||||
|
include Rackstash::Encoders::Helpers::Timestamp
|
||||||
|
|
||||||
|
SKIP = [
|
||||||
|
FIELD_MESSAGE,
|
||||||
|
FIELD_ERROR_TRACE
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# @param event [Hash] a log event as produced by the {Flow}
|
||||||
|
# @return [String] a log line with formatted key-value pairs
|
||||||
|
def encode(event)
|
||||||
|
normalize_timestamp(event)
|
||||||
|
|
||||||
|
format_error(event)
|
||||||
|
skip_fields(event)
|
||||||
|
|
||||||
|
serialize_hash(event)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def format_error(event)
|
||||||
|
error = event[FIELD_ERROR]
|
||||||
|
error_message = event.delete(FIELD_ERROR_MESSAGE)
|
||||||
|
|
||||||
|
event[FIELD_ERROR] =
|
||||||
|
if error.nil?
|
||||||
|
error_message.nil? ? nil : "'#{error_message}'"
|
||||||
|
else
|
||||||
|
error_message.nil? ? "'#{error}'" : "'#{error}: #{error_message}'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip_fields(event)
|
||||||
|
SKIP.each do |field|
|
||||||
|
event.delete(field)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_hash(hash, prefix: nil)
|
||||||
|
hash.map { |key, value|
|
||||||
|
serialize_pair(key, value, prefix)
|
||||||
|
}.compact.join(' '.freeze)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_array(array, prefix: nil)
|
||||||
|
array.each_with_index.map { |value, index|
|
||||||
|
serialize_pair(index.to_s, value, prefix)
|
||||||
|
}.compact.join(' '.freeze)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_pair(key, value, prefix)
|
||||||
|
if prefix
|
||||||
|
key = "#{prefix}.#{key}"
|
||||||
|
elsif key == FIELD_TIMESTAMP
|
||||||
|
# Use 'timestamp' instead of '@timestamp' on the top-level
|
||||||
|
key = 'timestamp'
|
||||||
|
end
|
||||||
|
|
||||||
|
case value
|
||||||
|
when nil
|
||||||
|
return
|
||||||
|
when Hash
|
||||||
|
return if value.empty?
|
||||||
|
return serialize_hash(value, prefix: key)
|
||||||
|
when Array
|
||||||
|
return if value.empty?
|
||||||
|
return serialize_array(value, prefix: key)
|
||||||
|
when Float
|
||||||
|
value = Kernel.format('%.2f'.freeze, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
"#{key}=#{value}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
70
spec/rackstash/encoders/lograge_spec.rb
Normal file
70
spec/rackstash/encoders/lograge_spec.rb
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
# Copyright 2017 Holger Just
|
||||||
|
#
|
||||||
|
# This software may be modified and distributed under the terms
|
||||||
|
# of the MIT license. See the LICENSE.txt file for details.
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
require 'rackstash/encoders/lograge'
|
||||||
|
|
||||||
|
describe Rackstash::Encoders::Lograge do
|
||||||
|
let(:encoder) { described_class.new }
|
||||||
|
|
||||||
|
describe '#encode' do
|
||||||
|
it 'formats the timestamp if present' do
|
||||||
|
event = { '@timestamp' => Time.new(2016, 10, 17, 16, 37, 0, '+03:00') }
|
||||||
|
expect(encoder.encode(event))
|
||||||
|
.to eql 'timestamp=2016-10-17T13:37:00.000000Z'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'formats multiple values' do
|
||||||
|
event = { 'pling' => 'plong', 'toot' => 'chirp' }
|
||||||
|
expect(encoder.encode(event))
|
||||||
|
.to eql 'pling=plong toot=chirp'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'formats nested objects' do
|
||||||
|
event = { 'pling' => ['plong', nil, { 'toot' => { 'bird' => ['chirp', 'tweet'] } }] }
|
||||||
|
expect(encoder.encode(event))
|
||||||
|
.to eql 'pling.0=plong pling.2.toot.bird.0=chirp pling.2.toot.bird.1=tweet'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'formats float values' do
|
||||||
|
event = { 'key' => 3.14159, 'rounded' => 4.947 }
|
||||||
|
expect(encoder.encode(event)).to eql 'key=3.14 rounded=4.95'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'formats complex errors' do
|
||||||
|
event = {
|
||||||
|
'error' => 'RuntimeError',
|
||||||
|
'error_message' => 'Something',
|
||||||
|
'error_trace' => "Foo\nBar\nBaz",
|
||||||
|
|
||||||
|
'nested' => {
|
||||||
|
'error' => 'NestedError',
|
||||||
|
'error_message' => 'a message'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(encoder.encode(event))
|
||||||
|
.to eql "error='RuntimeError: Something' nested.error=NestedError nested.error_message=a message"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'formats an error' do
|
||||||
|
event = { 'error' => 'StandardError' }
|
||||||
|
expect(encoder.encode(event)).to eql "error='StandardError'"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'formats an error_message' do
|
||||||
|
event = { 'error_message' => 'Something happened' }
|
||||||
|
expect(encoder.encode(event)).to eql "error='Something happened'"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'ignores dots, spaces and equal signs' do
|
||||||
|
event = { 'some.key' => 'a.value', 'a=b' => 'c=d', 'a key' => 'some value' }
|
||||||
|
expect(encoder.encode(event))
|
||||||
|
.to eql 'some.key=a.value a=b=c=d a key=some value'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user