newrelic_rpm 3.14.2.312 → 3.14.3.313
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -5
- data/CHANGELOG +22 -0
- data/lib/new_relic/agent/agent.rb +0 -4
- data/lib/new_relic/agent/configuration/default_source.rb +114 -107
- data/lib/new_relic/agent/database.rb +17 -1
- data/lib/new_relic/agent/database/obfuscation_helpers.rb +68 -48
- data/lib/new_relic/agent/database/obfuscator.rb +4 -23
- data/lib/new_relic/agent/database/postgres_explain_obfuscator.rb +1 -1
- data/lib/new_relic/agent/instrumentation/data_mapper.rb +20 -1
- data/lib/new_relic/agent/instrumentation/evented_subscriber.rb +1 -1
- data/lib/new_relic/agent/rules_engine.rb +39 -2
- data/lib/new_relic/agent/rules_engine/segment_terms_rule.rb +27 -5
- data/lib/new_relic/agent/sql_sampler.rb +7 -3
- data/lib/new_relic/language_support.rb +8 -0
- data/lib/new_relic/version.rb +1 -1
- data/lib/tasks/config.html.erb +5 -1
- data/lib/tasks/config.rake +10 -2
- data/lib/tasks/config.text.erb +6 -5
- data/test/environments/rails32/Gemfile +6 -1
- data/test/fixtures/cross_agent_tests/aws.json +95 -1
- data/test/fixtures/cross_agent_tests/cat/README.md +28 -0
- data/test/fixtures/cross_agent_tests/cat/cat_map.json +595 -0
- data/test/fixtures/cross_agent_tests/cat/path_hashing.json +51 -0
- data/test/fixtures/cross_agent_tests/data_transport/data_transport.json +1441 -0
- data/test/fixtures/cross_agent_tests/data_transport/data_transport.md +35 -0
- data/test/fixtures/cross_agent_tests/sql_obfuscation/README.md +7 -2
- data/test/fixtures/cross_agent_tests/sql_obfuscation/sql_obfuscation.json +261 -35
- data/test/fixtures/cross_agent_tests/transaction_segment_terms.json +305 -17
- data/test/multiverse/suites/active_record/active_record_test.rb +1 -1
- data/test/multiverse/suites/agent_only/rename_rule_test.rb +12 -12
- data/test/multiverse/suites/datamapper/datamapper_test.rb +23 -0
- data/test/multiverse/suites/rails/Envfile +10 -2
- data/test/new_relic/agent/database/sql_obfuscation_test.rb +2 -7
- data/test/performance/README.md +3 -10
- data/test/performance/lib/performance/table.rb +1 -1
- data/test/performance/suites/rules_engine.rb +35 -0
- data/test/performance/suites/segment_terms_rule.rb +27 -0
- data/test/performance/suites/sql_obfuscation.rb +19 -0
- metadata +9 -2
@@ -327,7 +327,23 @@ module NewRelic
|
|
327
327
|
end
|
328
328
|
|
329
329
|
def adapter
|
330
|
-
config && config[:adapter]
|
330
|
+
config && config[:adapter] && symbolized_adapter(config[:adapter].to_s.downcase)
|
331
|
+
end
|
332
|
+
|
333
|
+
POSTGRES_PREFIX = 'postgres'.freeze
|
334
|
+
MYSQL_PREFIX = 'mysql'.freeze
|
335
|
+
SQLITE_PREFIX = 'sqlite'.freeze
|
336
|
+
|
337
|
+
def symbolized_adapter(adapter)
|
338
|
+
if adapter.start_with? POSTGRES_PREFIX
|
339
|
+
:postgres
|
340
|
+
elsif adapter.start_with? MYSQL_PREFIX
|
341
|
+
:mysql
|
342
|
+
elsif adapter.start_with? SQLITE_PREFIX
|
343
|
+
:sqlite
|
344
|
+
else
|
345
|
+
adapter.to_sym
|
346
|
+
end
|
331
347
|
end
|
332
348
|
end
|
333
349
|
end
|
@@ -6,71 +6,91 @@ module NewRelic
|
|
6
6
|
module Agent
|
7
7
|
module Database
|
8
8
|
module ObfuscationHelpers
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# Reversing the query string before obfuscation allows us to get around
|
22
|
-
# the fact that a \' appearing within a string may or may not terminate
|
23
|
-
# the string, because we know that a string cannot *start* with a \'.
|
24
|
-
REVERSE_SINGLE_QUOTES_REGEX = /'(?:''|'\\|[^'])*'/
|
25
|
-
REVERSE_ANY_QUOTES_REGEX = /'(?:''|'\\|[^'])*'|"(?:""|"\\|[^"])*"/
|
9
|
+
COMPONENTS_REGEX_MAP = {
|
10
|
+
:single_quotes => /'(?:[^']|'')*?(?:\\'.*|'(?!'))/,
|
11
|
+
:double_quotes => /"(?:[^"]|"")*?(?:\\".*|"(?!"))/,
|
12
|
+
:dollar_quotes => /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/,
|
13
|
+
:uuids => /\{?(?:[0-9a-fA-F]\-*){32}\}?/,
|
14
|
+
:numeric_literals => /\b-?(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/,
|
15
|
+
:boolean_literals => /\b(?:true|false|null)\b/i,
|
16
|
+
:hexadecimal_literals => /0x[0-9a-fA-F]+/,
|
17
|
+
:comments => /(?:#|--).*?(?=\r|\n|$)/i,
|
18
|
+
:multi_line_comments => /\/\*(?:[^\/]|\/[^*])*?(?:\*\/|\/\*.*)/,
|
19
|
+
:oracle_quoted_strings => /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'\<.*?(?:\>'|$)|q'\(.*?(?:\)'|$)/
|
20
|
+
}
|
26
21
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
22
|
+
DIALECT_COMPONENTS = {
|
23
|
+
:fallback => COMPONENTS_REGEX_MAP.keys,
|
24
|
+
:mysql => [:single_quotes, :double_quotes, :numeric_literals, :boolean_literals,
|
25
|
+
:hexadecimal_literals, :comments, :multi_line_comments],
|
26
|
+
:postgres => [:single_quotes, :dollar_quotes, :uuids, :numeric_literals,
|
27
|
+
:boolean_literals, :comments, :multi_line_comments],
|
28
|
+
:sqlite => [:single_quotes, :numeric_literals, :boolean_literals, :hexadecimal_literals,
|
29
|
+
:comments, :multi_line_comments],
|
30
|
+
:oracle => [:single_quotes, :oracle_quoted_strings, :numeric_literals, :comments,
|
31
|
+
:multi_line_comments],
|
32
|
+
:cassandra => [:single_quotes, :uuids, :numeric_literals, :boolean_literals,
|
33
|
+
:hexadecimal_literals, :comments, :multi_line_comments]
|
34
|
+
}
|
35
35
|
|
36
36
|
# We use these to check whether the query contains any quote characters
|
37
37
|
# after obfuscation. If so, that's a good indication that the original
|
38
|
-
# query was malformed, and so our obfuscation can't
|
38
|
+
# query was malformed, and so our obfuscation can't reliably find
|
39
39
|
# literals. In such a case, we'll replace the entire query with a
|
40
40
|
# placeholder.
|
41
|
-
|
42
|
-
|
41
|
+
CLEANUP_REGEX = {
|
42
|
+
:mysql => /'|"|\/\*|\*\//,
|
43
|
+
:postgres => /'|\/\*|\*\/|\$(?!\?)/,
|
44
|
+
:sqlite => /'|\/\*|\*\/|\$/,
|
45
|
+
:cassandra => /'|\/\*|\*\//,
|
46
|
+
:oracle => /'|\/\*|\*\//
|
47
|
+
}
|
43
48
|
|
44
49
|
PLACEHOLDER = '?'.freeze
|
50
|
+
FAILED_TO_OBFUSCATE_MESSAGE = "Failed to obfuscate SQL query - quote characters remained after obfuscation".freeze
|
45
51
|
|
46
52
|
def obfuscate_single_quote_literals(sql)
|
47
|
-
|
48
|
-
obfuscated.gsub!(REVERSE_SINGLE_QUOTES_REGEX, PLACEHOLDER)
|
49
|
-
obfuscated.reverse!
|
50
|
-
obfuscated
|
53
|
+
sql.gsub!(COMPONENTS_REGEX_MAP[:single_quotes], PLACEHOLDER) || sql
|
51
54
|
end
|
52
55
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
obfuscated.reverse!
|
57
|
-
obfuscated
|
56
|
+
def self.generate_regex(dialect)
|
57
|
+
components = DIALECT_COMPONENTS[dialect]
|
58
|
+
Regexp.union(components.map{|component| COMPONENTS_REGEX_MAP[component]})
|
58
59
|
end
|
59
60
|
|
60
|
-
|
61
|
-
|
62
|
-
|
61
|
+
MYSQL_COMPONENTS_REGEX = self.generate_regex(:mysql)
|
62
|
+
POSTGRES_COMPONENTS_REGEX = self.generate_regex(:postgres)
|
63
|
+
SQLITE_COMPONENTS_REGEX = self.generate_regex(:sqlite)
|
64
|
+
ORACLE_COMPONENTS_REGEX = self.generate_regex(:oracle)
|
65
|
+
CASSANDRA_COMPONENTS_REGEX = self.generate_regex(:cassandra)
|
66
|
+
FALLBACK_REGEX = self.generate_regex(:fallback)
|
63
67
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
def obfuscate(sql, adapter)
|
69
|
+
case adapter
|
70
|
+
when :mysql
|
71
|
+
regex = MYSQL_COMPONENTS_REGEX
|
72
|
+
when :postgres
|
73
|
+
regex = POSTGRES_COMPONENTS_REGEX
|
74
|
+
when :sqlite
|
75
|
+
regex = SQLITE_COMPONENTS_REGEX
|
76
|
+
when :oracle
|
77
|
+
regex = ORACLE_COMPONENTS_REGEX
|
78
|
+
when :cassandra
|
79
|
+
regex = CASSANDRA_COMPONENTS_REGEX
|
80
|
+
else
|
81
|
+
regex = FALLBACK_REGEX
|
82
|
+
end
|
83
|
+
obfuscated = sql.gsub(regex, PLACEHOLDER)
|
84
|
+
obfuscated = FAILED_TO_OBFUSCATE_MESSAGE if detect_unmatched_pairs(obfuscated, adapter)
|
85
|
+
obfuscated
|
70
86
|
end
|
71
87
|
|
72
|
-
def
|
73
|
-
|
88
|
+
def detect_unmatched_pairs(obfuscated, adapter)
|
89
|
+
if CLEANUP_REGEX[adapter]
|
90
|
+
CLEANUP_REGEX[adapter].match(obfuscated)
|
91
|
+
else
|
92
|
+
CLEANUP_REGEX[:mysql].match(obfuscated)
|
93
|
+
end
|
74
94
|
end
|
75
95
|
end
|
76
96
|
end
|
@@ -13,8 +13,8 @@ module NewRelic
|
|
13
13
|
|
14
14
|
attr_reader :obfuscator
|
15
15
|
|
16
|
-
QUERY_TOO_LARGE_MESSAGE
|
17
|
-
|
16
|
+
QUERY_TOO_LARGE_MESSAGE = "Query too large (over 16k characters) to safely obfuscate".freeze
|
17
|
+
ELLIPSIS = "...".freeze
|
18
18
|
|
19
19
|
def initialize
|
20
20
|
reset
|
@@ -50,30 +50,11 @@ module NewRelic
|
|
50
50
|
def default_sql_obfuscator(sql)
|
51
51
|
stmt = sql.kind_of?(Statement) ? sql : Statement.new(sql)
|
52
52
|
|
53
|
-
if stmt.sql
|
53
|
+
if stmt.sql.end_with? ELLIPSIS
|
54
54
|
return QUERY_TOO_LARGE_MESSAGE
|
55
55
|
end
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
obfuscated = obfuscate_numeric_literals(stmt.sql)
|
60
|
-
|
61
|
-
if obfuscate_double_quotes
|
62
|
-
obfuscated = obfuscate_quoted_literals(obfuscated)
|
63
|
-
obfuscated = remove_comments(obfuscated)
|
64
|
-
if contains_quotes?(obfuscated)
|
65
|
-
obfuscated = FAILED_TO_OBFUSCATE_MESSAGE
|
66
|
-
end
|
67
|
-
else
|
68
|
-
obfuscated = obfuscate_single_quote_literals(obfuscated)
|
69
|
-
obfuscated = remove_comments(obfuscated)
|
70
|
-
if contains_single_quotes?(obfuscated)
|
71
|
-
obfuscated = FAILED_TO_OBFUSCATE_MESSAGE
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
|
76
|
-
obfuscated.to_s # return back to a regular String
|
57
|
+
obfuscate(stmt.sql, stmt.adapter).to_s
|
77
58
|
end
|
78
59
|
end
|
79
60
|
end
|
@@ -12,7 +12,7 @@ module NewRelic
|
|
12
12
|
|
13
13
|
# Note that this regex can't be shared with the ones in the
|
14
14
|
# Database::Obfuscator class because here we don't look for
|
15
|
-
# backslash-escaped strings
|
15
|
+
# backslash-escaped strings.
|
16
16
|
QUOTED_STRINGS_REGEX = /'(?:[^']|'')*'|"(?:[^"]|"")*"/
|
17
17
|
LABEL_LINE_REGEX = /^([^:\n]*:\s+).*$/.freeze
|
18
18
|
|
@@ -109,6 +109,9 @@ module NewRelic
|
|
109
109
|
end
|
110
110
|
|
111
111
|
DATA_MAPPER = "DataMapper".freeze
|
112
|
+
PASSWORD_REGEX = /&password=.*?&/
|
113
|
+
AMPERSAND = '&'.freeze
|
114
|
+
PASSWORD_PARAM = '&password='.freeze
|
112
115
|
|
113
116
|
def self.method_body(clazz, method_name, operation_only)
|
114
117
|
use_model_name = NewRelic::Helper.instance_methods_include?(clazz, :model)
|
@@ -136,7 +139,23 @@ module NewRelic
|
|
136
139
|
name)
|
137
140
|
|
138
141
|
NewRelic::Agent::MethodTracer.trace_execution_scoped(metrics) do
|
139
|
-
|
142
|
+
begin
|
143
|
+
self.send("#{method_name}_without_newrelic", *args, &blk)
|
144
|
+
rescue ::DataObjects::SQLError => e
|
145
|
+
e.uri.gsub!(PASSWORD_REGEX, AMPERSAND) if e.uri.include?(PASSWORD_PARAM)
|
146
|
+
|
147
|
+
strategy = NewRelic::Agent::Database.record_sql_method(:slow_sql)
|
148
|
+
case strategy
|
149
|
+
when :obfuscated
|
150
|
+
statement = NewRelic::Agent::Database::Statement.new(e.query, :adapter => self.options[:adapter])
|
151
|
+
obfuscated_sql = NewRelic::Agent::Database.obfuscate_sql(statement)
|
152
|
+
e.instance_variable_set(:@query, obfuscated_sql)
|
153
|
+
when :off
|
154
|
+
e.instance_variable_set(:@query, nil)
|
155
|
+
end
|
156
|
+
|
157
|
+
raise
|
158
|
+
end
|
140
159
|
end
|
141
160
|
end
|
142
161
|
end
|
@@ -64,7 +64,7 @@ module NewRelic
|
|
64
64
|
# with a couple minor additions so we don't have a hard
|
65
65
|
# dependency on ActiveSupport::Notifications.
|
66
66
|
#
|
67
|
-
# Represents an
|
67
|
+
# Represents an instrumentation event, provides timing and metric
|
68
68
|
# name information useful when recording metrics.
|
69
69
|
class Event
|
70
70
|
attr_reader :name, :time, :transaction_id, :payload, :children
|
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
require 'new_relic/agent/rules_engine/replacement_rule'
|
6
6
|
require 'new_relic/agent/rules_engine/segment_terms_rule'
|
7
|
+
require 'new_relic/language_support'
|
7
8
|
|
8
9
|
module NewRelic
|
9
10
|
module Agent
|
@@ -26,12 +27,48 @@ module NewRelic
|
|
26
27
|
txn_name_specs = connect_response['transaction_name_rules'] || []
|
27
28
|
segment_rule_specs = connect_response['transaction_segment_terms'] || []
|
28
29
|
|
29
|
-
txn_name_rules = txn_name_specs.map
|
30
|
-
|
30
|
+
txn_name_rules = txn_name_specs.map { |s| ReplacementRule.new(s) }
|
31
|
+
|
32
|
+
segment_rules = []
|
33
|
+
|
34
|
+
segment_rule_specs.each do |spec|
|
35
|
+
if spec[SegmentTermsRule::PREFIX_KEY] && SegmentTermsRule.valid?(spec)
|
36
|
+
# Build segment_rules in reverse order from which they're provided,
|
37
|
+
# so that when we eliminate duplicates with #uniq!, we retain the last
|
38
|
+
# instances of repeated rules.
|
39
|
+
segment_rules.unshift SegmentTermsRule.new(spec)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
reject_rules_with_duplicate_prefixes!(segment_rules)
|
44
|
+
|
45
|
+
segment_rules.reverse! # Reset the rules to their original order.
|
31
46
|
|
32
47
|
self.new(txn_name_rules, segment_rules)
|
33
48
|
end
|
34
49
|
|
50
|
+
# When multiple rules share the same prefix,
|
51
|
+
# only apply the rule with the last instance of the prefix.
|
52
|
+
# Note that the incoming rules are in reverse order to facilitate this.
|
53
|
+
if NewRelic::LanguageSupport.uniq_accepts_block?
|
54
|
+
def self.reject_rules_with_duplicate_prefixes!(rules)
|
55
|
+
rules.uniq! { |rule| rule.prefix }
|
56
|
+
end
|
57
|
+
else
|
58
|
+
def self.reject_rules_with_duplicate_prefixes!(rules)
|
59
|
+
unique_rules = {}
|
60
|
+
|
61
|
+
rules.reject! do |rule|
|
62
|
+
if unique_rules[rule.prefix]
|
63
|
+
true
|
64
|
+
else
|
65
|
+
unique_rules[rule.prefix] = rule
|
66
|
+
false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
35
72
|
def initialize(rules=[], segment_term_rules=[])
|
36
73
|
@rules = rules.sort
|
37
74
|
@segment_term_rules = segment_term_rules
|
@@ -6,15 +6,32 @@ module NewRelic
|
|
6
6
|
module Agent
|
7
7
|
class RulesEngine
|
8
8
|
class SegmentTermsRule
|
9
|
+
PREFIX_KEY = 'prefix'.freeze
|
10
|
+
TERMS_KEY = 'terms'.freeze
|
9
11
|
SEGMENT_PLACEHOLDER = '*'.freeze
|
10
12
|
ADJACENT_PLACEHOLDERS_REGEX = %r{((?:^|/)\*)(?:/\*)*}.freeze
|
11
13
|
ADJACENT_PLACEHOLDERS_REPLACEMENT = '\1'.freeze
|
14
|
+
VALID_PREFIX_SEGMENT_COUNT = 2
|
12
15
|
|
13
16
|
attr_reader :prefix, :terms
|
14
17
|
|
18
|
+
def self.valid?(rule_spec)
|
19
|
+
rule_spec[PREFIX_KEY].kind_of?(String) &&
|
20
|
+
rule_spec[TERMS_KEY].kind_of?(Array) &&
|
21
|
+
valid_prefix_segment_count?(rule_spec[PREFIX_KEY])
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.valid_prefix_segment_count?(prefix)
|
25
|
+
count = prefix.count(SEGMENT_SEPARATOR)
|
26
|
+
rindex = prefix.rindex(SEGMENT_SEPARATOR)
|
27
|
+
|
28
|
+
(count == 2 && prefix[rindex + 1].nil?) ||
|
29
|
+
(count == 1 && !prefix[rindex + 1].nil?)
|
30
|
+
end
|
31
|
+
|
15
32
|
def initialize(options)
|
16
|
-
@prefix = options[
|
17
|
-
@terms = options[
|
33
|
+
@prefix = options[PREFIX_KEY]
|
34
|
+
@terms = options[TERMS_KEY]
|
18
35
|
@trim_range = (@prefix.size..-1)
|
19
36
|
end
|
20
37
|
|
@@ -23,14 +40,19 @@ module NewRelic
|
|
23
40
|
end
|
24
41
|
|
25
42
|
def matches?(string)
|
26
|
-
string.start_with?(@prefix)
|
43
|
+
string.start_with?(@prefix) &&
|
44
|
+
(prefix_matches_on_segment_boundary?(string) || string.size == @prefix.size)
|
45
|
+
end
|
46
|
+
|
47
|
+
def prefix_matches_on_segment_boundary?(string)
|
48
|
+
string.size > @prefix.size &&
|
49
|
+
string[@prefix.chomp(SEGMENT_SEPARATOR).size].chr == SEGMENT_SEPARATOR
|
27
50
|
end
|
28
51
|
|
29
52
|
def apply(string)
|
30
53
|
rest = string[@trim_range]
|
31
54
|
leading_slash = rest.slice!(LEADING_SLASH_REGEX)
|
32
|
-
|
33
|
-
segments = rest.split(SEGMENT_SEPARATOR)
|
55
|
+
segments = rest.split(SEGMENT_SEPARATOR, -1)
|
34
56
|
segments.map! { |s| @terms.include?(s) ? s : SEGMENT_PLACEHOLDER }
|
35
57
|
transformed_suffix = collapse_adjacent_placeholder_segments(segments)
|
36
58
|
|
@@ -313,10 +313,14 @@ module NewRelic
|
|
313
313
|
|
314
314
|
private
|
315
315
|
|
316
|
+
# need to hash the same way in every process, to be able to aggregate slow SQL traces
|
316
317
|
def consistent_hash(string)
|
317
|
-
|
318
|
-
|
319
|
-
|
318
|
+
if NewRelic::Agent.config[:'slow_sql.use_longer_sql_id']
|
319
|
+
Digest::MD5.hexdigest(string).hex.modulo(2**63-1)
|
320
|
+
else
|
321
|
+
# from when sql_id needed to fit in an INT(11)
|
322
|
+
Digest::MD5.hexdigest(string).hex.modulo(2**31-1)
|
323
|
+
end
|
320
324
|
end
|
321
325
|
end
|
322
326
|
end
|
@@ -5,6 +5,8 @@
|
|
5
5
|
module NewRelic::LanguageSupport
|
6
6
|
extend self
|
7
7
|
|
8
|
+
RUBY_VERSION_192 = '1.9.2'.freeze
|
9
|
+
|
8
10
|
# need to use syck rather than psych when possible
|
9
11
|
def needs_syck?
|
10
12
|
!NewRelic::LanguageSupport.using_engine?('jruby') &&
|
@@ -90,6 +92,12 @@ module NewRelic::LanguageSupport
|
|
90
92
|
end
|
91
93
|
end
|
92
94
|
|
95
|
+
if ::RUBY_VERSION >= RUBY_VERSION_192
|
96
|
+
def uniq_accepts_block?; true; end
|
97
|
+
else
|
98
|
+
def uniq_accepts_block?; false; end
|
99
|
+
end
|
100
|
+
|
93
101
|
def jruby?
|
94
102
|
defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
95
103
|
end
|
data/lib/new_relic/version.rb
CHANGED
data/lib/tasks/config.html.erb
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
<dl class="clamshell-list">
|
7
7
|
<% configs.each do |config|%>
|
8
8
|
<!-- ********** <%= config[:key] %> ********** -->
|
9
|
-
<dt id="<%=config[:key]%>"><%=config[:key]%></dt>
|
9
|
+
<dt id="<%= config[:key].gsub('.', '-') %>"><%=config[:key]%></dt>
|
10
10
|
<dd>
|
11
11
|
<table class="specs">
|
12
12
|
<tbody>
|
@@ -18,6 +18,10 @@
|
|
18
18
|
<th>Default</th>
|
19
19
|
<td><%=config[:default]%></td>
|
20
20
|
</tr>
|
21
|
+
<tr>
|
22
|
+
<th>Environ variable</th>
|
23
|
+
<td><code><%=config[:env_var]%></code></td>
|
24
|
+
</tr>
|
21
25
|
</tbody>
|
22
26
|
</table>
|
23
27
|
|