1
0
mirror of https://github.com/meineerde/rackstash.git synced 2026-01-12 08:41:32 +00:00
rackstash/lib/rackstash.rb
Holger Just c58c70febb Add Rack middleware
The middleware can be used in a Rack appliction wrap all log messages
emitted to the logger during a single request in a single emitted log
event. This ensures that all data concerning the request, including log
messages as well as additional fields and tags are logged as one single
event.

This ensures that the data is kept as a whole when the log event is
handled by later systems like Logstash. Each request can be analyzed as
a whole without having to group or parse complex multi-line log formats.
2017-10-18 00:21:43 +02:00

175 lines
5.4 KiB
Ruby

# 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 'set'
require 'rackstash/version'
module Rackstash
# A custom error which is raised by methods which need to be implemented
# elsewhere, usually in a subclass. Please refer to the documentation of the
# method which raised this error for details.
#
# Note that this error is not a `StandardError` and will not be rescued
# unless it or any of its ancestors, e.g. `Exception` is specified explicitly.
class NotImplementedHereError < ScriptError; end
SEVERITIES = [
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
FATAL = 4,
UNKNOWN = 5
].freeze
SEVERITY_NAMES = {
'debug' => DEBUG,
'info' => INFO,
'warn' => WARN,
'error' => ERROR,
'fatal' => FATAL,
'unknown' => UNKNOWN
}.freeze
SEVERITY_LABELS = [
'DEBUG'.freeze,
'INFO'.freeze,
'WARN'.freeze,
'ERROR'.freeze,
'FATAL'.freeze,
'ANY'.freeze
].freeze
# Gets the label for a given severity. You can specify the severity either by
# its numeric value or its name in most variations (`Symbol`, `String`,
# different cases).
#
# If the given severity if unknown or out of range, we return `"ANY"`.
#
# @param severity [Integer, #to_s] A numeric value of one of the {SEVERITIES}
# or a {SEVERITY_NAMES} key
# @return [String] one of the {SEVERITY_LABELS}
def self.severity_label(severity)
if severity.is_a?(Integer)
return SEVERITY_LABELS.last if severity < 0
SEVERITY_LABELS[severity] || SEVERITY_LABELS.last
else
severity = SEVERITY_NAMES.fetch(severity.to_s.downcase, UNKNOWN)
SEVERITY_LABELS[severity]
end
end
# Resolve a given severity to its numeric value. You can specify the severity
# either by its numeric value (generally one of the {SEVERITIES}), or its name
# in most variations (`Symbol`, `String`, different cases), i.e. one of the
# {SEVERITY_NAMES}.
#
# If an invalid severity name is given, we raise an `ArgumentError`. All
# Integer values are accepted without further checks.
#
# @param severity [Integer, #to_s] A numeric value of one of the {SEVERITIES}
# or a {SEVERITY_NAMES} key)
# @raise [ArgumentError] if an invalid severity name is given.
# @return [Integer] the resolved severity
def self.severity(severity)
return severity if severity.is_a?(Integer)
SEVERITY_NAMES.fetch(severity.to_s.downcase) do
raise ArgumentError, "invalid log severity: #{severity.inspect}"
end
end
PROGNAME = "rackstash/v#{Rackstash::VERSION}".freeze
# A class for the {UNDEFINED} object. Generally, there will only be exactly
# one object of this class.
#
# The {UNDEFINED} object can be used as the default value for method arguments
# to distinguish it from `nil`. See https://holgerjust.de/2016/detecting-default-arguments-in-ruby/#special-default-value
# for details.
class UndefinedClass
# @return [Boolean] `true` iff `other` is the exact same object as `self`
def ==(other)
self.equal?(other)
end
alias === ==
alias eql? ==
# @return [String] the string `"undefined"`
def to_s
'undefined'.freeze
end
alias inspect to_s
end
UNDEFINED = UndefinedClass.new.tap do |undefined|
class << undefined.class
undef_method :allocate
undef_method :new
end
end
EMPTY_STRING = ''.freeze
EMPTY_SET = Set.new.freeze
# How many decimal places to render on ISO 8601 timestamps
ISO8601_PRECISION = 6
FIELD_MESSAGE = 'message'.freeze
FIELD_TAGS = 'tags'.freeze
FIELD_TIMESTAMP = '@timestamp'.freeze
FIELD_VERSION = '@version'.freeze
FIELD_ERROR = 'error'.freeze
FIELD_ERROR_MESSAGE = 'error_message'.freeze
FIELD_ERROR_TRACE = 'error_trace'.freeze
FIELD_DURATION = 'duration'.freeze
FIELD_METHOD = 'method'.freeze
FIELD_PATH = 'path'.freeze
FIELD_SCHEME = 'scheme'.freeze
FIELD_STATUS = 'status'.freeze
# Returns a {Flow} which is used by the normal logger {Flow}s to write details
# about any unexpected errors during interaction with their {Adapters}.
#
# By default, this Flow logs JSON-formatted messages to `STDERR`
#
# @return [Rackstash::Flow] the default error flow
def self.error_flow
@error_flow ||= Rackstash::Flow.new(STDERR)
end
# Set a {Flow} which is used bythe normal logger {Flow}s to write details
# of any unexpected errors during interaction with their {Adapters}.
#
# You can set a different `error_flow` for each {Flow} if required. You can
# also change this flow to match your desired fallback format and log adapter.
#
# To still work in the face of unexpected availability issues like a full
# filesystem, an unavailable network, broken external loggers, or any other
# external issues, it is usually desireable to chose a local and mostly
# relibable log target.
#
# @param flow [Flow, Adapters::Adapter, Object] a single {Flow} or an object
# which can be used as a {Flow}'s adapter. See {Flow#initialize}.
# @return [Rackstash::Flow] the given `flow`
def self.error_flow=(flow)
flow = Flow.new(flow) unless flow.is_a?(Rackstash::Flow)
@error_flow = flow
end
end
require 'rackstash/logger'
require 'rackstash/adapters/callable'
require 'rackstash/adapters/file'
require 'rackstash/adapters/logger'
require 'rackstash/adapters/io'
require 'rackstash/adapters/null'