timber 2.0.24 → 2.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/CHANGELOG +3 -0
  4. data/README.md +314 -59
  5. data/bin/timber +11 -2
  6. data/lib/timber.rb +2 -7
  7. data/lib/timber/cli.rb +16 -28
  8. data/lib/timber/cli/api.rb +80 -14
  9. data/lib/timber/cli/api/application.rb +30 -0
  10. data/lib/timber/cli/config_file.rb +66 -0
  11. data/lib/timber/cli/file_helper.rb +43 -0
  12. data/lib/timber/cli/installer.rb +58 -0
  13. data/lib/timber/cli/installers.rb +37 -0
  14. data/lib/timber/cli/installers/other.rb +47 -0
  15. data/lib/timber/cli/installers/rails.rb +255 -0
  16. data/lib/timber/cli/installers/root.rb +189 -0
  17. data/lib/timber/cli/io.rb +97 -0
  18. data/lib/timber/cli/io/ansi.rb +22 -0
  19. data/lib/timber/cli/io/messages.rb +213 -0
  20. data/lib/timber/cli/os_helper.rb +53 -0
  21. data/lib/timber/config.rb +97 -43
  22. data/lib/timber/config/integrations.rb +63 -0
  23. data/lib/timber/config/integrations/rack.rb +74 -0
  24. data/lib/timber/context.rb +13 -10
  25. data/lib/timber/contexts.rb +1 -0
  26. data/lib/timber/contexts/custom.rb +16 -3
  27. data/lib/timber/contexts/http.rb +10 -3
  28. data/lib/timber/contexts/organization.rb +4 -0
  29. data/lib/timber/contexts/release.rb +46 -0
  30. data/lib/timber/contexts/runtime.rb +7 -1
  31. data/lib/timber/contexts/session.rb +8 -1
  32. data/lib/timber/contexts/system.rb +5 -1
  33. data/lib/timber/contexts/user.rb +9 -2
  34. data/lib/timber/current_context.rb +43 -11
  35. data/lib/timber/events/controller_call.rb +4 -0
  36. data/lib/timber/events/custom.rb +13 -5
  37. data/lib/timber/events/exception.rb +4 -0
  38. data/lib/timber/events/http_client_request.rb +4 -0
  39. data/lib/timber/events/http_client_response.rb +4 -0
  40. data/lib/timber/events/http_server_request.rb +5 -0
  41. data/lib/timber/events/http_server_response.rb +15 -3
  42. data/lib/timber/events/sql_query.rb +3 -0
  43. data/lib/timber/events/template_render.rb +3 -0
  44. data/lib/timber/integration.rb +40 -0
  45. data/lib/timber/integrations.rb +21 -14
  46. data/lib/timber/integrations/action_controller.rb +18 -0
  47. data/lib/timber/integrations/action_controller/log_subscriber.rb +2 -0
  48. data/lib/timber/integrations/action_controller/log_subscriber/timber_log_subscriber.rb +6 -0
  49. data/lib/timber/integrations/action_dispatch.rb +23 -0
  50. data/lib/timber/integrations/action_dispatch/debug_exceptions.rb +2 -0
  51. data/lib/timber/integrations/action_view.rb +18 -0
  52. data/lib/timber/integrations/action_view/log_subscriber.rb +2 -0
  53. data/lib/timber/integrations/action_view/log_subscriber/timber_log_subscriber.rb +10 -0
  54. data/lib/timber/integrations/active_record.rb +18 -0
  55. data/lib/timber/integrations/active_record/log_subscriber.rb +2 -0
  56. data/lib/timber/integrations/active_record/log_subscriber/timber_log_subscriber.rb +8 -0
  57. data/lib/timber/integrations/rack.rb +12 -2
  58. data/lib/timber/integrations/rack/exception_event.rb +38 -5
  59. data/lib/timber/integrations/rack/http_context.rb +4 -6
  60. data/lib/timber/integrations/rack/http_events.rb +177 -27
  61. data/lib/timber/integrations/rack/middleware.rb +28 -0
  62. data/lib/timber/integrations/rack/session_context.rb +5 -6
  63. data/lib/timber/integrations/rack/user_context.rb +90 -43
  64. data/lib/timber/integrations/rails.rb +22 -0
  65. data/lib/timber/integrations/rails/rack_logger.rb +2 -0
  66. data/lib/timber/integrator.rb +18 -3
  67. data/lib/timber/log_devices/http.rb +107 -99
  68. data/lib/timber/log_devices/http/dropping_sized_queue.rb +26 -0
  69. data/lib/timber/log_devices/http/flushable_sized_queue.rb +42 -0
  70. data/lib/timber/log_entry.rb +14 -2
  71. data/lib/timber/logger.rb +51 -36
  72. data/lib/timber/overrides.rb +2 -0
  73. data/lib/timber/overrides/active_support_3_tagged_logging.rb +103 -0
  74. data/lib/timber/overrides/active_support_tagged_logging.rb +53 -90
  75. data/lib/timber/timer.rb +21 -0
  76. data/lib/timber/util/hash.rb +1 -1
  77. data/lib/timber/util/http_event.rb +16 -3
  78. data/lib/timber/version.rb +1 -1
  79. data/spec/support/timber.rb +2 -3
  80. data/spec/timber/cli/installers/rails_spec.rb +160 -0
  81. data/spec/timber/cli/installers/root_spec.rb +100 -0
  82. data/spec/timber/config_spec.rb +28 -0
  83. data/spec/timber/current_context_spec.rb +61 -12
  84. data/spec/timber/events/custom_spec.rb +13 -2
  85. data/spec/timber/events/exception_spec.rb +15 -0
  86. data/spec/timber/events/http_server_request_spec.rb +3 -3
  87. data/spec/timber/integrations/rack/http_events_spec.rb +101 -0
  88. data/spec/timber/log_devices/http_spec.rb +20 -4
  89. data/spec/timber/log_entry_spec.rb +2 -1
  90. data/spec/timber/logger_spec.rb +8 -8
  91. metadata +40 -9
  92. data/benchmarks/rails.rb +0 -122
  93. data/lib/timber/cli/application.rb +0 -28
  94. data/lib/timber/cli/install.rb +0 -196
  95. data/lib/timber/cli/io_helper.rb +0 -65
  96. data/lib/timber/cli/messages.rb +0 -180
  97. data/lib/timber/integrations/active_support/tagged_logging.rb +0 -71
@@ -0,0 +1,63 @@
1
+ require "timber/config/integrations/rack"
2
+ require "timber/integrations/action_controller"
3
+ require "timber/integrations/action_view"
4
+ require "timber/integrations/active_record"
5
+ require "timber/integrations/rack"
6
+
7
+ module Timber
8
+ class Config
9
+ # Convenience module for accessing the various `Timber::Integrations::*` classes
10
+ # through the {Timber::Config} object. Timber couples configuration with the class
11
+ # responsibls for implementing it. This provides for a tighter design, but also
12
+ # requires the user to understand and access the various classes. This module aims
13
+ # to provide a simple ruby-like configuration interface for internal Timber classes.
14
+ #
15
+ # For example:
16
+ #
17
+ # config = Timber::Config.instance
18
+ # config.integrations.active_record.silence = true
19
+ module Integrations
20
+ extend self
21
+
22
+ # Convenience method for accessing the {Timber::Integrations::ActionController} class
23
+ # specific configuration.
24
+ #
25
+ # @example
26
+ # config = Timber::Config.instance
27
+ # config.integrations.action_controller.silence = true
28
+ def action_controller
29
+ Timber::Integrations::ActionController
30
+ end
31
+
32
+ # Convenience method for accessing the {Timber::IIntegrations::ActionView} class
33
+ # specific configuration.
34
+ #
35
+ # @example
36
+ # config = Timber::Config.instance
37
+ # config.integrations.action_view.silence = true
38
+ def action_view
39
+ Timber::Integrations::ActionView
40
+ end
41
+
42
+ # Convenience method for accessing the {Timber::IIntegrations::ActiveRecord} class
43
+ # specific configuration.
44
+ #
45
+ # @example
46
+ # config = Timber::Config.instance
47
+ # config.integrations.active_record.silence = true
48
+ def active_record
49
+ Timber::Integrations::ActiveRecord
50
+ end
51
+
52
+ # Convenience method for accessing the various `Timber::IIntegrations::Rack::*`
53
+ # classes. See {Rack} for a list of methods available.
54
+ #
55
+ # @example
56
+ # config = Timber::Config.instance
57
+ # config.integrations.rack.http_events.enabled = true
58
+ def rack
59
+ Rack
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,74 @@
1
+ module Timber
2
+ class Config
3
+ module Integrations
4
+ # Convenience module for accessing the various `Timber::Integrations::Rack::*` classes
5
+ # through the {Timber::Config} object. Timber couples configuration with the class
6
+ # responsibls for implementing it. This provides for a tighter design, but also
7
+ # requires the user to understand and access the various classes. This module aims
8
+ # to provide a simple ruby-like configuration interface for internal Timber classes.
9
+ #
10
+ # For example:
11
+ #
12
+ # config = Timber::Config.instance
13
+ # config.integrations.rack.http_events.enabled = false
14
+ module Rack
15
+ extend self
16
+
17
+ # Convenience method for accessing the {Timber::Integrations::Rack::ExceptionEvent}
18
+ # middleware class specific configuration. See {Timber::Integrations::Rack::ExceptionEvent}
19
+ # for a list of methods available.
20
+ #
21
+ # @example
22
+ # config = Timber::Config.instance
23
+ # config.integrations.rack.exception_event.enabled = false
24
+ def exception_event
25
+ Timber::Integrations::Rack::ExceptionEvent
26
+ end
27
+
28
+ # Convenience method for accessing the {Timber::Integrations::Rack::HTTPContext}
29
+ # middleware class specific configuration. See {Timber::Integrations::Rack::HTTPContext}
30
+ # for a list of methods available.
31
+ #
32
+ # @example
33
+ # config = Timber::Config.instance
34
+ # config.integrations.rack.http_context.enabled = false
35
+ def http_context
36
+ Timber::Integrations::Rack::HTTPContext
37
+ end
38
+
39
+ # Convenience method for accessing the {Timber::Integrations::Rack::HTTPEvents}
40
+ # middleware class specific configuration. See {Timber::Integrations::Rack::HTTPEvents}
41
+ # for a list of methods available.
42
+ #
43
+ # @example
44
+ # config = Timber::Config.instance
45
+ # config.integrations.rack.http_events.enabled = false
46
+ def http_events
47
+ Timber::Integrations::Rack::HTTPEvents
48
+ end
49
+
50
+ # Convenience method for accessing the {Timber::Integrations::Rack::SessionContext}
51
+ # middleware class specific configuration. See {Timber::Integrations::Rack::SessionContext}
52
+ # for a list of methods available.
53
+ #
54
+ # @example
55
+ # config = Timber::Config.instance
56
+ # config.integrations.rack.session_context.enabled = false
57
+ def session_context
58
+ Timber::Integrations::Rack::SessionContext
59
+ end
60
+
61
+ # Convenience method for accessing the {Timber::Integrations::Rack::UserContext}
62
+ # middleware class specific configuration. See {Timber::Integrations::Rack::UserContext}
63
+ # for a list of methods available.
64
+ #
65
+ # @example
66
+ # config = Timber::Config.instance
67
+ # config.integrations.rack.user_context.enabled = false
68
+ def user_context
69
+ Timber::Integrations::Rack::UserContext
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -3,25 +3,28 @@ module Timber
3
3
  # @private
4
4
  class Context
5
5
  class << self
6
+ # The keyspace is the key used when storing the context.
7
+ # For example:
8
+ #
9
+ # {:build => {:version => "1.0.0"}}
10
+ #
11
+ # The keyspace in the above context is `:build`. This is required
12
+ # because it prevents key name conflicts. Without the keyspace
13
+ # it very possible another context type might also have a `:version`
14
+ # attribute.
6
15
  def keyspace
7
16
  @keyspace || raise(NotImplementedError.new)
8
17
  end
9
18
  end
10
19
 
11
- def keyspace
12
- self.class.keyspace
13
- end
14
-
20
+ # Returns a simple structure sufficient for encoding. We use
21
+ # `as_json` as the name since this is a ruby pattern.
15
22
  def as_json(options = {})
16
23
  raise NotImplementedError.new
17
24
  end
18
25
 
19
- def to_json(options = {})
20
- as_json.to_json(options)
21
- end
22
-
23
- def to_msgpack(*args)
24
- as_json.to_msgpack(*args)
26
+ def keyspace
27
+ self.class.keyspace
25
28
  end
26
29
  end
27
30
  end
@@ -1,6 +1,7 @@
1
1
  require "timber/contexts/custom"
2
2
  require "timber/contexts/http"
3
3
  require "timber/contexts/organization"
4
+ require "timber/contexts/release"
4
5
  require "timber/contexts/runtime"
5
6
  require "timber/contexts/session"
6
7
  require "timber/contexts/system"
@@ -1,10 +1,22 @@
1
+ require "timber/context"
2
+ require "timber/util"
3
+
1
4
  module Timber
2
5
  module Contexts
3
6
  # Custom contexts allow you to add application specific context not covered elsewhere.
7
+ # Any data added this way will be included in your logs. A good example is worker job
8
+ # IDs. When processing a job you might add the job ID to the context, allowing you to
9
+ # view *all* logs generated while processing that job, not just the logs that contain
10
+ # the ID.
11
+ #
12
+ # Note in the example below all custom contexts must contain a root key. This is to
13
+ # ensure attribute names and types never clash across your contexts. It gives you
14
+ # much cleaner pallete to organize your data on.
4
15
  #
5
- # @example Adding a context
16
+ # @example Adding a custom context
6
17
  # logger.with_context(build: {version: "1.0.0"}) do
7
- # # ... anything logged here will have the context ...
18
+ # # anything logged here will have the custom context above
19
+ # # when this block exits the context will no longer be included
8
20
  # end
9
21
  class Custom < Context
10
22
  @keyspace = :custom
@@ -16,7 +28,8 @@ module Timber
16
28
  @data = attributes[:data] || raise(ArgumentError.new(":data is required"))
17
29
  end
18
30
 
19
- def as_json(_options = {})
31
+ # Builds a hash representation of containing simply objects, suitable for serialization.
32
+ def as_json(options = {})
20
33
  {Timber::Util::Object.try(type, :to_sym) => data}
21
34
  end
22
35
  end
@@ -1,8 +1,14 @@
1
+ require "timber/context"
2
+
1
3
  module Timber
2
4
  module Contexts
3
- # The HTTP content tracks the current HTTP request being processed. This serves
4
- # as join data across your logs, allowing you to query all logs for any attribute
5
- # presented here. For example, viewing all logs for a given request_id.
5
+ # The HTTP context adds data about the current HTTP request being processed to your logs.
6
+ # This allows your to tail and filter by this data. A very useful piece of data this
7
+ # captures is the request ID. This gives you the ability to trace requests and view logs
8
+ # for a specific request only. For example, say you've searched your logs and found the
9
+ # specific line you are looking for, but it lacks context. With Timber you can simply
10
+ # click the request ID and "zoom out" to view all logs for that request. This gives you
11
+ # complete picture of how the log line in questio was generated.
6
12
  #
7
13
  # @note This context should be installed automatically through integrations,
8
14
  # such as the {Intregrations::Rack::HTTPContext} rack middleware.
@@ -18,6 +24,7 @@ module Timber
18
24
  @request_id = attributes[:request_id]
19
25
  end
20
26
 
27
+ # Builds a hash representation of containing simply objects, suitable for serialization.
21
28
  def as_json(_options = {})
22
29
  {:method => method, :path => path, :remote_addr => remote_addr, :request_id => request_id}
23
30
  end
@@ -1,3 +1,6 @@
1
+ require "timber/context"
2
+ require "timber/util"
3
+
1
4
  module Timber
2
5
  module Contexts
3
6
  # The organization context tracks the organization of the currently
@@ -25,6 +28,7 @@ module Timber
25
28
  @name = attributes[:name]
26
29
  end
27
30
 
31
+ # Builds a hash representation of containing simply objects, suitable for serialization.
28
32
  def as_json(_options = {})
29
33
  {id: Timber::Util::Object.try(id, :to_s), name: name}
30
34
  end
@@ -0,0 +1,46 @@
1
+ require "timber/context"
2
+ require "timber/util"
3
+
4
+ module Timber
5
+ module Contexts
6
+ # The release context tracks application releases / versions / deploys.
7
+ # To automatically set this context, see {.from_env}.
8
+ class Release < Context
9
+ @keyspace = :release
10
+
11
+ class << self
12
+ # Builds a release context based on environment variables. Simply add the
13
+ # `RELEASE_COMMIT`, `RELEASE_CREATED_AT`, or the `RELEASE_VERSION` env vars
14
+ # to get this context automatially. All are optional, but at least one
15
+ # must be present.
16
+ #
17
+ # If you're on Heroku, simply enable dyno metadata to get this automatically:
18
+ # https://devcenter.heroku.com/articles/dyno-metadata
19
+ def from_env
20
+ commit_hash = ENV['RELEASE_COMMIT'] || ENV['HEROKU_SLUG_COMMIT']
21
+ created_at = ENV['RELEASE_CREATED_AT'] || ENV['HEROKU_RELEASE_CREATED_AT']
22
+ version = ENV['RELEASE_VERSION'] || ENV['HEROKU_RELEASE_VERSION']
23
+
24
+ if commit_hash || created_at || version
25
+ new(commit_hash: commit_hash, created_at: created_at, version: version)
26
+ else
27
+ nil
28
+ end
29
+ end
30
+ end
31
+
32
+ attr_reader :commit_hash, :created_at, :version
33
+
34
+ def initialize(attributes)
35
+ @commit_hash = attributes[:commit_hash]
36
+ @created_at = attributes[:created_at]
37
+ @version = attributes[:version]
38
+ end
39
+
40
+ # Builds a hash representation of containing simply objects, suitable for serialization.
41
+ def as_json(_options = {})
42
+ {commit_hash: commit_hash, created_at: created_at, version: version}
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,6 +1,11 @@
1
+ require "timber/context"
2
+
1
3
  module Timber
2
4
  module Contexts
3
- # Tracks OS level process information, such as the process ID.
5
+ # The runtime context adds current runtime data to your logs, such as the file, line number,
6
+ # class or module name, etc. This makes it easy to tail and search your logs by their
7
+ # origin in your code. For example, if you are debugging a specific class, you can narrow
8
+ # by that class and see only it's logs.
4
9
  class Runtime < Context
5
10
  @keyspace = :runtime
6
11
 
@@ -15,6 +20,7 @@ module Timber
15
20
  @module_name = attributes[:module_name]
16
21
  end
17
22
 
23
+ # Builds a hash representation of containing simply objects, suitable for serialization.
18
24
  def as_json(_options = {})
19
25
  {application: application, class_name: class_name, file: file, function: function,
20
26
  line: line, module_name: module_name}
@@ -1,6 +1,12 @@
1
+ require "timber/context"
2
+ require "timber/util"
3
+
1
4
  module Timber
2
5
  module Contexts
3
- # The session context tracks the current session for the given user.
6
+ # The session context adds the current session ID to your logs. This allows your
7
+ # to tail and filter logs by specific session IDs. Moreover, it gives you a unique
8
+ # identifier to report on user activity by session. This way your logs can tell the
9
+ # story of how many time a user has engaged your site.
4
10
  #
5
11
  # @note This is tracked automatically with the {Integrations::Rack::SessionContext} rack
6
12
  # middleware.
@@ -13,6 +19,7 @@ module Timber
13
19
  @id = attributes[:id] || raise(ArgumentError.new(":id is required"))
14
20
  end
15
21
 
22
+ # Builds a hash representation of containing simply objects, suitable for serialization.
16
23
  def as_json(_options = {})
17
24
  {id: Timber::Util::Object.try(id, :to_s)}
18
25
  end
@@ -1,6 +1,9 @@
1
+ require "timber/context"
2
+ require "timber/util"
3
+
1
4
  module Timber
2
5
  module Contexts
3
- # Tracks OS level process information, such as the process ID.
6
+ # The system context tracks OS level process information, such as the process ID.
4
7
  class System < Context
5
8
  @keyspace = :system
6
9
 
@@ -12,6 +15,7 @@ module Timber
12
15
  @pid = @pid.to_s
13
16
  end
14
17
 
18
+ # Builds a hash representation of containing simply objects, suitable for serialization.
15
19
  def as_json(_options = {})
16
20
  {hostname: hostname, pid: Timber::Util::Object.try(pid, :to_s)}
17
21
  end
@@ -1,9 +1,15 @@
1
+ require "timber/context"
2
+ require "timber/util"
3
+
1
4
  module Timber
2
5
  module Contexts
3
- # The user context tracks the currently authenticated user.
6
+ # The user context adds data about the currently authenticated user to your logs.
7
+ # By adding this context all of your logs will contain user information. This allows
8
+ # filter and tail logs by specific users.
4
9
  #
5
10
  # @note This is tracked automatically with the {Integrations::Rack::UserContext} rack
6
- # middleware.
11
+ # middleware for supported authentication frameworks. See {Integrations::Rack::UserContext}
12
+ # for more details.
7
13
  class User < Context
8
14
  @keyspace = :user
9
15
 
@@ -15,6 +21,7 @@ module Timber
15
21
  @email = attributes[:email]
16
22
  end
17
23
 
24
+ # Builds a hash representation of containing simply objects, suitable for serialization.
18
25
  def as_json(_options = {})
19
26
  {id: Timber::Util::Object.try(id, :to_s), name: name, email: email}
20
27
  end
@@ -1,5 +1,7 @@
1
1
  require "singleton"
2
2
 
3
+ require "timber/contexts/release"
4
+
3
5
  module Timber
4
6
  # Holds the current context in a thread safe memory storage. This context is
5
7
  # appended to every log line. Think of context as join data between your log lines,
@@ -13,30 +15,43 @@ module Timber
13
15
  THREAD_NAMESPACE = :_timber_current_context.freeze
14
16
 
15
17
  class << self
16
- # Convenience method for {#with}. See {#with} for full details and examples.
18
+ # Convenience method for {CurrentContext#with}. See {CurrentContext#with} for more info.
17
19
  def with(*args, &block)
18
20
  instance.with(*args, &block)
19
21
  end
20
22
 
21
- # Convenience method for {#add}. See {#add} for full details and examples.
23
+ # Convenience method for {CurrentContext#add}. See {CurrentContext#add} for more info.
22
24
  def add(*args)
23
25
  instance.add(*args)
24
26
  end
25
27
 
26
- def hash(*args)
27
- instance.hash(*args)
28
+ # Convenience method for {CurrentContext#fetch}. See {CurrentContext#fetch} for more info.
29
+ def fetch(*args)
30
+ instance.fetch(*args)
28
31
  end
29
32
 
30
- # Convenience method for {#remove}. See {#remove} for full details and examples.
33
+ # Convenience method for {CurrentContext#remove}. See {CurrentContext#remove} for more info.
31
34
  def remove(*args)
32
35
  instance.remove(*args)
33
36
  end
34
37
 
38
+ # Convenience method for {CurrentContext#reset}. See {CurrentContext#reset} for more info.
35
39
  def reset(*args)
36
40
  instance.reset(*args)
37
41
  end
38
42
  end
39
43
 
44
+ # Instantiates a new object, automatically adding context based on env variables (if present).
45
+ # For example, the {Contexts::ReleaseContext} is automatically added if the proper environment
46
+ # variables are present. Please see that class for more details.
47
+ def initialize
48
+ super
49
+ release_context = Contexts::Release.from_env
50
+ if release_context
51
+ add(release_context)
52
+ end
53
+ end
54
+
40
55
  # Adds a context and then removes it when the block is finished executing.
41
56
  #
42
57
  # @note Because context is included with every log line, it is recommended that you limit this
@@ -85,12 +100,13 @@ module Timber
85
100
  hash[key] = json
86
101
  end
87
102
  end
103
+ expire_cache!
104
+ self
88
105
  end
89
106
 
90
- # The internal hash that is maintained. It is recommended that you use {#with} and {#add}
91
- # for hash maintenance.
92
- def hash
93
- Thread.current[THREAD_NAMESPACE] ||= {}
107
+ # Fetch a specific context by key.
108
+ def fetch(*args)
109
+ hash.fetch(*args)
94
110
  end
95
111
 
96
112
  # Removes a context. If you wish to remove by key, or some other way, use {#hash} and
@@ -113,18 +129,34 @@ module Timber
113
129
  end
114
130
  end
115
131
  end
132
+ expire_cache!
133
+ self
116
134
  end
117
135
 
118
136
  # Resets the context to be blank. Use this carefully! This will remove *any* context,
119
137
  # include context that is automatically included with Timber.
120
138
  def reset
121
139
  hash.clear
140
+ expire_cache!
141
+ self
122
142
  end
123
143
 
124
144
  # Snapshots the current context so that you get a moment in time representation of the context,
125
- # since the context can change as execution proceeds.
145
+ # since the context can change as execution proceeds. Note that individual contexts
146
+ # should be immutable, and we implement snapshot caching as a result of this assumption.
126
147
  def snapshot
127
- hash.clone
148
+ @snapshot ||= hash.clone
128
149
  end
150
+
151
+ private
152
+ # The internal hash that is maintained. Use {#with} and {#add} for hash maintenance.
153
+ def hash
154
+ Thread.current[THREAD_NAMESPACE] ||= {}
155
+ end
156
+
157
+ # Hook to clear any caching implement in this class
158
+ def expire_cache!
159
+ @snapshot = nil
160
+ end
129
161
  end
130
162
  end