Appenders

Appenders are destinations that log messages can be written to.

Log messages can be written to one or more of the following destinations at the same time:

To ensure no log messages are lost it is recommend to use TCP over UDP for logging purposes. Due to the architecture of Semantic Logger any performance difference between TCP and UDP will not impact the performance of you application.

Text File

Log to file with the standard formatter:

SemanticLogger.add_appender(file_name: "development.log")

Log to file with the standard colorized formatter:

SemanticLogger.add_appender(file_name: "development.log", formatter: :color)

Log to file in JSON format:

SemanticLogger.add_appender(file_name: "development.log", formatter: :json)

For performance reasons the log file is not re-opened with every call. When the log file needs to be rotated, use a copy-truncate operation rather than deleting the file.

JSON log format

When writing json log output, it has the following layout:

{
  "timestamp": "ISO-8601",
  
  "application": "Application name",
  "environment": "Custom Environment name",
  "host": "Host name",
  "pid": "Process Id",
  "thread": "Thread name or id",
  "file": "filename",
  "line": "line number",
  
  "level": "trace|debug|info|warn|error|fatal",
  "level_index": "0|1|2|3|4|5",
  "message": "The message text without any colorization",
  "name": "Name of the class that generated the log message. Including namespace, if any.",
  "tags": ["tag_name 1", "tag_name 2"],
  "duration": "Human readable duration",
  "duration_ms": "Duration in milliseconds",
  "metric": "Name of the metric",
  "metric_amount": "Size of the metric, usually 1",
  
  "named_tags": {
    "tag1": "any named tags will be inside this named_tags tag",
    "tag2": "any named tags will be inside this named_tags tag"
  },
  
  "payload": {
    "field1": "any custom payload fields will be inside this payload tag",
    "field2": "any custom payload fields will be inside this payload tag"
  },
  
  "exception": {
    "name": "Exception class name",
    "message": "Exception message",
    "stack_trace": ["line 1", "line 2"],
    "cause": {
      "name": "Exception class name",
      "message": "Exception message",
      "stack_trace": ["line 1", "line 2"]
    }
  }
}

Note:

IO Streams

Semantic Logger can log data to any IO Stream instance, such as $stderr or $stdout

# Log errors and above to standard error:
SemanticLogger.add_appender(io: $stderr, level: :error)

Syslog

Log to a local Syslog daemon

SemanticLogger.add_appender(appender: :syslog)

Log to a remote Syslog server using TCP with packet size 2048 bytes. By default the size is 1024 bytes:

SemanticLogger.add_appender(
  appender: :syslog,
  url:      "tcp://myloghost:514",
  max_size: 2048
)

Log to a remote Syslog server using UDP:

SemanticLogger.add_appender(
  appender: :syslog,
  url:      "udp://myloghost:514"
)

Optional: Add filter to exclude health_check, or other log entries:

SemanticLogger.add_appender(
  appender: :syslog,
  url:      "udp://myloghost:514",
  filter:   Proc.new { |log| log.message !~ /(health_check|Not logged in)/ }
)

If logging to a remote Syslog server using UDP, add the following line to your Gemfile:

gem "syslog_protocol"

If logging to a remote Syslog server using TCP, add the following lines to your Gemfile:

gem "syslog_protocol"
gem "net_tcp_client"

Note: :trace level messages are mapped to :debug.

Graylog

Send all log messages to a centralized logging server running Graylog.

The Graylog appender retains all Semantic information when forwarding to Graylog. ( I.e. Data is sent as JSON and avoids the loss of semantic information which would gave occurred had the log information been converted to text.)

For Rails applications, or running bundler, add the following line to the file Gemfile:

gem "gelf"

Install gems:

bundle install

If not using Bundler:

gem install gelf

To add to a Rails application that already uses Rails Semantic Logger create a file called <Rails Root>/config/initializers/graylog.rb with the following contents and restart the application.

To use the TCP Protocol:

SemanticLogger.add_appender(
  appender: :graylog,
  url:      "tcp://localhost:12201"
)

Or, to use the UDP Protocol:

SemanticLogger.add_appender(
  appender: :graylog,
  url:      "udp://localhost:12201"
)

If not using Rails, the facility can be removed, or set to a custom string describing the application.

Note: :trace level messages are mapped to :debug.

Splunk HTTP

In order to write messages to the Splunk HTTP Collector, follow the Splunk instructions to enable the HTTP Event Collector. The instructions also include information on how to generate a token that needs to be supplied below.

SemanticLogger.add_appender(
  appender: :splunk_http,
  url:      "http://localhost:8088/services/collector/event",
  token:    "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
)

Once log messages have been sent, open the Splunk web interface and select Search. To start with limit the message source to the newly added host:

If the machine’s name generating the messages is hostname, Search for: host=hostname

Then change the output list to table view and select only these “interesting columns”:

If HTTPS is being used for the Splunk HTTP Collector, update the url accordingly:

SemanticLogger.add_appender(
  appender: :splunk_http,
  url:      "https://localhost:8088/services/collector/event",
  token:    "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
)

Splunk HTTP Performance

In testing against a local Splunk instance the performance was only about 30 log messages per second. For much better performance consider using the TCP appender to write to Splunk, it achieved 1,400 messages per second with non-ssl. With SSL over TCP to Splunk it was logging just over 1,200 log messages per second.

Elasticsearch

Forward all log messages to Elasticsearch.

Example:

SemanticLogger.add_appender(
  appender:    :elasticsearch,
  url:         "http://localhost:9200",
  index:       "my-index",
  data_stream: true
)

logentries.com

Follow the instructions on logentries to obtain a token.

Replace xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx below with the above token.

module Logentries
  class Formatter < SemanticLogger::Formatters::Json
    attr_accessor :token

    def initialize(token)
      @token = token
    end

    def call(log, logger)
      "#{token} #{super(log, logger)}"
    end
  end
end

formatter = Logentries::Formatter.new("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
SemanticLogger.add_appender(appender: :tcp, server: "api.logentries.com:20000", ssl: true, formatter: formatter)

loggly.com

After signing up with Loggly obtain the token by logging into Loggly.com Navigate to Source Setup -> Customer Tokens and copy the token

Replace xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx below with the above token.

SemanticLogger.add_appender(
  appender: :http,
  url:      "http://logs-01.loggly.com/inputs/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/tag/semantic_logger/"
)

Once log messages have been sent, open the Loggly web interface and select Search. To isolate the new messages start with the following search: tag:"semantic_logger"

In the Field Explorer change to Grid view and add the following fields using Add as column to Grid:

If HTTPS is being used for Loggly, update the url accordingly:

SemanticLogger.add_appender(
  appender: :http,
  url:      "https://logs-01.loggly.com/inputs/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/tag/semantic_logger/"
)

Logstash

Forward log messages to Logstash.

For Rails applications, or running bundler, add the following line to the file Gemfile:

gem "logstash-logger"

Install gems:

bundle install

If not using Bundler:

gem install logstash-logger

If running Rails create an initializer with the following code, otherwise add the code to your program:

require "logstash-logger"

# Use the TCP logger
# See https://github.com/dwbutler/logstash-logger for further options
log_stash = LogStashLogger.new(type: :tcp, host: "localhost", port: 5229)

SemanticLogger.add_appender(logger: log_stash)

Note: :trace level messages are mapped to :debug.

Papertrail

Papertrail accepts log messages via TLS TCP in syslog format.

Sample configuration:

config.semantic_logger.add_appender(
    appender: :syslog,
    url:      "tcp://something.papertrailapp.com:1234",
    tcp_client: {
      ssl: {
        ca_file: File.join(Rails.root, "config", "papertrail-bundle.pem")
      }
    }
  )

For more information see the following section from Papertrail’s documentation.

Bugsnag

Forward :info, :warn, or :error log messages to Bugsnag.

Note: Payload information is not filtered, so take care not to push any sensitive information when logging with tags or a payload.

Configure Bugsnag following the Ruby Bugsnag documentation.

To add to a Rails application that already uses Rails Semantic Logger create a file called <Rails Root>/config/initializers/bugsnag.rb with the following contents and restart the application.

Send :error messages to Bugsnag:

SemanticLogger.add_appender(appender: :bugsnag)

Or, for a standalone installation add the code above after initializing Semantic Logger.

Send :warn and :error messages to Bugsnag:

SemanticLogger.add_appender(appender: :bugsnag, level: :warn)

Send :info, :warn and :error messages to Bugsnag:

SemanticLogger.add_appender(appender: :bugsnag, level: :info)

NewRelic

NewRelic supports Error Events in both it’s paid and free subscriptions.

Adding the New Relic appender will by default send :error and :fatal log entries to New Relic as error events.

Note: Payload information is not filtered, so take care not to push any sensitive information when logging with tags or a payload.

For Rails applications, or running bundler, add the following line to the bottom of the file Gemfile:

gem "newrelic_rpm"

Install gems:

bundle install

If not using Bundler:

gem install newrelic_rpm

To add to a Rails application that already uses Rails Semantic Logger create a file called <Rails Root>/config/initializers/new_relic.rb with the following contents and restart the application.

SemanticLogger.add_appender(appender: :new_relic)

To also send warnings to NewRelic:

SemanticLogger.add_appender(appender: :new_relic, level: :warn)

MongoDB

Write log messages as documents into a MongoDB capped collection.

For Rails applications, or running bundler, add the following line to the file Gemfile:

gem "mongo"

Install gems:

bundle install

If not using Bundler:

gem install mongo

To add to a Rails application that already uses Rails Semantic Logger create a file called <Rails Root>/config/initializers/mongodb.rb with the following contents and restart the application.

client   = Mongo::MongoClient.new("127.0.0.1", 27017)
database = client["test"]

appender = SemanticLogger::Appender::MongoDB.new(
  db:              database,
  collection_size: 1024**3, # 1.gigabyte
  application:     Rails.application.class.name
)
SemanticLogger.add_appender(appender: appender)

The following is written to Mongo:

> db.semantic_logger.findOne()
{
	"_id" : ObjectId("53a8d5b99b9eb4f282000001"),
	"time" : ISODate("2014-06-24T01:34:49.489Z"),
	"host_name" : "appserver1",
	"pid" : null,
	"thread_name" : "2160245740",
	"name" : "Example",
	"level" : "info",
	"level_index" : 2,
	"application" : "my_application",
	"message" : "This message is written to mongo as a document"
}

Apache Kafka

Publish log messages to an Apache Kafka broker.

SemanticLogger.add_appender(
  appender:     :kafka,
  seed_brokers: ["kafka1:9092", "kafka2:9092"],
)

RabbitMQ (AMQP)

Stream log messages through a queue on RabbitMQ broker.

SemanticLogger.add_appender(
  appender:      :rabbitmq,
  queue_name:    "semantic_logger",
  rabbitmq_host: "localhost",
  username:      "the-username",
  password:      "the-password",
)

HTTP(S)

The HTTP appender supports sending JSON messages to most services that can accept log messages in JSON format via HTTP or HTTPS.

SemanticLogger.add_appender(
  appender: :http,
  url:      "http://localhost:8088/path"
)

To send messages via HTTPS, change the url to:

SemanticLogger.add_appender(
  appender: :http,
  url:      "https://localhost:8088/path"
)

The JSON being sent can be modified as needed.

Example, customize the JSON being sent:

formatter = Proc.new do |log, logger|
  h = log.to_h(logger.host, logger.application)

  # Change time from iso8601 to seconds since epoch
  h[:timestamp] = log.time.utc.to_f

  # Render to JSON
  h.to_json
end

SemanticLogger.add_appender(
  appender:  :http,
  url:       "https://localhost:8088/path",
  formatter: formatter
)

TCP Appender (+SSL)

The TCP appender supports sending JSON or other formatted messages to services that can accept log messages via TCP or TCP with SSL.

Send messages in JSON format over TCP:

SemanticLogger.add_appender(
  appender: :tcp,
  server:   "localhost:8088"
)

Send messages in JSON format over TCP with SSL enabled:

SemanticLogger.add_appender(
  appender: :tcp,
  server:   "localhost:8088",
  ssl:      true
)

When using self-signed certificates, or to disable verification of the server SSL certificate:

SemanticLogger.add_appender(
  appender: :tcp,
  server:   "localhost:8088",
  ssl:      {verify_mode: OpenSSL::SSL::VERIFY_NONE}
)

Example, customize the message being sent:

formatter = Proc.new do |log, logger|
  h = log.to_h(logger.host, logger.application)

  # Change time from iso8601 to seconds since epoch
  h[:timestamp] = log.time.utc.to_f

  # Render to JSON
  h.to_json
end

SemanticLogger.add_appender(
  appender: :tcp,
  server:   "localhost:8088",
  ssl:      {verify_mode: OpenSSL::SSL::VERIFY_NONE},
  formatter: formatter
)

See Net::TCPClient for the remaining options that can be set when the appender is added.

UDP Appender

The UDP appender supports sending JSON or other formatted messages to services that can accept log messages via UDP.

Send messages in JSON format over UDP:

SemanticLogger.add_appender(
  appender: :udp,
  server:   "localhost:8088"
)

Example, customize the message being sent:

formatter = Proc.new do |log, logger|
  h = log.to_h(logger.host, logger.application)

  # Change time from iso8601 to seconds since epoch
  h[:timestamp] = log.time.utc.to_f

  # Render to JSON
  h.to_json
end

SemanticLogger.add_appender(
  appender:  :udp,
  server:    "localhost:8088",
  formatter: formatter
)

Rollbar

In order to integrate Rollbar error handling into Semantic Logger it requires it’s own current thread context. As such, it cannot use a regular appender unless running in Synchronous logging mode.

As recommended by @gingerlime, to enable Rollbar for Semantic Logger add the following initializer:

SemanticLogger.on_log do |log|
  next unless log.try(:level) == :error

  err = RuntimeError.new(log.try(:message))
  err.set_backtrace(log.backtrace) if log.backtrace
  Rollbar.error(err, :log_extra => log.to_h)
end

Sentry

Usage of sentry-raven gem is possible but the gem itself is deprecated.

Use the sentry-ruby gem instead, and the corresponding appender:

SemanticLogger.add_appender(appender: :sentry_ruby)

Some of the logging context will be sent to Sentry:

SemanticLogger.tagged(transaction_name: "foo", user_id: 42, baz: "quz") do
  logger.error("some message", username: "joe", fingerprint: ["bar"])
end

Logger, log4r, etc.

Semantic Logger can log to other logging libraries:

ruby_logger = Logger.new($stdout)

# Log to an existing Ruby Logger instance
SemanticLogger.add_appender(logger: ruby_logger)

Note: :trace level messages are mapped to :debug.

Multiple Appenders

Messages can be logged to multiple appenders at the same time.

Example, log to a local file and to a remote Syslog server such as syslog-ng over TCP:

require "semantic_logger"
SemanticLogger.default_level = :trace
SemanticLogger.add_appender(file_name: "development.log", formatter: :color)
SemanticLogger.add_appender(appender: :syslog, url: "tcp://myloghost:514")

Appender Logging Levels

The logging level for each appender can be set explicitly. This supports:

Stand alone example:

require "semantic_logger"

# Set default log level for new logger instances
SemanticLogger.default_level = :info

# Log all warning messages and above to warnings.log
SemanticLogger.add_appender(file_name: "log/warnings.log", level: :warn)

# Log all trace messages and above to trace.log
SemanticLogger.add_appender(file_name: "log/trace.log", level: :trace)

logger = SemanticLogger["MyClass"]
logger.level = :trace
logger.trace "This is a trace message"
logger.info "This is an info message"
logger.warn "This is a warning message"

The output is as follows:

==> trace.log <==
2013-08-02 14:15:56.733532 T [35669:70176909690580] MyClass -- This is a trace message
2013-08-02 14:15:56.734273 I [35669:70176909690580] MyClass -- This is an info message
2013-08-02 14:15:56.735273 W [35669:70176909690580] MyClass -- This is a warning message

==> warnings.log <==
2013-08-02 14:15:56.735273 W [35669:70176909690580] MyClass -- This is a warning message

Standalone

Appenders can be created and logged to directly with the same API as for all other logging. Supports logging specific activity in the current thread to a separate appender without it being written to the appenders that have been registered in Semantic Logger.

Example:

require "semantic_logger"

appender = SemanticLogger::Appender::File.new("separate.log", level: :info, formatter: :color)

# Use appender directly, without using global Semantic Logger
appender.warn "Only send this to separate.log"

appender.measure_info "Called supplier" do
  # Call supplier ...
end

Note: Once an appender has been registered with Semantic Logger it must not be called directly, otherwise non-deterministic concurrency issues will arise when it is used across threads.

Next: Metrics ==>