Skip to content
This repository has been archived by the owner on Dec 8, 2020. It is now read-only.

Commit

Permalink
Fix conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
binarylogic committed Feb 19, 2017
2 parents d65373d + 5e008b5 commit 4e12a48
Show file tree
Hide file tree
Showing 21 changed files with 178 additions and 110 deletions.
42 changes: 37 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ encouraged. In cases where the data is meaningful, consider [logging a custom ev

Use `Logger` as normal:

```elixir
```ruby
logger.info("My log message")

# My log message @metadata {"level": "info", "context": {...}}
Expand All @@ -128,17 +128,48 @@ Timber will never deviate from the public `::Logger` interface in *any* way.

</p></details>

<details><summary><strong>Tagging logs</strong></summary><p>

Need a quick and easy way to identify a log? Use tags!:

```ruby
logger.info(message: "My log message", tag: "tag")

# My log message @metadata {"level": "info", "tags": ["tag"], "context": {...}}
```

Multiple tags:

```ruby
logger.info(message: "My log message", tags: ["tag1", "tag2"])

# My log message @metadata {"level": "info", "tags": ["tag1", "tag2"], "context": {...}}
```

Using `ActiveSupport::TaggedLogging`? It works with that as well:

```ruby
logger.tagged("tag") do
logger.info(message: "My log message", tags: ["important", "slow"])
end

# My log message @metadata {"level": "info", "tags": ["tag"], "context": {...}}
```

</p></details>

<details><summary><strong>Custom events</strong></summary><p>

1. Log a structured Hash (simplest)

```ruby
Logger.warn message: "Payment rejected", type: :payment_rejected,
data: {customer_id: "abcd1234", amount: 100, reason: "Card expired"}
Logger.warn message: "Payment rejected", payment_rejected: {customer_id: "abcd1234", amount: 100, reason: "Card expired"}

# Payment rejected @metadata {"level": "warn", "event": {"payment_rejected": {"customer_id": "abcd1234", "amount": 100, "reason": "Card expired"}}, "context": {...}}
```

* The hash can *only* have a `:message` and "event type" key, where `:payment_rejected` is the event type in the above example.

2. Log a Struct (recommended)

Defining structs for your important events just feels oh so good :) It creates a strong contract
Expand Down Expand Up @@ -190,7 +221,7 @@ value.
1. Add a Hash (simplest)

```ruby
Timber::CurrentContext.with({type: :build, data: {version: "1.0.0"}}) do
Timber::CurrentContext.with({build: {version: "1.0.0"}}) do
logger.info("My log message")
end
Expand Down Expand Up @@ -219,6 +250,7 @@ value.
</p></details>



## Installation

```ruby
Expand Down Expand Up @@ -320,6 +352,6 @@ Checkout our [docs](https://timber.io/docs) for a comprehensive list of install

---

<p align="center" style="background: #140f2a;">
<p align="center" style="background: #221f40;">
<a href="http://github.com/timberio/timber-ruby"><img src="http://files.timber.io/images/ruby-library-readme-log-truth.png" height="947" /></a>
</p>
16 changes: 9 additions & 7 deletions lib/timber/contexts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
require "timber/contexts/organization"
require "timber/contexts/runtime"
require "timber/contexts/system"
require "timber/contexts/tags"
require "timber/contexts/user"

module Timber
Expand All @@ -12,21 +11,24 @@ module Contexts
# Protocol for casting objects into a `Timber::Context`.
#
# @example Casting a hash
# Timber::Contexts.build({type: :build, data: {version: "1.0.0"}})
# Timber::Contexts.build(deploy: {version: "1.0.0"})
def self.build(obj)
if obj.is_a?(::Timber::Context)
obj
elsif obj.respond_to?(:to_timber_context)
obj.to_timber_context
elsif obj.is_a?(Hash) && obj.key?(:type) && obj.key?(:data)
elsif obj.is_a?(Hash) && obj.length == 1
type = obj.keys.first
data = obj.values.first

Contexts::Custom.new(
type: obj[:type],
data: obj[:data]
type: type,
data: data
)
elsif obj.is_a?(Struct) && obj.respond_to?(:type)
Events::Custom.new(
Contexts::Custom.new(
type: obj.type,
data: obj.respond_to?(:hash) ? obj.hash : obj.to_h # ruby 1.9.3 does not have to_h
data: obj.respond_to?(:to_h) ? obj.to_h : Timber::Util::Struct.to_hash(obj) # ruby 1.9.3 does not have to_h
)
else
nil
Expand Down
3 changes: 2 additions & 1 deletion lib/timber/contexts/system.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ class System < Context
attr_reader :pid

def initialize(attributes)
@pid = attributes[:pid]
@pid = attributes[:pid] || raise(ArgumentError.new(":pid is required"))
@pid = @pid.to_s
end

def as_json(_options = {})
Expand Down
22 changes: 0 additions & 22 deletions lib/timber/contexts/tags.rb

This file was deleted.

14 changes: 11 additions & 3 deletions lib/timber/current_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,17 @@ def hash(*args)
# @note Because context is included with every log line, it is recommended that you limit this
# to only neccessary data.
#
# @example Adding a custom context
# custom_context = Timber::Contexts::Custom.new(type: :organization, data: %{id: 1, name: "Timber"})
# Timber::CurrentContext.with(custom_context) do
# @example Adding a custom context with a map
# Timber::CurrentContext.with({build: {version: "1.0.0"}}) do
# # ... anything logged here will include the context ...
# end
#
# @example Adding a custom context with a struct
# BuildContext = Struct.new(:version) do
# def type; :build; end
# end
# build_context = BuildContext.new("1.0.0")
# Timber::CurrentContext.with(build_context) do
# # ... anything logged here will include the context ...
# end
# # Be sure to checkout Timber::Contexts! These are officially supported and many of these
Expand Down
12 changes: 8 additions & 4 deletions lib/timber/events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@ def self.build(obj)
obj
elsif obj.respond_to?(:to_timber_event)
obj.to_timber_event
elsif obj.is_a?(Hash) && obj.key?(:message) && obj.key?(:type) && obj.key?(:data)
elsif obj.is_a?(Hash) && obj.key?(:message) && obj.length == 2
event = obj.select { |k,v| k != :message }
type = event.keys.first
data = event.values.first

Events::Custom.new(
type: obj[:type],
type: type,
message: obj[:message],
data: obj[:data]
data: data
)
elsif obj.is_a?(Struct) && obj.respond_to?(:message) && obj.respond_to?(:type)
Events::Custom.new(
type: obj.type,
message: obj.message,
data: obj.respond_to?(:hash) ? obj.hash : obj.to_h # ruby 1.9.3 does not have to_h
data: obj.respond_to?(:to_h) ? obj.to_h : Timber::Util::Struct.to_hash(obj) # ruby 1.9.3 does not have to_h :(
)
else
nil
Expand Down
3 changes: 2 additions & 1 deletion lib/timber/events/http_server_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class HTTPResponse < Timber::Event
def initialize(attributes)
@status = attributes[:status] || raise(ArgumentError.new(":status is required"))
@time_ms = attributes[:time_ms] || raise(ArgumentError.new(":time_ms is required"))
@time_ms = @time_ms.round(6)
@additions = attributes[:additions]
end

Expand All @@ -19,7 +20,7 @@ def to_hash
alias to_h to_hash

def as_json(_options = {})
{:server_side_app => {:http_response => to_hash}}
{:server_side_app => {:http_server_response => to_hash}}
end

def message
Expand Down
1 change: 1 addition & 0 deletions lib/timber/events/sql_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class SQLQuery < Timber::Event
def initialize(attributes)
@sql = attributes[:sql] || raise(ArgumentError.new(":sql is required"))
@time_ms = attributes[:time_ms] || raise(ArgumentError.new(":time_ms is required"))
@time_ms = @time_ms.round(6)
@message = attributes[:message] || raise(ArgumentError.new(":message is required"))
end

Expand Down
1 change: 1 addition & 0 deletions lib/timber/events/template_render.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def initialize(attributes)
@message = attributes[:message] || raise(ArgumentError.new(":message is required"))
@name = attributes[:name] || raise(ArgumentError.new(":name is required"))
@time_ms = attributes[:time_ms] || raise(ArgumentError.new(":time_ms is required"))
@time_ms = @time_ms.round(6)
end

def to_hash
Expand Down
16 changes: 11 additions & 5 deletions lib/timber/log_entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ module Timber
# `Logger` and the log device that you set it up with.
class LogEntry #:nodoc:
DT_PRECISION = 6.freeze
SCHEMA = "https://raw.githubusercontent.com/timberio/log-event-json-schema/1.2.3/schema.json".freeze

attr_reader :level, :time, :progname, :message, :context_snapshot, :event
attr_reader :context_snapshot, :event, :level, :message, :progname, :tags, :time

# Creates a log entry suitable to be sent to the Timber API.
# @param severity [Integer] the log level / severity
Expand All @@ -16,11 +17,12 @@ class LogEntry #:nodoc:
# @param event [Timber.Event] structured data representing the log line event. This should be
# an instance of `Timber.Event`.
# @return [LogEntry] the resulting LogEntry object
def initialize(level, time, progname, message, context_snapshot, event)
def initialize(level, time, progname, message, context_snapshot, event, tags)
@level = level
@time = time.utc
@progname = progname
@message = message
@tags = tags

context_snapshot = {} if context_snapshot.nil?
system_context = Contexts::System.new(pid: Process.pid)
Expand All @@ -32,7 +34,7 @@ def initialize(level, time, progname, message, context_snapshot, event)

def as_json(options = {})
options ||= {}
hash = {level: level, dt: formatted_dt, message: message}
hash = {:level => level, :dt => formatted_dt, :message => message, :tags => tags}

if !event.nil?
hash[:event] = event
Expand All @@ -42,7 +44,9 @@ def as_json(options = {})
hash[:context] = context_snapshot
end

if options[:only]
hash[:"$schema"] = SCHEMA

hash = if options[:only]
hash.select do |key, _value|
options[:only].include?(key)
end
Expand All @@ -53,10 +57,12 @@ def as_json(options = {})
else
hash
end

Util::Hash.compact(hash)
end

def to_json(options = {})
Util::Hash.compact(as_json(options)).to_json
as_json(options).to_json
end

def to_msgpack(*args)
Expand Down
19 changes: 15 additions & 4 deletions lib/timber/logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ module Timber
# logger.info "Payment rejected for customer #{customer_id}"
#
# @example Using a Hash
# # The :message, :type, and :data keys are required
# # The :message key is required, the other additional key is your event type and data
# # :type is the namespace used in timber for the :data
# logger.info message: "Payment rejected", type: :payment_rejected, data: {customer_id: customer_id, amount: 100}
# logger.info message: "Payment rejected", payment_rejected: {customer_id: customer_id, amount: 100}
#
# @example Using a Struct (a simple, more structured way, to define events)
# PaymentRejectedEvent = Struct.new(:customer_id, :amount, :reason) do
Expand Down Expand Up @@ -76,13 +76,24 @@ class Formatter
def build_log_entry(severity, time, progname, msg)
level = SEVERITY_MAP.fetch(severity)
context_snapshot = CurrentContext.instance.snapshot
tags = extract_active_support_tagged_logging_tags
tags += [msg.delete(:tag)] if msg.is_a?(Hash) && msg.key?(:tag)
tags += msg.delete(:tags) if msg.is_a?(Hash) && msg.key?(:tags)
event = Events.build(msg)

if event
LogEntry.new(level, time, progname, event.message, context_snapshot, event)
LogEntry.new(level, time, progname, event.message, context_snapshot, event, tags)
else
LogEntry.new(level, time, progname, msg, context_snapshot, nil)
LogEntry.new(level, time, progname, msg, context_snapshot, nil, tags)
end
end

# Because of all the crazy ways Rails has attempted this we need this crazy method.
def extract_active_support_tagged_logging_tags
Thread.current[:activesupport_tagged_logging_tags] ||
Thread.current["activesupport_tagged_logging_tags:#{object_id}"] ||
[]
end
end

# Structures your log messages into Timber's hybrid format, which makes
Expand Down
43 changes: 0 additions & 43 deletions lib/timber/probes/active_support_tagged_logging.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,56 +17,13 @@ def call(severity, timestamp, progname, msg)
super(severity, timestamp, progname, "#{tags_text}#{msg}")
end
end

def push_tags(*tags)
_timber_original_push_tags(*tags).tap do
if current_tags.size > 0
context = Contexts::Tags.new(values: current_tags)
CurrentContext.add(context)
end
end
end

def pop_tags(size = 1)
_timber_original_pop_tags(size).tap do
if current_tags.size == 0
CurrentContext.remove(Contexts::Tags)
else
context = Contexts::Tags.new(values: current_tags)
CurrentContext.add(context)
end
end
end
end
end
end

module LoggerMethods
def self.included(klass)
klass.class_eval do
alias_method :_timber_original_push_tags, :push_tags
alias_method :_timber_original_pop_tags, :pop_tags

def push_tags(*tags)
_timber_original_push_tags(*tags).tap do
if current_tags.size > 0
context = Contexts::Tags.new(values: current_tags)
CurrentContext.add(context)
end
end
end

def pop_tags(size = 1)
_timber_original_pop_tags(size).tap do
if current_tags.size == 0
CurrentContext.remove(Contexts::Tags)
else
context = Contexts::Tags.new(values: current_tags)
CurrentContext.add(context)
end
end
end

def add(severity, message = nil, progname = nil, &block)
if message.nil?
if block_given?
Expand Down
Loading

0 comments on commit 4e12a48

Please sign in to comment.