lumberjack 1.0.13 → 1.2.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +129 -0
- data/{MIT_LICENSE → MIT_LICENSE.txt} +0 -0
- data/README.md +142 -23
- data/VERSION +1 -1
- data/lib/lumberjack.rb +70 -22
- data/lib/lumberjack/context.rb +35 -0
- data/lib/lumberjack/device.rb +22 -8
- data/lib/lumberjack/device/date_rolling_log_file.rb +9 -9
- data/lib/lumberjack/device/log_file.rb +14 -3
- data/lib/lumberjack/device/multi.rb +46 -0
- data/lib/lumberjack/device/null.rb +1 -3
- data/lib/lumberjack/device/rolling_log_file.rb +45 -21
- data/lib/lumberjack/device/size_rolling_log_file.rb +10 -10
- data/lib/lumberjack/device/writer.rb +92 -61
- data/lib/lumberjack/formatter.rb +97 -28
- data/lib/lumberjack/formatter/date_time_formatter.rb +25 -0
- data/lib/lumberjack/formatter/exception_formatter.rb +25 -2
- data/lib/lumberjack/formatter/id_formatter.rb +23 -0
- data/lib/lumberjack/formatter/object_formatter.rb +12 -0
- data/lib/lumberjack/formatter/pretty_print_formatter.rb +4 -4
- data/lib/lumberjack/formatter/string_formatter.rb +1 -1
- data/lib/lumberjack/formatter/strip_formatter.rb +12 -0
- data/lib/lumberjack/formatter/structured_formatter.rb +63 -0
- data/lib/lumberjack/log_entry.rb +44 -16
- data/lib/lumberjack/logger.rb +275 -69
- data/lib/lumberjack/rack.rb +3 -2
- data/lib/lumberjack/rack/context.rb +18 -0
- data/lib/lumberjack/rack/request_id.rb +4 -4
- data/lib/lumberjack/rack/unit_of_work.rb +1 -1
- data/lib/lumberjack/severity.rb +11 -10
- data/lib/lumberjack/tag_formatter.rb +96 -0
- data/lib/lumberjack/tagged_logger_support.rb +66 -0
- data/lib/lumberjack/tagged_logging.rb +29 -0
- data/lib/lumberjack/tags.rb +42 -0
- data/lib/lumberjack/template.rb +81 -33
- data/lumberjack.gemspec +31 -0
- metadata +26 -53
- data/Rakefile +0 -40
- data/spec/device/date_rolling_log_file_spec.rb +0 -73
- data/spec/device/log_file_spec.rb +0 -48
- data/spec/device/null_spec.rb +0 -12
- data/spec/device/rolling_log_file_spec.rb +0 -151
- data/spec/device/size_rolling_log_file_spec.rb +0 -58
- data/spec/device/writer_spec.rb +0 -118
- data/spec/formatter/exception_formatter_spec.rb +0 -20
- data/spec/formatter/inspect_formatter_spec.rb +0 -13
- data/spec/formatter/pretty_print_formatter_spec.rb +0 -14
- data/spec/formatter/string_formatter_spec.rb +0 -12
- data/spec/formatter_spec.rb +0 -45
- data/spec/log_entry_spec.rb +0 -69
- data/spec/logger_spec.rb +0 -411
- data/spec/lumberjack_spec.rb +0 -29
- data/spec/rack/request_id_spec.rb +0 -48
- data/spec/rack/unit_of_work_spec.rb +0 -26
- data/spec/severity_spec.rb +0 -23
- data/spec/spec_helper.rb +0 -32
- data/spec/template_spec.rb +0 -34
data/lib/lumberjack/rack.rb
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
module Lumberjack
|
4
4
|
module Rack
|
5
|
-
|
6
|
-
|
5
|
+
require_relative "rack/unit_of_work"
|
6
|
+
require_relative "rack/request_id"
|
7
|
+
require_relative "rack/context"
|
7
8
|
end
|
8
9
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literals: true
|
2
|
+
|
3
|
+
module Lumberjack
|
4
|
+
module Rack
|
5
|
+
# Middleware to create a global context for Lumberjack for the scope of a rack request.
|
6
|
+
class Context
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
Lumberjack.context do
|
13
|
+
@app.call(env)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -6,17 +6,17 @@ module Lumberjack
|
|
6
6
|
# The format is expected to be a random UUID and only the first chunk is used for terseness
|
7
7
|
# if the abbreviated argument is true.
|
8
8
|
class RequestId
|
9
|
-
REQUEST_ID = "action_dispatch.request_id"
|
10
|
-
|
9
|
+
REQUEST_ID = "action_dispatch.request_id"
|
10
|
+
|
11
11
|
def initialize(app, abbreviated = false)
|
12
12
|
@app = app
|
13
13
|
@abbreviated = abbreviated
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def call(env)
|
17
17
|
request_id = env[REQUEST_ID]
|
18
18
|
if request_id && @abbreviated
|
19
|
-
request_id = request_id.split(
|
19
|
+
request_id = request_id.split("-", 2).first
|
20
20
|
end
|
21
21
|
Lumberjack.unit_of_work(request_id) do
|
22
22
|
@app.call(env)
|
data/lib/lumberjack/severity.rb
CHANGED
@@ -3,20 +3,21 @@
|
|
3
3
|
module Lumberjack
|
4
4
|
# The standard severity levels for logging messages.
|
5
5
|
module Severity
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
WARN =
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
# Backward compatibilty with 1.0 API
|
7
|
+
DEBUG = ::Logger::Severity::DEBUG
|
8
|
+
INFO = ::Logger::Severity::INFO
|
9
|
+
WARN = ::Logger::Severity::WARN
|
10
|
+
ERROR = ::Logger::Severity::ERROR
|
11
|
+
FATAL = ::Logger::Severity::FATAL
|
12
|
+
UNKNOWN = ::Logger::Severity::UNKNOWN
|
13
|
+
|
14
|
+
SEVERITY_LABELS = %w[DEBUG INFO WARN ERROR FATAL UNKNOWN].freeze
|
15
|
+
|
15
16
|
class << self
|
16
17
|
def level_to_label(severity)
|
17
18
|
SEVERITY_LABELS[severity] || SEVERITY_LABELS.last
|
18
19
|
end
|
19
|
-
|
20
|
+
|
20
21
|
def label_to_level(label)
|
21
22
|
SEVERITY_LABELS.index(label.to_s.upcase) || UNKNOWN
|
22
23
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lumberjack
|
4
|
+
# Class for formatting tags. You can register a default formatter and tag
|
5
|
+
# name specific formatters. Formatters can be either `Lumberjack::Formatter`
|
6
|
+
# objects or any object that responds to `call`.
|
7
|
+
#
|
8
|
+
# tag_formatter = Lumberjack::TagFormatter.new.default(Lumberjack::Formatter.new)
|
9
|
+
# tag_formatter.add(["password", "email"]) { |value| "***" }
|
10
|
+
# tag_formatter.add("finished_at", Lumberjack::Formatter::DateTimeFormatter.new("%Y-%m-%dT%H:%m:%S%z"))
|
11
|
+
class TagFormatter
|
12
|
+
def initialize
|
13
|
+
@formatters = {}
|
14
|
+
@default_formatter = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add a default formatter applied to all tag values. This can either be a Lumberjack::Formatter
|
18
|
+
# or an object that responds to `call` or a block.
|
19
|
+
def default(formatter = nil, &block)
|
20
|
+
formatter ||= block
|
21
|
+
formatter = dereference_formatter(formatter)
|
22
|
+
@default_formatter = formatter
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
# Remove the default formatter.
|
27
|
+
def remove_default
|
28
|
+
@default_formatter = nil
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Add a formatter for specific tag names. This can either be a Lumberjack::Formatter
|
33
|
+
# or an object that responds to `call` or a block. The default formatter will not be
|
34
|
+
# applied.
|
35
|
+
def add(names, formatter = nil, &block)
|
36
|
+
formatter ||= block
|
37
|
+
formatter = dereference_formatter(formatter)
|
38
|
+
if formatter.nil?
|
39
|
+
remove(key)
|
40
|
+
else
|
41
|
+
Array(names).each do |name|
|
42
|
+
@formatters[name.to_s] = formatter
|
43
|
+
end
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# Remove formatters for specific tag names. The default formatter will still be applied.
|
49
|
+
def remove(names)
|
50
|
+
Array(names).each do |name|
|
51
|
+
@formatters.delete(name.to_s)
|
52
|
+
end
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# Remove all formatters.
|
57
|
+
def clear
|
58
|
+
@default_formatter = nil
|
59
|
+
@formatters.clear
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
# Format a hash of tags using the formatters
|
64
|
+
def format(tags)
|
65
|
+
return nil if tags.nil?
|
66
|
+
if @default_formatter.nil? && (@formatters.empty? || (@formatters.keys & tags.keys).empty?)
|
67
|
+
tags
|
68
|
+
else
|
69
|
+
formatted = {}
|
70
|
+
tags.each do |name, value|
|
71
|
+
formatter = (@formatters[name.to_s] || @default_formatter)
|
72
|
+
if formatter.is_a?(Lumberjack::Formatter)
|
73
|
+
value = formatter.format(value)
|
74
|
+
elsif formatter.respond_to?(:call)
|
75
|
+
value = formatter.call(value)
|
76
|
+
end
|
77
|
+
formatted[name.to_s] = value
|
78
|
+
end
|
79
|
+
formatted
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def dereference_formatter(formatter)
|
86
|
+
if formatter.is_a?(TaggedLoggerSupport::Formatter)
|
87
|
+
formatter.__formatter
|
88
|
+
elsif formatter.is_a?(Symbol)
|
89
|
+
formatter_class_name = "#{formatter.to_s.gsub(/(^|_)([a-z])/) { |m| $~[2].upcase }}Formatter"
|
90
|
+
Formatter.const_get(formatter_class_name).new
|
91
|
+
else
|
92
|
+
formatter
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
module Lumberjack
|
7
|
+
# Methods to make Lumberjack::Logger API compatible with ActiveSupport::TaggedLogger.
|
8
|
+
module TaggedLoggerSupport
|
9
|
+
class Formatter < DelegateClass(Lumberjack::Formatter)
|
10
|
+
extend Forwardable
|
11
|
+
def_delegators :@logger, :tagged, :push_tags, :pop_tags, :clear_tags!
|
12
|
+
|
13
|
+
def initialize(formatter:, logger:)
|
14
|
+
@logger = logger
|
15
|
+
@formatter = formatter
|
16
|
+
super(formatter)
|
17
|
+
end
|
18
|
+
|
19
|
+
def current_tags
|
20
|
+
tags = @logger.instance_variable_get(:@tags)
|
21
|
+
if tags.is_a?(Hash)
|
22
|
+
Array(tags["tagged"])
|
23
|
+
else
|
24
|
+
[]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def tags_text
|
29
|
+
tags = current_tags
|
30
|
+
if tags.any?
|
31
|
+
tags.collect { |tag| "[#{tag}] " }.join
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def __formatter
|
36
|
+
@formatter
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Compatibility with ActiveSupport::TaggedLogging which only supports adding tags as strings.
|
41
|
+
# If a tag looks like "key:value" or "key=value", it will be added as a key value pair.
|
42
|
+
# Otherwise it will be appended to a list named "tagged".
|
43
|
+
def tagged(*tags, &block)
|
44
|
+
tag_hash = {}
|
45
|
+
tags.flatten.each do |tag|
|
46
|
+
tagged_values = Array(tag_hash["tagged"] || self.tags["tagged"])
|
47
|
+
tag_hash["tagged"] = tagged_values + [tag]
|
48
|
+
end
|
49
|
+
tag(tag_hash, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def push_tags(*tags)
|
53
|
+
tagged(*tags)
|
54
|
+
end
|
55
|
+
|
56
|
+
def pop_tags(size = 1)
|
57
|
+
tagged_values = Array(@tags["tagged"])
|
58
|
+
tagged_values = (tagged_values.size > size ? tagged_values[0, tagged_values.size - size] : nil)
|
59
|
+
tag("tagged" => tagged_values)
|
60
|
+
end
|
61
|
+
|
62
|
+
def clear_tags!
|
63
|
+
tag("tagged" => nil)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lumberjack
|
4
|
+
# Monkey patch for ActiveSupport::TaggedLogger so it doesn't blow up when
|
5
|
+
# a Lumberjack logger is trying to be wrapped. This module will be automatically
|
6
|
+
# included in ActiveSupport::TaggedLogger if activesupport is already loaded.
|
7
|
+
module TaggedLogging
|
8
|
+
class << self
|
9
|
+
def included(base)
|
10
|
+
base.singleton_class.send(:prepend, ClassMethods)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def new(logger)
|
16
|
+
if logger.is_a?(Lumberjack::Logger)
|
17
|
+
logger = logger.tagged_logger! unless logger.respond_to?(:tagged)
|
18
|
+
logger
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if defined?(ActiveSupport::TaggedLogging)
|
28
|
+
ActiveSupport::TaggedLogging.include(Lumberjack::TaggedLogging)
|
29
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lumberjack
|
4
|
+
class Tags
|
5
|
+
class << self
|
6
|
+
# Transform hash keys to strings. This method exists for optimization and backward compatibility.
|
7
|
+
# If a hash already has string keys, it will be returned as is.
|
8
|
+
def stringify_keys(hash)
|
9
|
+
return nil if hash.nil?
|
10
|
+
if hash.keys.all? { |key| key.is_a?(String) }
|
11
|
+
hash
|
12
|
+
elsif hash.respond_to?(:transform_keys)
|
13
|
+
hash.transform_keys(&:to_s)
|
14
|
+
else
|
15
|
+
copy = {}
|
16
|
+
hash.each do |key, value|
|
17
|
+
copy[key.to_s] = value
|
18
|
+
end
|
19
|
+
copy
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Ensure keys are strings and expand any values in a hash that are Proc's by calling them and replacing
|
24
|
+
# the value with the result. This allows setting global tags with runtime values.
|
25
|
+
def expand_runtime_values(hash)
|
26
|
+
return nil if hash.nil?
|
27
|
+
if hash.all? { |key, value| key.is_a?(String) && !value.is_a?(Proc) }
|
28
|
+
return hash
|
29
|
+
end
|
30
|
+
|
31
|
+
copy = {}
|
32
|
+
hash.each do |key, value|
|
33
|
+
if value.is_a?(Proc) && (value.arity == 0 || value.arity == -1)
|
34
|
+
value = value.call
|
35
|
+
end
|
36
|
+
copy[key.to_s] = value
|
37
|
+
end
|
38
|
+
copy
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/lumberjack/template.rb
CHANGED
@@ -4,70 +4,118 @@ module Lumberjack
|
|
4
4
|
# A template converts entries to strings. Templates can contain the following place holders to
|
5
5
|
# reference log entry values:
|
6
6
|
#
|
7
|
-
# *
|
8
|
-
# *
|
9
|
-
# *
|
10
|
-
# *
|
11
|
-
# *
|
7
|
+
# * :time
|
8
|
+
# * :severity
|
9
|
+
# * :progname
|
10
|
+
# * :tags
|
11
|
+
# * :message
|
12
|
+
#
|
13
|
+
# Any other words prefixed with a colon will be substituted with the value of the tag with that name.
|
14
|
+
# If your tag name contains characters other than alpha numerics and the underscore, you must surround it
|
15
|
+
# with curly brackets: `:{http.request-id}`.
|
12
16
|
class Template
|
13
|
-
TEMPLATE_ARGUMENT_ORDER = %w
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
TEMPLATE_ARGUMENT_ORDER = %w[:time :severity :progname :pid :message :tags].freeze
|
18
|
+
MILLISECOND_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%3N"
|
19
|
+
MICROSECOND_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%6N"
|
20
|
+
PLACEHOLDER_PATTERN = /:(([a-z0-9_]+)|({[^}]+}))/i.freeze
|
21
|
+
|
18
22
|
# Create a new template from the markup. The +first_line+ argument is used to format only the first
|
19
23
|
# line of a message. Additional lines will be added to the message unformatted. If you wish to format
|
20
|
-
# the additional lines, use the
|
24
|
+
# the additional lines, use the :additional_lines options to specify a template. Note that you'll need
|
21
25
|
# to provide the line separator character in this template if you want to keep the message on multiple lines.
|
22
26
|
#
|
23
27
|
# The time will be formatted as YYYY-MM-DDTHH:MM:SSS.SSS by default. If you wish to change the format, you
|
24
|
-
# can specify the
|
28
|
+
# can specify the :time_format option which can be either a time format template as documented in
|
25
29
|
# +Time#strftime+ or the values +:milliseconds+ or +:microseconds+ to use the standard format with the
|
26
30
|
# specified precision.
|
27
31
|
#
|
28
32
|
# Messages will have white space stripped from both ends.
|
29
33
|
def initialize(first_line, options = {})
|
30
|
-
@first_line_template = compile(first_line)
|
34
|
+
@first_line_template, @first_line_tags = compile(first_line)
|
31
35
|
additional_lines = options[:additional_lines] || "#{Lumberjack::LINE_SEPARATOR}:message"
|
32
|
-
@additional_line_template = compile(additional_lines)
|
36
|
+
@additional_line_template, @additional_line_tags = compile(additional_lines)
|
33
37
|
# Formatting the time is relatively expensive, so only do it if it will be used
|
34
38
|
@template_include_time = first_line.include?(":time") || additional_lines.include?(":time")
|
35
|
-
|
39
|
+
self.datetime_format = (options[:time_format] || :milliseconds)
|
40
|
+
end
|
41
|
+
|
42
|
+
def datetime_format=(format)
|
43
|
+
if format == :milliseconds
|
44
|
+
format = MILLISECOND_TIME_FORMAT
|
45
|
+
elsif format == :microseconds
|
46
|
+
format = MICROSECOND_TIME_FORMAT
|
47
|
+
end
|
48
|
+
@time_formatter = Formatter::DateTimeFormatter.new(format)
|
36
49
|
end
|
37
|
-
|
50
|
+
|
51
|
+
def datetime_format
|
52
|
+
@time_formatter.format
|
53
|
+
end
|
54
|
+
|
38
55
|
# Convert an entry into a string using the template.
|
39
56
|
def call(entry)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
57
|
+
return entry unless entry.is_a?(LogEntry)
|
58
|
+
|
59
|
+
first_line = entry.message.to_s
|
60
|
+
additional_lines = nil
|
61
|
+
if first_line.include?(Lumberjack::LINE_SEPARATOR)
|
62
|
+
additional_lines = first_line.split(Lumberjack::LINE_SEPARATOR)
|
63
|
+
first_line = additional_lines.shift
|
64
|
+
end
|
65
|
+
|
66
|
+
formatted_time = @time_formatter.call(entry.time) if @template_include_time
|
67
|
+
format_args = [formatted_time, entry.severity_label, entry.progname, entry.pid, first_line]
|
68
|
+
tag_arguments = tag_args(entry.tags, @first_line_tags)
|
69
|
+
message = (@first_line_template % (format_args + tag_arguments))
|
70
|
+
message.rstrip! if message.end_with?(" ")
|
71
|
+
|
72
|
+
if additional_lines && !additional_lines.empty?
|
73
|
+
tag_arguments = tag_args(entry.tags, @additional_line_tags) unless @additional_line_tags == @first_line_tags
|
74
|
+
additional_lines.each do |line|
|
75
|
+
format_args[format_args.size - 1] = line
|
76
|
+
line_message = (@additional_line_template % (format_args + tag_arguments)).rstrip
|
77
|
+
line_message.rstrip! if line_message.end_with?(" ")
|
78
|
+
message << line_message
|
79
|
+
end
|
45
80
|
end
|
46
81
|
message
|
47
82
|
end
|
48
|
-
|
83
|
+
|
49
84
|
private
|
50
85
|
|
51
|
-
def
|
52
|
-
if
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
86
|
+
def tag_args(tags, tag_vars)
|
87
|
+
return [nil] * (tag_vars.size + 1) if tags.nil? || tags.size == 0
|
88
|
+
|
89
|
+
tags_string = ""
|
90
|
+
tags.each do |name, value|
|
91
|
+
unless value.nil? || tag_vars.include?(name)
|
92
|
+
value = value.to_s
|
93
|
+
value = value.gsub(Lumberjack::LINE_SEPARATOR, " ") if value.include?(Lumberjack::LINE_SEPARATOR)
|
94
|
+
tags_string << "[#{name}:#{value}] "
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
args = [tags_string.chop]
|
99
|
+
tag_vars.each do |name|
|
100
|
+
args << tags[name]
|
58
101
|
end
|
102
|
+
args
|
59
103
|
end
|
60
|
-
|
104
|
+
|
61
105
|
# Compile the template string into a value that can be used with sprintf.
|
62
106
|
def compile(template) #:nodoc:
|
63
|
-
|
64
|
-
|
107
|
+
tag_vars = []
|
108
|
+
template = template.gsub(PLACEHOLDER_PATTERN) do |match|
|
109
|
+
var_name = match.sub("{", "").sub("}", "")
|
110
|
+
position = TEMPLATE_ARGUMENT_ORDER.index(var_name)
|
65
111
|
if position
|
66
112
|
"%#{position + 1}$s"
|
67
113
|
else
|
68
|
-
|
114
|
+
tag_vars << var_name[1, var_name.length]
|
115
|
+
"%#{TEMPLATE_ARGUMENT_ORDER.size + tag_vars.size}$s"
|
69
116
|
end
|
70
117
|
end
|
118
|
+
[template, tag_vars]
|
71
119
|
end
|
72
120
|
end
|
73
121
|
end
|