module Logging::MappedDiagnosticContext

A Mapped Diagnostic Context, or MDC in short, is an instrument used to distinguish interleaved log output from different sources. Log output is typically interleaved when a server handles multiple clients near-simultaneously.

Interleaved log output can still be meaningful if each log entry from different contexts had a distinctive stamp. This is where MDCs come into play.

The MDC provides a hash of contextual messages that are identified by unique keys. These unique keys are set by the application and appended to log messages to identify groups of log events. One use of the Mapped Diagnostic Context is to store HTTP request headers associated with a Rack request. These headers can be included with all log messages emitted while generating the HTTP response.

When configured to do so, PatternLayout instances will automatically retrieve the mapped diagnostic context for the current thread with out any user intervention. This context information can be used to track user sessions in a Rails application, for example.

Note that MDCs are managed on a per thread basis. MDC operations such as `[]`, `[]=`, and `clear` affect the MDC of the current thread only. MDCs of other threads remain unaffected.

By default, when a new thread is created it will inherit the context of its parent thread. However, the `inherit` method may be used to inherit context for any other thread in the application.

Constants

NAME

The name used to retrieve the MDC from thread-local storage.

STACK_NAME

The name used to retrieve the MDC stack from thread-local storage.

Public Instance Methods

[]( key ) click to toggle source

Public: Get the context value identified with the key parameter.

key - The String identifier for the context.

Returns the value associated with the key or nil if there is no value present.

# File lib/logging/diagnostic_context.rb, line 62
def []( key )
  context.fetch(key.to_s, nil)
end
[]=( key, value ) click to toggle source

Public: Put a context value as identified with the key parameter into the current thread's context map.

key - The String identifier for the context. value - The String value to store.

Returns the value.

# File lib/logging/diagnostic_context.rb, line 50
def []=( key, value )
  clear_context
  peek.store(key.to_s, value)
end
clear() click to toggle source

Public: Clear all mapped diagnostic information if any. This method is useful in cases where the same thread can be potentially used over and over in different unrelated contexts.

Returns the MappedDiagnosticContext.

# File lib/logging/diagnostic_context.rb, line 126
def clear
  clear_context
  Thread.current.thread_variable_set(STACK_NAME, nil)
  self
end
clear_context() click to toggle source

Remove the flattened context.

# File lib/logging/diagnostic_context.rb, line 196
def clear_context
  Thread.current.thread_variable_set(NAME, nil)
  self
end
context() click to toggle source

Returns the Hash acting as the storage for this MappedDiagnosticContext. A new storage Hash is created for each Thread running in the application.

# File lib/logging/diagnostic_context.rb, line 160
def context
  c = Thread.current.thread_variable_get(NAME)

  if c.nil?
    c = if Thread.current.thread_variable_get(STACK_NAME)
      flatten(stack)
    else
      Hash.new
    end
    Thread.current.thread_variable_set(NAME, c)
  end

  return c
end
delete( key ) click to toggle source

Public: Remove the context value identified with the key parameter.

key - The String identifier for the context.

Returns the value associated with the key or nil if there is no value present.

# File lib/logging/diagnostic_context.rb, line 73
def delete( key )
  clear_context
  peek.delete(key.to_s)
end
flatten( ary ) click to toggle source

Given an Array of Hash objects, flatten all the key/value pairs from the Hash objects in the ary into a single Hash. The flattening occurs left to right. So that the key/value in the very last Hash overrides any other key from the previous Hash objcts.

ary - An Array of Hash objects.

Returns a Hash.

# File lib/logging/diagnostic_context.rb, line 229
def flatten( ary )
  return ary.first.dup if ary.length == 1

  hash = {}
  ary.each { |h| hash.update h }
  return hash
end
inherit( obj ) click to toggle source

Public: Inherit the diagnostic context of another thread. In the vast majority of cases the other thread will the parent that spawned the current thread. The diagnostic context from the parent thread is cloned before being inherited; the two diagnostic contexts can be changed independently.

Returns the MappedDiagnosticContext.

# File lib/logging/diagnostic_context.rb, line 140
def inherit( obj )
  case obj
  when Hash
    Thread.current.thread_variable_set(STACK_NAME, [obj.dup])
  when Thread
    return if Thread.current == obj
    DIAGNOSTIC_MUTEX.synchronize do
      if hash = obj.thread_variable_get(STACK_NAME)
        Thread.current.thread_variable_set(STACK_NAME, [flatten(hash)])
      end
    end
  end

  self
end
peek() click to toggle source

Returns the most current Hash from the stack of contexts.

# File lib/logging/diagnostic_context.rb, line 190
def peek
  stack.last
end
pop() click to toggle source

Public: Remove the most recently pushed Hash from the stack of contexts. If no contexts have been pushed then no action will be taken. The default context cannot be popped off the stack; please use the `clear` method if you want to remove all key/value pairs from the context.

Returns nil or the Hash removed from the stack.

# File lib/logging/diagnostic_context.rb, line 112
def pop
  return unless Thread.current.thread_variable_get(STACK_NAME)
  return unless stack.length > 1
  clear_context
  stack.pop
end
push( hash ) click to toggle source

Public: Push a new Hash of key/value pairs onto the stack of contexts.

hash - The Hash of values to push onto the context stack.

Returns this context. Raises an ArgumentError if hash is not a Hash.

# File lib/logging/diagnostic_context.rb, line 99
def push( hash )
  clear_context
  stack << sanitize(hash)
  self
end
sanitize( hash, target = {} ) click to toggle source

Given a Hash convert all keys into Strings. The values are not altered in any way. The converted keys and their values are stored in the target Hash if provided. Otherwise a new Hash is created and returned.

hash - The Hash of values to push onto the context stack. target - The target Hash to store the key value pairs.

Returns a new Hash with all keys converted to Strings. Raises an ArgumentError if hash is not a Hash.

# File lib/logging/diagnostic_context.rb, line 211
def sanitize( hash, target = {} )
  unless hash.is_a?(Hash)
    raise ArgumentError, "Expecting a Hash but received a #{hash.class.name}"
  end

  hash.each { |k,v| target[k.to_s] = v }
  return target
end
stack() click to toggle source

Returns the stack of Hash objects that are storing the diagnostic context information. This stack is guarnteed to always contain at least one Hash.

# File lib/logging/diagnostic_context.rb, line 179
def stack
  s = Thread.current.thread_variable_get(STACK_NAME)
  if s.nil?
    s = [{}]
    Thread.current.thread_variable_set(STACK_NAME, s)
  end
  return s
end
update( hash ) click to toggle source

Public: Add all the key/value pairs from the given hash to the current mapped diagnostic context. The keys will be converted to strings. Existing keys of the same name will be overwritten.

hash - The Hash of values to add to the current context.

Returns this context.

# File lib/logging/diagnostic_context.rb, line 86
def update( hash )
  clear_context
  sanitize(hash, peek)
  self
end