semantic_logger 3.2.1 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/semantic_logger.rb +22 -2
  4. data/lib/semantic_logger/appender/bugsnag.rb +2 -2
  5. data/lib/semantic_logger/appender/elasticsearch.rb +9 -1
  6. data/lib/semantic_logger/appender/file.rb +1 -1
  7. data/lib/semantic_logger/appender/graylog.rb +18 -21
  8. data/lib/semantic_logger/appender/honeybadger.rb +27 -12
  9. data/lib/semantic_logger/appender/http.rb +19 -11
  10. data/lib/semantic_logger/appender/mongodb.rb +23 -30
  11. data/lib/semantic_logger/appender/new_relic.rb +2 -2
  12. data/lib/semantic_logger/appender/splunk.rb +32 -21
  13. data/lib/semantic_logger/appender/splunk_http.rb +1 -3
  14. data/lib/semantic_logger/appender/syslog.rb +25 -10
  15. data/lib/semantic_logger/appender/tcp.rb +231 -0
  16. data/lib/semantic_logger/appender/udp.rb +106 -0
  17. data/lib/semantic_logger/appender/wrapper.rb +1 -1
  18. data/lib/semantic_logger/base.rb +9 -3
  19. data/lib/semantic_logger/formatters/base.rb +36 -0
  20. data/lib/semantic_logger/formatters/color.rb +13 -10
  21. data/lib/semantic_logger/formatters/default.rb +8 -5
  22. data/lib/semantic_logger/formatters/json.rb +10 -3
  23. data/lib/semantic_logger/formatters/raw.rb +13 -0
  24. data/lib/semantic_logger/formatters/syslog.rb +119 -0
  25. data/lib/semantic_logger/log.rb +7 -5
  26. data/lib/semantic_logger/loggable.rb +5 -0
  27. data/lib/semantic_logger/logger.rb +45 -10
  28. data/lib/semantic_logger/metrics/new_relic.rb +1 -1
  29. data/lib/semantic_logger/metrics/statsd.rb +5 -1
  30. data/lib/semantic_logger/metrics/udp.rb +80 -0
  31. data/lib/semantic_logger/semantic_logger.rb +23 -27
  32. data/lib/semantic_logger/subscriber.rb +127 -0
  33. data/lib/semantic_logger/version.rb +1 -1
  34. data/test/appender/elasticsearch_test.rb +6 -4
  35. data/test/appender/file_test.rb +12 -12
  36. data/test/appender/honeybadger_test.rb +7 -1
  37. data/test/appender/http_test.rb +4 -2
  38. data/test/appender/mongodb_test.rb +1 -2
  39. data/test/appender/splunk_http_test.rb +8 -6
  40. data/test/appender/splunk_test.rb +48 -45
  41. data/test/appender/syslog_test.rb +3 -3
  42. data/test/appender/tcp_test.rb +68 -0
  43. data/test/appender/udp_test.rb +61 -0
  44. data/test/appender/wrapper_test.rb +5 -5
  45. data/test/concerns/compatibility_test.rb +6 -6
  46. data/test/debug_as_trace_logger_test.rb +2 -2
  47. data/test/loggable_test.rb +2 -2
  48. data/test/logger_test.rb +48 -45
  49. metadata +13 -3
  50. data/lib/semantic_logger/appender/base.rb +0 -101
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93bacb9e50c85d3c705135737294436245cf1260
4
- data.tar.gz: 76f4ea34ce009dec4d498c738a149ca59a3d952f
3
+ metadata.gz: 1332704e0667fa360e4b9a632e861fa75966f3e0
4
+ data.tar.gz: ad079a8ed4ce5fb1802594e845f38a523c0a1a09
5
5
  SHA512:
6
- metadata.gz: 0658a00169d903ef6605fa5e5a29bc50f84e3a8381a6c0e9143308885bc8764ca3ba41e3691526b690d220f4eb10f6b13f4f926bf74ea4d90cf6c97b69775cf4
7
- data.tar.gz: 3d2554eefc189f28a9bc1411a4b360583bca3342e531ba9f7cfa0a7fcbcc7f37012f97e60ff82eb39dcdb495a518443b09fa246ea8387a60853be618b909d0e2
6
+ metadata.gz: 1c33e421728430664dba299821ceb31816dda9068c94f1cefbaecd3ce64b9b4cd9be16f6109bbd9ac52fbac38e935e46d4935f09088c7a1e508c4625704ad4da
7
+ data.tar.gz: c8199f834fdeee217a50ff07c682e3954c7b42bc2d48514af3f9fbbee698358eb59e5fd834d34b57f521437c8a6c785a575333abb4640a0814f043bdd386574b
data/README.md CHANGED
@@ -38,7 +38,7 @@ instead of Semantic Logger directly since it will automatically replace the Rail
38
38
  ## Supports
39
39
 
40
40
  Semantic Logger is tested and supported on the following Ruby platforms:
41
- - Ruby 2.0 and above
41
+ - Ruby 2.1 and above
42
42
  - JRuby 1.7 and above
43
43
  - JRuby 9.0 and above
44
44
  - Rubinius 2.5 and above
@@ -10,9 +10,22 @@ module SemanticLogger
10
10
  autoload :Log, 'semantic_logger/log'
11
11
  autoload :Logger, 'semantic_logger/logger'
12
12
  autoload :Loggable, 'semantic_logger/loggable'
13
+ autoload :Subscriber, 'semantic_logger/subscriber'
13
14
 
14
15
  module Appender
15
- autoload :Base, 'semantic_logger/appender/base'
16
+ # DEPRECATED, use SemanticLogger::AnsiColors
17
+ AnsiColors = SemanticLogger::AnsiColors
18
+
19
+ # DEPRECATED: use SemanticLogger::Formatters::Color.new
20
+ def self.colorized_formatter
21
+ SemanticLogger::Formatters::Color.new
22
+ end
23
+
24
+ # DEPRECATED: use SemanticLogger::Formatters::Json.new
25
+ def self.json_formatter
26
+ SemanticLogger::Formatters::Json.new
27
+ end
28
+
16
29
  autoload :Bugsnag, 'semantic_logger/appender/bugsnag'
17
30
  autoload :Elasticsearch, 'semantic_logger/appender/elasticsearch'
18
31
  autoload :File, 'semantic_logger/appender/file'
@@ -24,6 +37,8 @@ module SemanticLogger
24
37
  autoload :Splunk, 'semantic_logger/appender/splunk'
25
38
  autoload :SplunkHttp, 'semantic_logger/appender/splunk_http'
26
39
  autoload :Syslog, 'semantic_logger/appender/syslog'
40
+ autoload :Tcp, 'semantic_logger/appender/tcp'
41
+ autoload :Udp, 'semantic_logger/appender/udp'
27
42
  autoload :Wrapper, 'semantic_logger/appender/wrapper'
28
43
  end
29
44
 
@@ -32,14 +47,18 @@ module SemanticLogger
32
47
  end
33
48
 
34
49
  module Formatters
50
+ autoload :Base, 'semantic_logger/formatters/base'
35
51
  autoload :Color, 'semantic_logger/formatters/color'
36
52
  autoload :Default, 'semantic_logger/formatters/default'
37
53
  autoload :Json, 'semantic_logger/formatters/json'
54
+ autoload :Raw, 'semantic_logger/formatters/raw'
55
+ autoload :Syslog, 'semantic_logger/formatters/syslog'
38
56
  end
39
57
 
40
58
  module Metrics
41
59
  autoload :NewRelic, 'semantic_logger/metrics/new_relic'
42
60
  autoload :Statsd, 'semantic_logger/metrics/statsd'
61
+ autoload :Udp, 'semantic_logger/metrics/udp'
43
62
  end
44
63
 
45
64
  if defined?(JRuby)
@@ -50,8 +69,9 @@ module SemanticLogger
50
69
  end
51
70
  # @formatter:on
52
71
 
53
- # Flush all appenders at exit, waiting for outstanding messages on the queue
72
+ # Close and flush all appenders at exit, waiting for outstanding messages on the queue
54
73
  # to be written first
55
74
  at_exit do
75
+ # Cannot call #close since test frameworks use at_exit to run loaded tests
56
76
  SemanticLogger.flush
57
77
  end
@@ -9,7 +9,7 @@ end
9
9
  # Example:
10
10
  # SemanticLogger.add_appender(appender: :bugsnag)
11
11
  #
12
- class SemanticLogger::Appender::Bugsnag < SemanticLogger::Appender::Base
12
+ class SemanticLogger::Appender::Bugsnag < SemanticLogger::Subscriber
13
13
  # Create Bugsnag Error / Exception Appender
14
14
  #
15
15
  # Parameters
@@ -43,7 +43,7 @@ class SemanticLogger::Appender::Bugsnag < SemanticLogger::Appender::Base
43
43
 
44
44
  # Returns [Hash] of parameters to send to Bugsnag.
45
45
  def call(log, logger)
46
- h = log.to_h
46
+ h = log.to_h(host, application)
47
47
  h[:severity] = log_level(log)
48
48
  h.delete(:time)
49
49
  h.delete(:exception)
@@ -26,7 +26,7 @@ class SemanticLogger::Appender::Elasticsearch < SemanticLogger::Appender::Http
26
26
  # Override the log level for this appender.
27
27
  # Default: SemanticLogger.default_level
28
28
  #
29
- # formatter: [Object|Proc]
29
+ # formatter: [Object|Proc|Symbol|Hash]
30
30
  # An instance of a class that implements #call, or a Proc to be used to format
31
31
  # the output from this appender
32
32
  # Default: Use the built-in formatter (See: #call)
@@ -36,6 +36,14 @@ class SemanticLogger::Appender::Elasticsearch < SemanticLogger::Appender::Http
36
36
  # regular expression. All other messages will be ignored.
37
37
  # Proc: Only include log messages where the supplied Proc returns true
38
38
  # The Proc must return true or false.
39
+ #
40
+ # host: [String]
41
+ # Name of this host to appear in log messages.
42
+ # Default: SemanticLogger.host
43
+ #
44
+ # application: [String]
45
+ # Name of this application to appear in log messages.
46
+ # Default: SemanticLogger.application
39
47
  def initialize(options, &block)
40
48
  options = options.dup
41
49
  @index = options.delete(:index) || 'semantic_logger'
@@ -4,7 +4,7 @@
4
4
  #
5
5
  module SemanticLogger
6
6
  module Appender
7
- class File < SemanticLogger::Appender::Base
7
+ class File < SemanticLogger::Subscriber
8
8
 
9
9
  # Create a File Logger appender instance.
10
10
  #
@@ -18,7 +18,7 @@ end
18
18
  #
19
19
  # In the Graylog Web UI search screen, it is recommended to include the following fields:
20
20
  # `duration`, `level`, `message`, `metric`, `name`, `tags
21
- class SemanticLogger::Appender::Graylog < SemanticLogger::Appender::Base
21
+ class SemanticLogger::Appender::Graylog < SemanticLogger::Subscriber
22
22
  # Map Semantic Logger levels to Graylog levels
23
23
  LEVEL_MAP = {
24
24
  fatal: GELF::FATAL,
@@ -42,14 +42,6 @@ class SemanticLogger::Appender::Graylog < SemanticLogger::Appender::Base
42
42
  # 'tcp://localhost:12201'
43
43
  # Default: 'udp://localhost:12201'
44
44
  #
45
- # host: [String]
46
- # Name of this host to appear in log messages.
47
- # Default: Socket.gethostname
48
- #
49
- # application: [String]
50
- # Name of this application to appear in log messages.
51
- # Default: SemanticLogger.application
52
- #
53
45
  # max_size: [String]
54
46
  # Max udp packet size. Ignored when protocol is :tcp
55
47
  # Default: "WAN"
@@ -68,10 +60,18 @@ class SemanticLogger::Appender::Graylog < SemanticLogger::Appender::Base
68
60
  # regular expression. All other messages will be ignored.
69
61
  # Proc: Only include log messages where the supplied Proc returns true
70
62
  # The Proc must return true or false.
63
+ #
64
+ # host: [String]
65
+ # Name of this host to appear in log messages.
66
+ # Default: SemanticLogger.host
67
+ #
68
+ # application: [String]
69
+ # Name of this application to appear in log messages.
70
+ # Default: SemanticLogger.application
71
71
  def initialize(options = {}, &block)
72
- @gelf_options = options.dup
73
- @url = @gelf_options.delete(:url) || 'udp://localhost:12201'
74
- @max_size = @gelf_options.delete(:max_size) || 'WAN'
72
+ options = options.dup
73
+ @url = options.delete(:url) || 'udp://localhost:12201'
74
+ @max_size = options.delete(:max_size) || 'WAN'
75
75
 
76
76
  uri = URI.parse(@url)
77
77
  @server = uri.host
@@ -80,28 +80,25 @@ class SemanticLogger::Appender::Graylog < SemanticLogger::Appender::Base
80
80
 
81
81
  raise(ArgumentError, "Invalid protocol value: #{protocol}. Must be :udp or :tcp") unless [:udp, :tcp].include?(protocol)
82
82
 
83
- @gelf_options[:protocol] = protocol == :tcp ? GELF::Protocol::TCP : GELF::Protocol::UDP
84
- @gelf_options[:facility] = @gelf_options.delete(:application) || SemanticLogger.application
83
+ options[:protocol] = protocol == :tcp ? GELF::Protocol::TCP : GELF::Protocol::UDP
85
84
 
86
- options = {
87
- level: @gelf_options.delete(:level),
88
- filter: @gelf_options.delete(:filter),
89
- formatter: @gelf_options.delete(:formatter)
90
- }
91
- reopen
85
+ @gelf_options = options
86
+ options = extract_subscriber_options!(options)
92
87
 
93
88
  super(options, &block)
89
+ reopen
94
90
  end
95
91
 
96
92
  # Re-open after process fork
97
93
  def reopen
94
+ @gelf_options[:facility] = application
98
95
  @notifier = GELF::Notifier.new(@server, @port, @max_size, @gelf_options)
99
96
  @notifier.collect_file_and_line = false
100
97
  end
101
98
 
102
99
  # Returns [Hash] of parameters to send
103
100
  def call(log, logger)
104
- h = log.to_h
101
+ h = log.to_h(host, application)
105
102
  h.delete(:time)
106
103
  h[:timestamp] = log.time.utc.to_f
107
104
  h[:level] = logger.map_level(log)
@@ -9,7 +9,7 @@ end
9
9
  # Example:
10
10
  # SemanticLogger.add_appender(appender: :honeybadger)
11
11
  #
12
- class SemanticLogger::Appender::Honeybadger < SemanticLogger::Appender::Base
12
+ class SemanticLogger::Appender::Honeybadger < SemanticLogger::Subscriber
13
13
  # Create Appender
14
14
  #
15
15
  # Parameters
@@ -17,7 +17,7 @@ class SemanticLogger::Appender::Honeybadger < SemanticLogger::Appender::Base
17
17
  # Override the log level for this appender.
18
18
  # Default: :error
19
19
  #
20
- # formatter: [Object|Proc]
20
+ # formatter: [Object|Proc|Symbol|Hash]
21
21
  # An instance of a class that implements #call, or a Proc to be used to format
22
22
  # the output from this appender
23
23
  # Default: Use the built-in formatter (See: #call)
@@ -27,30 +27,45 @@ class SemanticLogger::Appender::Honeybadger < SemanticLogger::Appender::Base
27
27
  # regular expression. All other messages will be ignored.
28
28
  # Proc: Only include log messages where the supplied Proc returns true
29
29
  # The Proc must return true or false.
30
+ #
31
+ # host: [String]
32
+ # Name of this host to appear in log messages.
33
+ # Default: SemanticLogger.host
34
+ #
35
+ # application: [String]
36
+ # Name of this application to appear in log messages.
37
+ # Default: SemanticLogger.application
30
38
  def initialize(options = {}, &block)
31
- options = {level: options} unless options.is_a?(Hash)
32
- @options = options.dup
33
- level = @options.delete(:level) || :error
34
-
35
- super(level, &block)
39
+ options = options.is_a?(Hash) ? options.dup : {level: options}
40
+ options[:level] ||= :error
41
+ super(options, &block)
36
42
  end
37
43
 
38
44
  # Send an error notification to honeybadger
39
45
  def log(log)
40
46
  return false unless should_log?(log)
41
47
 
48
+ context = formatter.call(log, self)
42
49
  if log.exception
43
- Honeybadger.notify(log.exception, log.to_h)
50
+ context.delete(:exception)
51
+ Honeybadger.notify(log.exception, context)
44
52
  else
45
53
  message = {
46
- error_class: log.name,
47
- error_message: log.message,
48
- backtrace: log.backtrace,
49
- context: log.to_h,
54
+ error_class: context.delete(:name),
55
+ error_message: context.delete(:message),
56
+ context: context
50
57
  }
58
+ message[:backtrace] = log.backtrace if log.backtrace
51
59
  Honeybadger.notify(message)
52
60
  end
53
61
  true
54
62
  end
55
63
 
64
+ private
65
+
66
+ # Use Raw Formatter by default
67
+ def default_formatter
68
+ SemanticLogger::Formatters::Raw.new
69
+ end
70
+
56
71
  end
@@ -15,7 +15,7 @@ require 'json'
15
15
  # appender: :http,
16
16
  # url: 'http://localhost:8088/path'
17
17
  # )
18
- class SemanticLogger::Appender::Http < SemanticLogger::Appender::Base
18
+ class SemanticLogger::Appender::Http < SemanticLogger::Subscriber
19
19
  attr_accessor :username, :application, :host, :compress, :header,
20
20
  :open_timeout, :read_timeout, :continue_timeout
21
21
  attr_reader :http, :url, :server, :port, :path, :ssl_options
@@ -83,22 +83,19 @@ class SemanticLogger::Appender::Http < SemanticLogger::Appender::Base
83
83
  @ssl_options = options.delete(:ssl)
84
84
  @username = options.delete(:username)
85
85
  @password = options.delete(:password)
86
- @application = options.delete(:application) || 'Semantic Logger'
87
- @host = options.delete(:host) || SemanticLogger.host
88
86
  @compress = options.delete(:compress) || false
89
87
  @open_timeout = options.delete(:open_timeout) || 2.0
90
88
  @read_timeout = options.delete(:read_timeout) || 1.0
91
89
  @continue_timeout = options.delete(:continue_timeout) || 1.0
92
90
 
93
- unless options.has_key?(:formatter)
94
- options[:formatter] = block || (respond_to?(:call) ? self : SemanticLogger::Formatters::Json.new)
95
- end
96
-
97
91
  raise(ArgumentError, 'Missing mandatory parameter :url') unless @url
98
92
 
99
93
  @header = {
100
94
  'Accept' => 'application/json',
101
- 'Content-Type' => 'application/json'
95
+ 'Content-Type' => 'application/json',
96
+ # On Ruby v2.0 and greater, Net::HTTP.new already uses a persistent connection if the server allows it
97
+ 'Connection' => 'keep-alive',
98
+ 'Keep-Alive' => '300'
102
99
  }
103
100
  @header['Content-Encoding'] = 'gzip' if @compress
104
101
 
@@ -119,16 +116,21 @@ class SemanticLogger::Appender::Http < SemanticLogger::Appender::Base
119
116
  else
120
117
  @port ||= HTTP.http_default_port
121
118
  end
122
-
123
- reopen
119
+ @http = nil
124
120
 
125
121
  # Pass on the level and custom formatter if supplied
126
122
  super(options)
123
+ reopen
127
124
  end
128
125
 
129
126
  # Re-open after process fork
130
127
  def reopen
131
- # On Ruby v2.0 and greater, Net::HTTP.new uses a persistent connection if the server allows it
128
+ # Close open connection if any
129
+ begin
130
+ @http.finish if @http
131
+ rescue IOError
132
+ end
133
+
132
134
  @http = Net::HTTP.new(server, port)
133
135
 
134
136
  if @ssl_options
@@ -142,6 +144,7 @@ class SemanticLogger::Appender::Http < SemanticLogger::Appender::Base
142
144
  @http.open_timeout = @open_timeout
143
145
  @http.read_timeout = @read_timeout
144
146
  @http.continue_timeout = @continue_timeout
147
+ @http.start
145
148
  end
146
149
 
147
150
  # Forward log messages to HTTP Server
@@ -153,6 +156,11 @@ class SemanticLogger::Appender::Http < SemanticLogger::Appender::Base
153
156
 
154
157
  private
155
158
 
159
+ # Use JSON Formatter by default
160
+ def default_formatter
161
+ SemanticLogger::Formatters::Json.new
162
+ end
163
+
156
164
  def compress_data(data)
157
165
  str = StringIO.new
158
166
  gz = Zlib::GzipWriter.new(str)
@@ -48,9 +48,9 @@ module SemanticLogger
48
48
  #
49
49
  # # Log some messages
50
50
  # logger.info 'This message is written to mongo as a document'
51
- class MongoDB < SemanticLogger::Appender::Base
51
+ class MongoDB < SemanticLogger::Subscriber
52
52
  attr_reader :db, :collection_name, :collection
53
- attr_accessor :host, :write_concern, :application
53
+ attr_accessor :write_concern
54
54
 
55
55
  # Create a MongoDB Appender instance
56
56
  #
@@ -67,14 +67,6 @@ module SemanticLogger
67
67
  # see: http://docs.mongodb.org/manual/reference/write-concern/
68
68
  # Default: 0
69
69
  #
70
- # host: [String]
71
- # host name to include in the document logged to Mongo
72
- # Default: SemanticLogger.host_name
73
- #
74
- # application: [String]
75
- # Name of the application to include in the document written to mongo
76
- # Default: nil (None)
77
- #
78
70
  # collection_size: [Integer]
79
71
  # The size of the MongoDB capped collection to create in bytes
80
72
  # Default: 1 GB
@@ -91,7 +83,7 @@ module SemanticLogger
91
83
  # Override the log level for this appender.
92
84
  # Default: SemanticLogger.default_level
93
85
  #
94
- # formatter: [Object|Proc]
86
+ # formatter: [Object|Proc|Symbol]
95
87
  # An instance of a class that implements #call, or a Proc to be used to format
96
88
  # the output from this appender
97
89
  # Default: Use the built-in formatter (See: #call)
@@ -101,25 +93,30 @@ module SemanticLogger
101
93
  # regular expression. All other messages will be ignored.
102
94
  # Proc: Only include log messages where the supplied Proc returns true
103
95
  # The Proc must return true or false.
96
+ #
97
+ # host: [String]
98
+ # Name of this host to appear in log messages.
99
+ # Default: SemanticLogger.host
100
+ #
101
+ # application: [String]
102
+ # Name of this application to appear in log messages.
103
+ # Default: SemanticLogger.application
104
104
  def initialize(options = {}, &block)
105
- options = options.dup
106
- @db = options.delete(:db) || raise('Missing mandatory parameter :db')
107
- @collection_name = options.delete(:collection_name) || 'semantic_logger'
108
- @host = options.delete(:host) || options.delete(:host_name) || SemanticLogger.host
109
- @write_concern = options.delete(:write_concern) || 0
110
- @application = options.delete(:application) || SemanticLogger.application
105
+ options = options.dup
106
+ @db = options.delete(:db) || raise('Missing mandatory parameter :db')
107
+ @collection_name = options.delete(:collection_name) || 'semantic_logger'
108
+ @write_concern = options.delete(:write_concern) || 0
111
109
 
112
110
  # Create a collection that will hold the lesser of 1GB space or 10K documents
113
- @collection_size = options.delete(:collection_size) || 1024**3
114
- @collection_max = options.delete(:collection_max)
115
-
116
- reopen
111
+ @collection_size = options.delete(:collection_size) || 1024**3
112
+ @collection_max = options.delete(:collection_max)
117
113
 
118
114
  # Create the collection and necessary indexes
119
115
  create_indexes
120
116
 
121
117
  # Set the log level and formatter
122
118
  super(options, &block)
119
+ reopen
123
120
  end
124
121
 
125
122
  # After forking an active process call #reopen to re-open
@@ -160,15 +157,6 @@ module SemanticLogger
160
157
  db.get_last_error
161
158
  end
162
159
 
163
- # Default log formatter
164
- # Replace this formatter by supplying a Block to the initializer
165
- def call(log, logger)
166
- h = log.to_h
167
- h[:host] = host
168
- h[:application] = application
169
- h
170
- end
171
-
172
160
  # Log the message to MongoDB
173
161
  def log(log)
174
162
  return false unless should_log?(log)
@@ -178,6 +166,11 @@ module SemanticLogger
178
166
  true
179
167
  end
180
168
 
169
+ private
170
+
171
+ def default_formatter
172
+ SemanticLogger::Formatters::Raw.new
173
+ end
181
174
  end
182
175
  end
183
176
  end