semantic_logger 2.21.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,12 +1,17 @@
1
- require 'splunk-sdk-ruby'
1
+ begin
2
+ require 'splunk-sdk-ruby'
3
+ rescue LoadError
4
+ raise 'Gem splunk-sdk-ruby is required for logging to Splunk. Please add the gem "splunk-sdk-ruby" to your Gemfile.'
5
+ end
2
6
 
3
- # Note: Not recommended to use the colorized formatter.
7
+ # Note: This appender is Deprecated. Use: SemanticLogger::Appender::SplunkHttp
4
8
  class SemanticLogger::Appender::Splunk < SemanticLogger::Appender::Base
5
9
  attr_reader :config, :index, :service, :service_index
6
10
 
7
- # Allow the level for this appender to be overwritten
8
- # Default: :error
11
+ # DEPRECATED, Please use SemanticLogger::Appender::SplunkHttp
9
12
  def initialize(options, level=:error, &block)
13
+ Kernel.warn('Splunk Appender is deprecated, please use SemanticLogger::Appender::SplunkHttp')
14
+
10
15
  # Parse input options for setting up splunk connection
11
16
  parse_options(options)
12
17
 
@@ -31,7 +36,7 @@ class SemanticLogger::Appender::Splunk < SemanticLogger::Appender::Base
31
36
  # Ensure minimum log level is met, and check filter
32
37
  return false if (level_index > (log.level_index || 0)) || !include_message?(log)
33
38
  # Submit the log message
34
- @service_index.submit(formatter.call(log))
39
+ @service_index.submit(formatter.call(log, self))
35
40
  true
36
41
  end
37
42
 
@@ -0,0 +1,99 @@
1
+ # Splunk log appender.
2
+ #
3
+ # Use the newer, faster and more complete JSON over HTTP interface for Splunk.
4
+ #
5
+ # To configure Splunk to receive log messages via this appender:
6
+ # http://dev.splunk.com/view/event-collector/SP-CAAAE7F
7
+ #
8
+ # Example
9
+ # appender = SemanticLogger::Appender::SplunkHttp.new(
10
+ # url: 'http://localhost:8080',
11
+ # token: '70CA900C-3D7E-42A4-9C79-7975D1C422A8'
12
+ # )
13
+ # SemanticLogger.add_appender(appender)
14
+ class SemanticLogger::Appender::SplunkHttp < SemanticLogger::Appender::Http
15
+ # Create Splunk appender over persistent HTTP(S)
16
+ #
17
+ # Parameters:
18
+ # token: [String]
19
+ # Token created in Splunk for this HTTP Appender
20
+ # Mandatory.
21
+ #
22
+ # source_type: [String]
23
+ # Optional: Source type to display in Splunk
24
+ #
25
+ # index: [String]
26
+ # Optional: Name of a valid index for this message in Splunk.
27
+ #
28
+ # url: [String]
29
+ # Valid URL to post to.
30
+ # Example: http://example.com
31
+ # To enable SSL include https in the URL.
32
+ # Example: https://example.com
33
+ # verify_mode will default: OpenSSL::SSL::VERIFY_PEER
34
+ #
35
+ # application: [String]
36
+ # Name of this application to appear in log messages.
37
+ # Default: SemanticLogger.application
38
+ #
39
+ # host: [String]
40
+ # Name of this host to appear in log messages.
41
+ # Default: SemanticLogger.host
42
+ #
43
+ # compress: [true|false]
44
+ # Whether to compress the JSON string with GZip.
45
+ # Default: true
46
+ #
47
+ # ssl: [Hash]
48
+ # Specific SSL options: For more details see NET::HTTP.start
49
+ # ca_file, ca_path, cert, cert_store, ciphers, key, open_timeout, read_timeout, ssl_timeout,
50
+ # ssl_version, use_ssl, verify_callback, verify_depth and verify_mode.
51
+ #
52
+ # level: [:trace | :debug | :info | :warn | :error | :fatal]
53
+ # Override the log level for this appender.
54
+ # Default: SemanticLogger.default_level
55
+ #
56
+ # filter: [Regexp|Proc]
57
+ # RegExp: Only include log messages where the class name matches the supplied.
58
+ # regular expression. All other messages will be ignored.
59
+ # Proc: Only include log messages where the supplied Proc returns true
60
+ # The Proc must return true or false.
61
+ def initialize(options, &block)
62
+ options = options.dup
63
+ @source_type = options.delete(:source_type)
64
+ @index = options.delete(:index)
65
+ token = options.delete(:token)
66
+ raise(ArgumentError, 'Missing mandatory parameter :token') unless token
67
+
68
+ # Splunk supports HTTP Compression, enable by default
69
+ options[:compress] = true unless options.key?(:compress)
70
+
71
+ super(options, &block)
72
+
73
+ @header['Authorization'] = "Splunk #{token}"
74
+ end
75
+
76
+ # Returns [String] JSON to send to Splunk
77
+ # For splunk format requirements see:
78
+ # http://dev.splunk.com/view/event-collector/SP-CAAAE6P
79
+ def default_formatter
80
+ Proc.new do |log, logger|
81
+ h = log.to_h
82
+ h.delete(:application)
83
+ h.delete(:host)
84
+ h.delete(:time)
85
+ message = {
86
+ source: logger.application,
87
+ host: logger.host,
88
+ time: log.time.utc.to_f,
89
+ event: h
90
+ }
91
+ message[:source_type] = @source_type if @source_type
92
+ message[:index] = @index if @index
93
+
94
+ # Render to JSON
95
+ message.to_json
96
+ end
97
+ end
98
+
99
+ end
@@ -1,41 +1,36 @@
1
- # syslog appender for SemanticLogger - Supports local and remote syslog (over TCP or UDP)
2
- #
3
- # Example 1
4
- # Log to the local syslog.
5
- #
6
- # require 'semantic_logger'
7
- # SemanticLogger.default_level = :trace
1
+ require 'syslog'
2
+ require 'uri'
3
+ require 'socket'
4
+
5
+ # Send log messages to local syslog, or remote syslog servers over TCP or UDP.
8
6
  #
9
- # syslog_appender = SemanticLogger::Appender::Syslog.new
10
- # SemanticLogger.add_appender(syslog_appender)
7
+ # Example: Log to a local Syslog daemon
8
+ # SemanticLogger.add_appender(SemanticLogger::Appender::Syslog.new)
11
9
  #
12
- # logger = SemanticLogger['SyslogAppenderExample']
13
- # logger.info "Info Hello! - This message should appear in the local syslog!"
10
+ # Example: Log to a remote Syslog server using TCP:
11
+ # appender = SemanticLogger::Appender::Syslog.new(
12
+ # url: 'tcp://myloghost:514'
13
+ # )
14
14
  #
15
+ # # Optional: Add filter to exclude health_check, or other log entries
16
+ # appender.filter = Proc.new { |log| log.message !~ /(health_check|Not logged in)/ }
15
17
  #
16
- # Example 2
17
- # Send to a remote syslog appender - myloghost - over TCP on port 514.
18
- # Tested with syslog-ng as part of an ELSA installation.
19
- # https://code.google.com/p/enterprise-log-search-and-archive/
18
+ # SemanticLogger.add_appender(appender)
20
19
  #
21
- # require 'semantic_logger'
22
- # # Only log warn and above messages to the remote syslog.
23
- # syslog_appender = SemanticLogger::Appender::Syslog.new(level: :warn, server: 'tcp://myloghost:514')
24
- # SemanticLogger.add_appender(syslog_appender)
20
+ # Example: Log to a remote Syslog server using UDP:
21
+ # appender = SemanticLogger::Appender::Syslog.new(
22
+ # url: 'udp://myloghost:514'
23
+ # )
25
24
  #
26
- # logger = SemanticLogger['SyslogAppenderExample']
27
- # logger.info "Info Hello! - The log level is too low and will not be logged."
28
- # logger.error "Error! Error! - This message should appear in the remote syslog!"
25
+ # # Optional: Add filter to exclude health_check, or other log entries
26
+ # appender.filter = Proc.new { |log| log.message !~ /(health_check|Not logged in)/ }
29
27
  #
30
- require 'syslog'
31
- require 'uri'
32
- require 'socket'
33
-
28
+ # SemanticLogger.add_appender(appender)
34
29
  module SemanticLogger
35
30
  module Appender
36
31
  class Syslog < SemanticLogger::Appender::Base
37
32
 
38
- attr_reader :remote_syslog, :server, :host, :port, :protocol, :facility, :local_hostname
33
+ attr_reader :remote_syslog, :url, :server, :port, :protocol, :facility, :host, :application
39
34
 
40
35
  # Default mapping of ruby log levels to syslog log levels
41
36
  #
@@ -56,14 +51,47 @@ module SemanticLogger
56
51
  trace: ::Syslog::LOG_DEBUG
57
52
  }
58
53
 
59
- # For more information on the Syslog constants used below see http://ruby-doc.org/stdlib-2.0.0/libdoc/syslog/rdoc/Syslog.html
54
+ # Create a Syslog appender instance.
55
+ #
60
56
  # Parameters
57
+ # url: [String]
58
+ # Default: 'syslog://localhost'
59
+ # For writing logs to a remote syslog server
60
+ # URL of server: protocol://host:port
61
+ # Uses port 514 by default for TCP and UDP.
62
+ # local syslog example: 'syslog://localhost'
63
+ # TCP example with default port: 'tcp://logger'
64
+ # TCP example with custom port: 'tcp://logger:8514'
65
+ # UDP example with default port: 'udp://logger'
66
+ # UDP example with custom port: 'udp://logger:8514'
67
+ # When using the :syslog protocol, logs will always be sent to the localhost syslog
61
68
  #
62
- # :ident [String]
63
- # Identity of the program
64
- # Default: 'ruby'
69
+ # host: [String]
70
+ # Host name to provide to the remote syslog.
71
+ # Default: SemanticLogger.host
65
72
  #
66
- # :options [Integer]
73
+ # tcp_client: [Hash]
74
+ # Default: {}
75
+ # Only used with the TCP protocol.
76
+ # Specify custom parameters to pass into Net::TCPClient.new
77
+ # For a list of options see the net_tcp_client documentation:
78
+ # https://www.omniref.com/ruby/gems/net_tcp_client/1.0.0/symbols/Net::TCPClient/initialize
79
+ #
80
+ # level: [:trace | :debug | :info | :warn | :error | :fatal]
81
+ # Override the log level for this appender.
82
+ # Default: SemanticLogger.default_level
83
+ #
84
+ # filter: [Regexp|Proc]
85
+ # RegExp: Only include log messages where the class name matches the supplied.
86
+ # regular expression. All other messages will be ignored.
87
+ # Proc: Only include log messages where the supplied Proc returns true
88
+ # The Proc must return true or false.
89
+ #
90
+ # application: [String]
91
+ # Identity of the program.
92
+ # Default: SemanticLogger.application
93
+ #
94
+ # options: [Integer]
67
95
  # Default: ::Syslog::LOG_PID | ::Syslog::LOG_CONS
68
96
  # Any of the following (options can be logically OR'd together)
69
97
  # ::Syslog::LOG_CONS
@@ -73,7 +101,7 @@ module SemanticLogger
73
101
  # ::Syslog::LOG_PERROR
74
102
  # ::Syslog::LOG_PID
75
103
  #
76
- # :facility [Integer]
104
+ # facility: [Integer]
77
105
  # Default: ::Syslog::LOG_USER
78
106
  # Type of program (can be logically OR'd together)
79
107
  # ::Syslog::LOG_AUTH
@@ -100,11 +128,7 @@ module SemanticLogger
100
128
  # ::Syslog::LOG_LOCAL6
101
129
  # ::Syslog::LOG_LOCAL7
102
130
  #
103
- # :level [Symbol]
104
- # Default: SemanticLogger's log level.
105
- # The minimum level at which this appender will write logs. Any log messages below this level will be ignored.
106
- #
107
- # :level_map [Hash]
131
+ # level_map: [Hash]
108
132
  # Supply a custom map of SemanticLogger levels to syslog levels.
109
133
  # For example, passing in { warn: ::Syslog::LOG_NOTICE }
110
134
  # would result in a log mapping that matches the default level map,
@@ -119,51 +143,28 @@ module SemanticLogger
119
143
  # debug: ::Syslog::LOG_INFO,
120
144
  # trace: ::Syslog::LOG_DEBUG
121
145
  # }
122
- #
123
- # :local_hostname [String]
124
- # Default: Socket.gethostname || `hostname`.strip
125
- # Hostname to provide to the remote syslog.
126
- #
127
- # :server [String]
128
- # Default: 'syslog://localhost'
129
- # For writing logs to a remote syslog server
130
- # URI of server: protocol://host:port
131
- # Uses port 514 by default for TCP and UDP.
132
- # local syslog example: 'syslog://localhost'
133
- # TCP example with default port: 'tcp://logger'
134
- # TCP example with custom port: 'tcp://logger:8514'
135
- # UDP example with default port: 'udp://logger'
136
- # UDP example with custom port: 'udp://logger:8514'
137
- # When using the :syslog protocol, logs will always be sent to the localhost syslog
138
- #
139
- # :tcp_client [Hash]
140
- # Default: {}
141
- # Only used with the TCP protocol.
142
- # Specify custom parameters to pass into Net::TCPClient.new
143
- # For a list of options see the net_tcp_client documentation:
144
- # https://www.omniref.com/ruby/gems/net_tcp_client/1.0.0/symbols/Net::TCPClient/initialize
145
- def initialize(params = {}, &block)
146
- params = params.dup
147
- @ident = params.delete(:ident) || 'ruby'
148
- @options = params.delete(:options) || (::Syslog::LOG_PID | ::Syslog::LOG_CONS)
149
- @facility = params.delete(:facility) || ::Syslog::LOG_USER
150
- filter = params.delete(:filter)
151
- level = params.delete(:level)
152
- level_map = params.delete(:level_map)
153
- @level_map = DEFAULT_LEVEL_MAP.dup
154
- @level_map.update(level_map) if level_map
155
- @server = params.delete(:server) || 'syslog://localhost'
156
- uri = URI(@server)
157
- @host = uri.host || 'localhost'
158
- @protocol = (uri.scheme || :syslog).to_sym
146
+ def initialize(options = {}, &block)
147
+ options = options.dup
148
+ level = options.delete(:level)
149
+ filter = options.delete(:filter)
150
+ @application = options.delete(:application) || options.delete(:ident) || 'ruby'
151
+ @options = options.delete(:options) || (::Syslog::LOG_PID | ::Syslog::LOG_CONS)
152
+ @facility = options.delete(:facility) || ::Syslog::LOG_USER
153
+ level_map = options.delete(:level_map)
154
+ @url = options.delete(:url) || options.delete(:server) || 'syslog://localhost'
155
+ uri = URI(@url)
156
+ @server = uri.host || 'localhost'
157
+ @protocol = (uri.scheme || :syslog).to_sym
158
+ @port = uri.port || 514
159
+ @server = 'localhost' if @protocol == :syslog
160
+ @host = options.delete(:host) || options.delete(:local_hostname) || SemanticLogger.host
161
+ @tcp_client_options = options.delete(:tcp_client)
162
+
159
163
  raise "Unknown protocol #{@protocol}!" unless [:syslog, :tcp, :udp].include?(@protocol)
160
- @host = 'localhost' if @protocol == :syslog
161
- @port = URI(@server).port || 514
162
- @local_hostname = params.delete(:local_hostname) || Socket.gethostname || `hostname`.strip
163
- @tcp_client_options = params.delete(:tcp_client)
164
+ raise(ArgumentError, "Unknown options: #{options.inspect}") if options.size > 0
164
165
 
165
- # Warn about any unknown configuration options.
166
- params.each_pair { |key, val| SemanticLogger::Logger.logger.warn "Ignoring unknown configuration option: #{key.inspect} => #{val.inspect}" }
166
+ @level_map = DEFAULT_LEVEL_MAP.dup
167
+ @level_map.update(level_map) if level_map
167
168
 
168
169
  # The syslog_protocol gem is required when logging over TCP or UDP.
169
170
  if [:tcp, :udp].include?(@protocol)
@@ -176,7 +177,7 @@ module SemanticLogger
176
177
  # The net_tcp_client gem is required when logging over TCP.
177
178
  if protocol == :tcp
178
179
  @tcp_client_options ||= {}
179
- @tcp_client_options[:server] = "#{@host}:#{@port}"
180
+ @tcp_client_options[:server] = "#{@server}:#{@port}"
180
181
  begin
181
182
  require 'net/tcp_client'
182
183
  rescue LoadError
@@ -195,7 +196,7 @@ module SemanticLogger
195
196
  def reopen
196
197
  case @protocol
197
198
  when :syslog
198
- ::Syslog.open(@ident, @options, @facility)
199
+ ::Syslog.open(@application, @options, @facility)
199
200
  when :tcp
200
201
  # Use the local logger for @remote_syslog so errors with the remote logger can be recorded locally.
201
202
  @tcp_client_options[:logger] = SemanticLogger::Logger.logger
@@ -207,7 +208,7 @@ module SemanticLogger
207
208
  end
208
209
  end
209
210
 
210
- # Write the log using the specified protocol and host.
211
+ # Write the log using the specified protocol and server.
211
212
  def log(log)
212
213
  # Ensure minimum log level is met, and check filter
213
214
  return false if (level_index > (log.level_index || 0)) || !include_message?(log)
@@ -215,12 +216,12 @@ module SemanticLogger
215
216
  case @protocol
216
217
  when :syslog
217
218
  # Since the Ruby Syslog API supports sprintf format strings, double up all existing '%'
218
- message = formatter.call(log).gsub '%', '%%'
219
+ message = formatter.call(log, self).gsub '%', '%%'
219
220
  ::Syslog.log @level_map[log.level], message
220
221
  when :tcp
221
222
  @remote_syslog.retry_on_connection_failure { @remote_syslog.write("#{syslog_packet_formatter(log)}\r\n") }
222
223
  when :udp
223
- @remote_syslog.send syslog_packet_formatter(log), 0, @host, @port
224
+ @remote_syslog.send syslog_packet_formatter(log), 0, @server, @port
224
225
  else
225
226
  raise "Unsupported protocol: #{protocol}"
226
227
  end
@@ -232,36 +233,48 @@ module SemanticLogger
232
233
  @remote_syslog.flush if @remote_syslog && @remote_syslog.respond_to?(:flush)
233
234
  end
234
235
 
235
- # Custom log formatter for syslog
236
+ # Custom log formatter for syslog.
237
+ # Only difference is the removal of the timestamp string since it is in the syslog packet.
236
238
  def default_formatter
237
239
  Proc.new do |log|
238
- tags = log.tags.collect { |tag| "[#{tag}]" }.join(" ") + " " if log.tags && (log.tags.size > 0)
240
+ # Header with date, time, log level and process info
241
+ entry = "#{log.level_to_s} [#{log.process_info}]"
239
242
 
240
- message = log.message.to_s
241
- message << ' -- ' << log.payload.inspect if log.payload
242
- log.each_exception do |exception, i|
243
- if i == 0
244
- message << ' -- '
245
- else
246
- message << "\nCause: "
247
- end
248
- message << "#{exception.class}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}"
249
- end
243
+ # Tags
244
+ entry << ' ' << log.tags.collect { |tag| "[#{tag}]" }.join(' ') if log.tags && (log.tags.size > 0)
245
+
246
+ # Duration
247
+ entry << " (#{log.duration_human})" if log.duration
248
+
249
+ # Class / app name
250
+ entry << " #{log.name}"
250
251
 
251
- duration_str = log.duration ? "(#{'%.1f' % log.duration}ms) " : ''
252
+ # Log message
253
+ entry << " -- #{log.message}" if log.message
252
254
 
253
- "#{log.level.to_s[0..0].upcase} [#{$$}:#{log.thread_name}] #{tags}#{duration_str}#{log.name} -- #{message}"
255
+ # Payload
256
+ if payload = log.payload_to_s(false)
257
+ entry << ' -- ' << payload
258
+ end
259
+
260
+ # Exceptions
261
+ if log.exception
262
+ entry << " -- Exception: #{log.exception.class}: #{log.exception.message}\n"
263
+ entry << log.backtrace_to_s
264
+ end
265
+ entry
254
266
  end
255
267
  end
256
268
 
257
269
  # Format the syslog packet so it can be sent over TCP or UDP
258
270
  def syslog_packet_formatter(log)
259
271
  packet = SyslogProtocol::Packet.new
260
- packet.hostname = @local_hostname
272
+ packet.hostname = @host
261
273
  packet.facility = @facility
262
274
  packet.severity = @level_map[log.level]
263
- packet.tag = @ident
264
- packet.content = default_formatter.call(log)
275
+ packet.tag = @application
276
+ packet.content = default_formatter.call(log, self)
277
+ packet.time = log.time
265
278
  packet.to_s
266
279
  end
267
280
  end
@@ -1,19 +1,35 @@
1
- # Wrapper appender
1
+ # Send log messages to any standard Ruby logging class.
2
2
  #
3
- # Wraps the Rails log, log4r, or Ruby Logger with the SemanticLogger API's
3
+ # Forwards logging call to loggers such as Logger, log4r, etc.
4
4
  #
5
5
  module SemanticLogger
6
6
  module Appender
7
7
  class Wrapper < SemanticLogger::Appender::Base
8
8
  attr_reader :logger
9
9
 
10
- # Create a Logger or Rails Logger appender instance
10
+ # Forward all logging calls to the supplied logging instance.
11
+ #
12
+ # Parameters
13
+ # logger: [Object]
14
+ # Instance of an existing logger conforming to the Ruby Logger methods.
15
+ #
16
+ # level: [:trace | :debug | :info | :warn | :error | :fatal]
17
+ # Override the log level for this appender.
18
+ # Default: SemanticLogger.default_level
19
+ #
20
+ # filter: [Regexp|Proc]
21
+ # RegExp: Only include log messages where the class name matches the supplied.
22
+ # regular expression. All other messages will be ignored.
23
+ # Proc: Only include log messages where the supplied Proc returns true
24
+ # The Proc must return true or false.
11
25
  #
12
26
  # Ruby Logger
13
27
  # require 'logger'
14
28
  # require 'semantic_logger'
29
+ #
15
30
  # ruby_logger = Logger.new(STDOUT)
16
31
  # SemanticLogger.add_appender(ruby_logger)
32
+ #
17
33
  # logger = SemanticLogger['test']
18
34
  # logger.info('Hello World', some: :payload)
19
35
  #
@@ -25,26 +41,25 @@ module SemanticLogger
25
41
  # # Make ActiveRecord logging include its class name in every log entry
26
42
  # ActiveRecord::Base.logger = SemanticLogger['ActiveRecord']
27
43
  #
28
- # Note: Since the log level is controlled by setting the Ruby or Rails logger directly
29
- # the level is ignored for this appender
30
- def initialize(logger, filter=nil, &block)
31
- raise 'logger cannot be null when initiailizing the SemanticLogging::Appender::Wrapper' unless logger
44
+ # Install the `rails_semantic_logger` gem to replace the Rails logger with Semantic Logger.
45
+ def initialize(logger, level = nil, filter = nil, &block)
46
+ raise 'logger cannot be null when initializing the SemanticLogging::Appender::Wrapper' unless logger
32
47
  @logger = logger
33
48
 
34
49
  # Set the formatter to the supplied block
35
50
  @formatter = block || self.default_formatter
36
- super(nil, filter, &block)
51
+ super(level, filter, &block)
37
52
  end
38
53
 
39
54
  # Pass log calls to the underlying Rails, log4j or Ruby logger
40
55
  # trace entries are mapped to debug since :trace is not supported by the
41
56
  # Ruby or Rails Loggers
42
57
  def log(log)
43
- # Check filter
44
- return false unless include_message?(log)
58
+ # Ensure minimum log level is met, and check filter
59
+ return false if (level_index > (log.level_index || 0)) || !include_message?(log)
45
60
 
46
61
  # Underlying wrapper logger implements log level, so don't check here
47
- @logger.send(log.level == :trace ? :debug : log.level, @formatter.call(log))
62
+ @logger.send(log.level == :trace ? :debug : log.level, @formatter.call(log, self))
48
63
  true
49
64
  end
50
65