Module: Logging::MappedDiagnosticContext

Extended by:
MappedDiagnosticContext
Included in:
MappedDiagnosticContext
Defined in:
lib/logging/diagnostic_context.rb

Overview

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.

Constant Summary collapse

NAME =

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

:logging_mapped_diagnostic_context
STACK_NAME =

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

:logging_mapped_diagnostic_context_stack

Instance Method Summary collapse

Instance Method Details

#[](key) ⇒ Object

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.



62
63
64
# File 'lib/logging/diagnostic_context.rb', line 62

def []( key )
  context.fetch(key.to_s, nil)
end

#[]=(key, value) ⇒ Object

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.



50
51
52
53
# File 'lib/logging/diagnostic_context.rb', line 50

def []=( key, value )
  clear_context
  peek.store(key.to_s, value)
end

#clearObject

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.



126
127
128
129
130
# File 'lib/logging/diagnostic_context.rb', line 126

def clear
  clear_context
  Thread.current.thread_variable_set(STACK_NAME, nil)
  self
end

#clear_contextObject

Remove the flattened context.



196
197
198
199
# File 'lib/logging/diagnostic_context.rb', line 196

def clear_context
  Thread.current.thread_variable_set(NAME, nil)
  self
end

#contextObject

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



160
161
162
163
164
165
166
167
168
169
170
171
172
173
# 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) ⇒ Object

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.



73
74
75
76
# File 'lib/logging/diagnostic_context.rb', line 73

def delete( key )
  clear_context
  peek.delete(key.to_s)
end

#flatten(ary) ⇒ Object

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.



229
230
231
232
233
234
235
# 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) ⇒ Object

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.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# 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

#peekObject

Returns the most current Hash from the stack of contexts.



190
191
192
# File 'lib/logging/diagnostic_context.rb', line 190

def peek
  stack.last
end

#popObject

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.



112
113
114
115
116
117
# 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) ⇒ Object

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.



99
100
101
102
103
# File 'lib/logging/diagnostic_context.rb', line 99

def push( hash )
  clear_context
  stack << sanitize(hash)
  self
end

#sanitize(hash, target = {}) ⇒ Object

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.



211
212
213
214
215
216
217
218
# 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

#stackObject

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



179
180
181
182
183
184
185
186
# 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) ⇒ Object

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.



86
87
88
89
90
# File 'lib/logging/diagnostic_context.rb', line 86

def update( hash )
  clear_context
  sanitize(hash, peek)
  self
end