opencensus 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +48 -0
- data/.travis.yml +16 -0
- data/AUTHORS +1 -0
- data/CODE_OF_CONDUCT.md +43 -0
- data/CONTRIBUTING.md +34 -0
- data/Gemfile +4 -0
- data/LICENSE +201 -0
- data/README.md +180 -0
- data/Rakefile +20 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/.gitignore +3 -0
- data/docs/404.html +24 -0
- data/docs/Gemfile +31 -0
- data/docs/_config.yml +39 -0
- data/docs/_layouts/default.html +65 -0
- data/docs/index.md +151 -0
- data/lib/opencensus.rb +21 -0
- data/lib/opencensus/common.rb +24 -0
- data/lib/opencensus/common/config.rb +521 -0
- data/lib/opencensus/config.rb +54 -0
- data/lib/opencensus/context.rb +72 -0
- data/lib/opencensus/stats.rb +26 -0
- data/lib/opencensus/tags.rb +25 -0
- data/lib/opencensus/trace.rb +181 -0
- data/lib/opencensus/trace/annotation.rb +60 -0
- data/lib/opencensus/trace/config.rb +119 -0
- data/lib/opencensus/trace/exporters.rb +26 -0
- data/lib/opencensus/trace/exporters/logger.rb +149 -0
- data/lib/opencensus/trace/formatters.rb +29 -0
- data/lib/opencensus/trace/formatters/binary.rb +66 -0
- data/lib/opencensus/trace/formatters/cloud_trace.rb +102 -0
- data/lib/opencensus/trace/formatters/trace_context.rb +124 -0
- data/lib/opencensus/trace/integrations.rb +24 -0
- data/lib/opencensus/trace/integrations/faraday_middleware.rb +176 -0
- data/lib/opencensus/trace/integrations/rack_middleware.rb +127 -0
- data/lib/opencensus/trace/integrations/rails.rb +121 -0
- data/lib/opencensus/trace/link.rb +90 -0
- data/lib/opencensus/trace/message_event.rb +80 -0
- data/lib/opencensus/trace/samplers.rb +50 -0
- data/lib/opencensus/trace/samplers/always_sample.rb +34 -0
- data/lib/opencensus/trace/samplers/max_qps.rb +55 -0
- data/lib/opencensus/trace/samplers/never_sample.rb +34 -0
- data/lib/opencensus/trace/samplers/probability.rb +69 -0
- data/lib/opencensus/trace/span.rb +196 -0
- data/lib/opencensus/trace/span_builder.rb +560 -0
- data/lib/opencensus/trace/span_context.rb +308 -0
- data/lib/opencensus/trace/status.rb +49 -0
- data/lib/opencensus/trace/time_event.rb +38 -0
- data/lib/opencensus/trace/trace_context_data.rb +22 -0
- data/lib/opencensus/trace/truncatable_string.rb +61 -0
- data/lib/opencensus/version.rb +18 -0
- data/opencensus.gemspec +32 -0
- metadata +210 -0
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
require "yard"
|
4
|
+
|
5
|
+
require "rubocop/rake_task"
|
6
|
+
RuboCop::RakeTask.new
|
7
|
+
|
8
|
+
Rake::TestTask.new :test do |t|
|
9
|
+
t.libs << "test"
|
10
|
+
t.libs << "lib"
|
11
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
12
|
+
end
|
13
|
+
|
14
|
+
YARD::Rake::YardocTask.new do |t|
|
15
|
+
t.files = ['lib/**/*.rb'] # optional
|
16
|
+
t.options = ['--output-dir', 'docs/api'] # optional
|
17
|
+
t.stats_options = ['--list-undoc'] # optional
|
18
|
+
end
|
19
|
+
|
20
|
+
task :default => [:test, :rubocop, :yard]
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "opencensus"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/docs/.gitignore
ADDED
data/docs/404.html
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
---
|
4
|
+
|
5
|
+
<style type="text/css" media="screen">
|
6
|
+
.container {
|
7
|
+
margin: 10px auto;
|
8
|
+
max-width: 600px;
|
9
|
+
text-align: center;
|
10
|
+
}
|
11
|
+
h1 {
|
12
|
+
margin: 30px 0;
|
13
|
+
font-size: 4em;
|
14
|
+
line-height: 1;
|
15
|
+
letter-spacing: -1px;
|
16
|
+
}
|
17
|
+
</style>
|
18
|
+
|
19
|
+
<div class="container">
|
20
|
+
<h1>404</h1>
|
21
|
+
|
22
|
+
<p><strong>Page not found :(</strong></p>
|
23
|
+
<p>The requested page could not be found.</p>
|
24
|
+
</div>
|
data/docs/Gemfile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
source "https://rubygems.org"
|
3
|
+
|
4
|
+
# Hello! This is where you manage which Jekyll version is used to run.
|
5
|
+
# When you want to use a different version, change it below, save the
|
6
|
+
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
|
7
|
+
#
|
8
|
+
# bundle exec jekyll serve
|
9
|
+
#
|
10
|
+
# This will help ensure the proper Jekyll version is running.
|
11
|
+
# Happy Jekylling!
|
12
|
+
gem "jekyll", "~> 3.6.2"
|
13
|
+
|
14
|
+
# This is the default theme for new Jekyll sites. You may change this to anything you like.
|
15
|
+
# gem "minima", "~> 2.0"
|
16
|
+
gem "jekyll-theme-minimal", "~> 0.1"
|
17
|
+
# gem "minimal"
|
18
|
+
|
19
|
+
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
|
20
|
+
# uncomment the line below. To upgrade, run `bundle update github-pages`.
|
21
|
+
# gem "github-pages", group: :jekyll_plugins
|
22
|
+
|
23
|
+
# If you have any plugins, put them here!
|
24
|
+
group :jekyll_plugins do
|
25
|
+
gem "jekyll-feed", "~> 0.6"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
29
|
+
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
30
|
+
|
31
|
+
gem "github-pages", group: :jekyll_plugins
|
data/docs/_config.yml
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Welcome to Jekyll!
|
2
|
+
#
|
3
|
+
# This config file is meant for settings that affect your whole blog, values
|
4
|
+
# which you are expected to set up once and rarely edit after that. If you find
|
5
|
+
# yourself editing this file very often, consider using Jekyll's data files
|
6
|
+
# feature for the data you need to update frequently.
|
7
|
+
#
|
8
|
+
# For technical reasons, this file is *NOT* reloaded automatically when you use
|
9
|
+
# 'bundle exec jekyll serve'. If you change this file, please restart the server process.
|
10
|
+
|
11
|
+
# Site settings
|
12
|
+
# These are used to personalize your new site. If you look in the HTML files,
|
13
|
+
# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
|
14
|
+
# You can create any custom variable you would like, and they will be accessible
|
15
|
+
# in the templates via {{ site.myvariable }}.
|
16
|
+
title: OpenCensus for Ruby
|
17
|
+
email: your-email@example.com
|
18
|
+
description: A stats collection and distributed tracing framework
|
19
|
+
baseurl: "/opencensus-ruby" # the subpath of your site, e.g. /blog
|
20
|
+
url: "http://opencensus.io" # the base hostname & protocol for your site, e.g. http://example.com
|
21
|
+
github_username: census-instrumentation
|
22
|
+
|
23
|
+
# Build settings
|
24
|
+
markdown: kramdown
|
25
|
+
theme: jekyll-theme-minimal
|
26
|
+
plugins:
|
27
|
+
- jekyll-feed
|
28
|
+
|
29
|
+
# Exclude from processing.
|
30
|
+
# The following items will not be processed, by default. Create a custom list
|
31
|
+
# to override the default setting.
|
32
|
+
# exclude:
|
33
|
+
# - Gemfile
|
34
|
+
# - Gemfile.lock
|
35
|
+
# - node_modules
|
36
|
+
# - vendor/bundle/
|
37
|
+
# - vendor/cache/
|
38
|
+
# - vendor/gems/
|
39
|
+
# - vendor/ruby/
|
@@ -0,0 +1,65 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html lang="{{ site.lang | default: "en-US" }}">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
6
|
+
|
7
|
+
{% seo %}
|
8
|
+
|
9
|
+
<link rel="stylesheet" href="{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}">
|
10
|
+
<meta name="viewport" content="width=device-width">
|
11
|
+
<!--[if lt IE 9]>
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
13
|
+
<![endif]-->
|
14
|
+
</head>
|
15
|
+
<body>
|
16
|
+
<div class="wrapper">
|
17
|
+
<header>
|
18
|
+
<h1>{{ site.title | default: site.github.repository_name }}</h1>
|
19
|
+
<p>{{ site.description | default: site.github.project_tagline }}</p>
|
20
|
+
|
21
|
+
{% if site.github.is_project_page %}
|
22
|
+
<p class="view"><a href="{{ site.github.repository_url }}">View the Project on GitHub <small>{{ github_name }}</small></a></p>
|
23
|
+
{% endif %}
|
24
|
+
<p class="view"><a href="{{ '/api' | relative_url }}">View the API documentation</a>
|
25
|
+
|
26
|
+
{% if site.github.is_user_page %}
|
27
|
+
<p class="view"><a href="{{ site.github.owner_url }}">View My GitHub Profile</a></p>
|
28
|
+
{% endif %}
|
29
|
+
|
30
|
+
{% if site.show_downloads %}
|
31
|
+
<ul>
|
32
|
+
<li><a href="{{ site.github.zip_url }}">Download <strong>ZIP File</strong></a></li>
|
33
|
+
<li><a href="{{ site.github.tar_url }}">Download <strong>TAR Ball</strong></a></li>
|
34
|
+
<li><a href="{{ site.github.repository_url }}">View On <strong>GitHub</strong></a></li>
|
35
|
+
</ul>
|
36
|
+
{% endif %}
|
37
|
+
</header>
|
38
|
+
<section>
|
39
|
+
|
40
|
+
{{ content }}
|
41
|
+
|
42
|
+
</section>
|
43
|
+
<footer>
|
44
|
+
{% if site.github.is_project_page %}
|
45
|
+
<p>This project is maintained by <a href="{{ site.github.owner_url }}">{{ site.github.owner_name }}</a></p>
|
46
|
+
{% endif %}
|
47
|
+
<p><small>Hosted on GitHub Pages — Theme by <a href="https://github.com/orderedlist">orderedlist</a></small></p>
|
48
|
+
</footer>
|
49
|
+
</div>
|
50
|
+
<script src="{{ '/assets/js/scale.fix.js' | relative_url }}"></script>
|
51
|
+
|
52
|
+
|
53
|
+
{% if site.google_analytics %}
|
54
|
+
<script>
|
55
|
+
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
56
|
+
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
57
|
+
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
58
|
+
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
59
|
+
|
60
|
+
ga('create', '{{ site.google_analytics }}', 'auto');
|
61
|
+
ga('send', 'pageview');
|
62
|
+
</script>
|
63
|
+
{% endif %}
|
64
|
+
</body>
|
65
|
+
</html>
|
data/docs/index.md
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
---
|
4
|
+
|
5
|
+
# OpenCensus for Ruby
|
6
|
+
|
7
|
+
OpenCensus provides a framework to measure a server's resource usage and
|
8
|
+
collect performance stats. The `opencensus` Rubygem contains the core
|
9
|
+
OpenCensus APIs and basic integrations with Rails, Faraday, and GRPC.
|
10
|
+
|
11
|
+
The library is in alpha stage, and the API is subject to change.
|
12
|
+
|
13
|
+
## Quick Start
|
14
|
+
|
15
|
+
### Installation
|
16
|
+
|
17
|
+
Install the gem directly:
|
18
|
+
|
19
|
+
```sh
|
20
|
+
$ gem install opencensus
|
21
|
+
```
|
22
|
+
|
23
|
+
Or install through Bundler:
|
24
|
+
|
25
|
+
1. Add the `opencensus` gem to your Gemfile:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem "opencensus"
|
29
|
+
```
|
30
|
+
|
31
|
+
2. Use Bundler to install the gem:
|
32
|
+
|
33
|
+
```sh
|
34
|
+
$ bundle install
|
35
|
+
```
|
36
|
+
|
37
|
+
### Getting started with Ruby on Rails
|
38
|
+
|
39
|
+
The OpenCensus library provides a Railtie that integrates with Ruby On Rails,
|
40
|
+
automatically tracing incoming requests in the application. It also
|
41
|
+
automatically traces key processes in your application such as database queries
|
42
|
+
and view rendering.
|
43
|
+
|
44
|
+
To enable Rails integration, require this file during application startup:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
# In config/application.rb
|
48
|
+
require "opencensus/trace/integrations/rails"
|
49
|
+
```
|
50
|
+
|
51
|
+
### Getting started with other Rack-based frameworks
|
52
|
+
|
53
|
+
Other Rack-based frameworks, such as Sinatra, can use the Rack Middleware
|
54
|
+
integration, which automatically traces incoming requests. To enable the
|
55
|
+
integration for a non-Rails Rack framework, add the middleware to your
|
56
|
+
middleware stack.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
# In config.ru or similar Rack configuration file
|
60
|
+
require "opencensus/trace/integrations/rack_middleware"
|
61
|
+
use OpenCensus::Trace::Integrations::RackMiddleware
|
62
|
+
```
|
63
|
+
|
64
|
+
## Instrumentation features
|
65
|
+
|
66
|
+
### Tracing outgoing HTTP requests
|
67
|
+
|
68
|
+
If your app uses the [Faraday](https://github.com/lostisland/faraday) library
|
69
|
+
to make outgoing HTTP requests, consider installing the Faraday Middleware
|
70
|
+
integration. This integration creates a span for each outgoing Faraday request,
|
71
|
+
tracking the latency of that request, and propagates distributed trace headers
|
72
|
+
into the request so you can potentially connect your request trace with that of
|
73
|
+
the remote service. Here is an example:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
conn = Faraday.new(url: "http://www.example.com") do |c|
|
77
|
+
c.use OpenCensus::Trace::Integrations::FaradayMiddleware
|
78
|
+
c.adapter Faraday.default_adapter
|
79
|
+
end
|
80
|
+
conn.get "/"
|
81
|
+
```
|
82
|
+
|
83
|
+
See the documentation for the
|
84
|
+
[FaradayMiddleware](http://opencensus.io/opencensus-ruby/api/OpenCensus/Trace/Integrations/FaradayMiddleware.html)
|
85
|
+
class for more info.
|
86
|
+
|
87
|
+
### Adding Custom Trace Spans
|
88
|
+
|
89
|
+
In addition to the spans added by the Rails integration (e.g. for database
|
90
|
+
queries) and by Faraday integration for outgoing HTTP requests, you can add
|
91
|
+
additional custom spans to the request trace:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
OpenCensus::Trace.in_span "my_task" do |span|
|
95
|
+
# Do stuff...
|
96
|
+
|
97
|
+
OpenCensus::Trace.in_span "my_subtask" do |subspan|
|
98
|
+
# Do other stuff
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
See the documentation for the
|
104
|
+
[OpenCensus::Trace](http://opencensus.io/opencensus-ruby/api/OpenCensus/Trace.html)
|
105
|
+
module for more info.
|
106
|
+
|
107
|
+
### Exporting traces
|
108
|
+
|
109
|
+
By default, OpenCensus will log request trace data as JSON. To export traces to
|
110
|
+
your favorite analytics backend, install an export plugin. There are plugins
|
111
|
+
currently being developed for Stackdriver, Zipkin, and other services.
|
112
|
+
|
113
|
+
### Configuring the library
|
114
|
+
|
115
|
+
OpenCensus allows configuration of a number of aspects via the configuration
|
116
|
+
class. The following example illustrates how that looks:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
OpenCensus.configure do |c|
|
120
|
+
c.trace.default_sampler = OpenCensus::Trace::Samplers::AlwaysSample.new
|
121
|
+
c.trace.default_max_attributes = 16
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
If you are using Rails, you can equivalently use the Rails config:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
config.opencensus.trace.default_sampler =
|
129
|
+
OpenCensus::Trace::Samplers::AlwaysSample.new
|
130
|
+
config.opencensus.trace.default_max_attributes = 16
|
131
|
+
```
|
132
|
+
|
133
|
+
You can configure a variety of core OpenCensuys options, including:
|
134
|
+
|
135
|
+
* Sampling, which controls how often a request is traced.
|
136
|
+
* Exporting, which controls how trace information is reported.
|
137
|
+
* Formatting, which controls how distributed request trace headers are
|
138
|
+
constructed
|
139
|
+
* Size maximums, which control when trace data is truncated.
|
140
|
+
|
141
|
+
Additionally, integrations and other plugins might have their own
|
142
|
+
configurations.
|
143
|
+
|
144
|
+
For more information, consult the documentation for
|
145
|
+
[OpenCensus.configure](http://opencensus.io/opencensus-ruby/api/OpenCensus.html#configure-class_method)
|
146
|
+
and
|
147
|
+
[OpenCensus::Trace.configure](http://opencensus.io/opencensus-ruby/api/OpenCensus/Trace.html#configure-class_method).
|
148
|
+
|
149
|
+
## Supported Ruby Versions
|
150
|
+
|
151
|
+
This library is supported on Ruby 2.0+.
|
data/lib/opencensus.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Copyright 2017 OpenCensus Authors
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "opencensus/common"
|
16
|
+
require "opencensus/config"
|
17
|
+
require "opencensus/context"
|
18
|
+
require "opencensus/stats"
|
19
|
+
require "opencensus/tags"
|
20
|
+
require "opencensus/trace"
|
21
|
+
require "opencensus/version"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Copyright 2017 OpenCensus Authors
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "opencensus/common/config"
|
16
|
+
|
17
|
+
module OpenCensus
|
18
|
+
##
|
19
|
+
# The Common module contains common infrastructure that can be shared between
|
20
|
+
# Trace and Stats (not yet implemented)
|
21
|
+
#
|
22
|
+
module Common
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,521 @@
|
|
1
|
+
# Copyright 2017 OpenCensus Authors
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module OpenCensus
|
16
|
+
module Common
|
17
|
+
##
|
18
|
+
# OpenCensus configuration class.
|
19
|
+
#
|
20
|
+
# Configuration mechanism for OpenCensus libraries. A Config object contains
|
21
|
+
# a list of predefined keys, some of which are values and others of which
|
22
|
+
# are subconfigurations, i.e. categories. Option values are generally
|
23
|
+
# validated to ensure they are the correct type.
|
24
|
+
#
|
25
|
+
# You generally access fields and subconfigs by calling accessor methods.
|
26
|
+
# Only explicitly defined fields have these accessor methods defined.
|
27
|
+
# Methods meant for "administration" such as adding options, are always
|
28
|
+
# named with a trailing "!" or "?" so they don't pollute the method
|
29
|
+
# namespace.
|
30
|
+
#
|
31
|
+
# Example:
|
32
|
+
#
|
33
|
+
# config = OpenCensus::Common::Config.new do |c|
|
34
|
+
# c.add_option! :opt1, 10
|
35
|
+
# c.add_option! :opt2, :one, enum: [:one, :two, :three]
|
36
|
+
# c.add_option! :opt3, "hi", match: [String, Symbol]
|
37
|
+
# c.add_option! :opt4, "hi", match: /^[a-z]+$/, allow_nil: true
|
38
|
+
# c.add_config! :sub do |c2|
|
39
|
+
# c2.add_option! :opt5, false
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# config.opt1 #=> 10
|
44
|
+
# config.opt1 = 20 #=> 20
|
45
|
+
# config.opt1 #=> 20
|
46
|
+
# config.opt1 = "hi" #=> exception (only Integer allowed)
|
47
|
+
# config.opt1 = nil #=> exception (nil not allowed)
|
48
|
+
#
|
49
|
+
# config.opt2 #=> :one
|
50
|
+
# config.opt2 = :two #=> :two
|
51
|
+
# config.opt2 #=> :two
|
52
|
+
# config.opt2 = :four #=> exception (not in allowed enum)
|
53
|
+
# config.opt2 = nil #=> exception (nil not allowed)
|
54
|
+
#
|
55
|
+
# config.opt3 #=> "hi"
|
56
|
+
# config.opt3 = "hiho" #=> "hiho"
|
57
|
+
# config.opt3 #=> "hiho"
|
58
|
+
# config.opt3 = "HI" #=> exception (regexp check failed)
|
59
|
+
# config.opt3 = nil #=> exception (nil not allowed)
|
60
|
+
#
|
61
|
+
# config.opt4 #=> "yo"
|
62
|
+
# config.opt4 = :yo #=> :yo (Strings and Symbols allowed)
|
63
|
+
# config.opt4 #=> :yo
|
64
|
+
# config.opt4 = 3.14 #=> exception (not in allowed types)
|
65
|
+
# config.opt4 = nil #=> nil (nil explicitly allowed)
|
66
|
+
#
|
67
|
+
# config.sub #=> <OpenCensus::Common::Config>
|
68
|
+
#
|
69
|
+
# config.sub.opt5 #=> false
|
70
|
+
# config.sub.opt5 = true #=> true (true and false allowed)
|
71
|
+
# config.sub.opt5 #=> true
|
72
|
+
# config.sub.opt5 = nil #=> exception (nil not allowed)
|
73
|
+
#
|
74
|
+
# config.opt9 #=> exception (unknown key)
|
75
|
+
# config.sub.opt9 #=> exception (unknown key)
|
76
|
+
#
|
77
|
+
class Config
|
78
|
+
##
|
79
|
+
# Constructs a Configuration object. If a block is given, yields `self`
|
80
|
+
# to the block, which makes it convenient to initialize the structure by
|
81
|
+
# making calls to {Config#add_option!}, {Config#add_config!}, and
|
82
|
+
# {Config#add_alias!}.
|
83
|
+
#
|
84
|
+
def initialize
|
85
|
+
@fields = {}
|
86
|
+
yield self if block_given?
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Add an option field to this configuration.
|
91
|
+
#
|
92
|
+
# You must provide a key, which becomes the field name in this config.
|
93
|
+
# Field names may comprise only letters, numerals, and underscores, and
|
94
|
+
# must begin with a letter. This will create accessor methods for the
|
95
|
+
# new configuration key.
|
96
|
+
#
|
97
|
+
# You may pass an initial value (which defaults to nil if not provided).
|
98
|
+
#
|
99
|
+
# You may also specify how values are validated. Validation is defined
|
100
|
+
# as follows:
|
101
|
+
#
|
102
|
+
# * If you provide a block or a `:validator` option, it is used as the
|
103
|
+
# validator. A proposed value is passed to the proc, and it should
|
104
|
+
# return true or false to indicate whether the value is acceptable.
|
105
|
+
# * If you provide a `:match` option, it is compared to the proposed
|
106
|
+
# value using the `===` operator. You may, for example, provide a
|
107
|
+
# class, a regular expression, or a range. If you pass an array,
|
108
|
+
# the value is accepted if _any_ of the elements match.
|
109
|
+
# * If you provide an `:enum` option, it should be an `Enumerable`.
|
110
|
+
# A proposed value is accepted if it is included.
|
111
|
+
# * Otherwise if you do not provide any of the above options, then a
|
112
|
+
# default validation strategy is inferred from the initial value:
|
113
|
+
# * If the initial is `true` or `false`, then either boolean value
|
114
|
+
# is considered valid. This is the same as `enum: [true, false]`.
|
115
|
+
# * If the initial is `nil`, then any object is considered valid.
|
116
|
+
# * Otherwise, any object of the same class as the initial value is
|
117
|
+
# considered valid. This is effectively the same as
|
118
|
+
# `match: initial.class`.
|
119
|
+
# * You may also provide the `:allow_nil` option, which, if set to
|
120
|
+
# true, alters any of the above validators to allow `nil` values.
|
121
|
+
# If the initial value is `nil` but a specific validator is provided
|
122
|
+
# via `:match` or `:enum`, then `:allow_nil` defaults to true,
|
123
|
+
# otherwise it defaults to false.
|
124
|
+
#
|
125
|
+
# In many cases, you may find that the default validation behavior
|
126
|
+
# (interpreted from the initial value) is sufficient. If you want to
|
127
|
+
# accept any value, use `match: Object`.
|
128
|
+
#
|
129
|
+
# @param [String, Symbol] key The name of the option
|
130
|
+
# @param [Object] initial Initial value (defaults to nil)
|
131
|
+
# @param [Hash] opts Validation options
|
132
|
+
#
|
133
|
+
# @return [Config] self for chaining
|
134
|
+
#
|
135
|
+
def add_option! key, initial = nil, opts = {}, &block
|
136
|
+
key = validate_new_key! key
|
137
|
+
opts[:validator] = block if block
|
138
|
+
validator = resolve_validator! initial, opts
|
139
|
+
validate_value! key, validator, initial
|
140
|
+
@fields[key] = Option.new initial, initial, validator
|
141
|
+
define_getter_method! key
|
142
|
+
define_setter_method! key
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Add a subconfiguration field to this configuration.
|
148
|
+
#
|
149
|
+
# You must provide a key, which becomes the method name that you use to
|
150
|
+
# navigate to the subconfig. Names may comprise only letters, numerals,
|
151
|
+
# and underscores, and must begin with a letter.
|
152
|
+
#
|
153
|
+
# If you provide a block, the subconfig object is passed to the block,
|
154
|
+
# so you can easily add fields.
|
155
|
+
#
|
156
|
+
# @param [String, Symbol] key The name of the subconfig
|
157
|
+
#
|
158
|
+
# @return [Config] self for chaining
|
159
|
+
#
|
160
|
+
def add_config! key, &block
|
161
|
+
key = validate_new_key! key
|
162
|
+
@fields[key] = Config.new(&block)
|
163
|
+
define_getter_method! key
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Add a field to this configuration that is an alias of some other
|
169
|
+
# object, which may be another field or another configuration. This will
|
170
|
+
# effectively become an alternate "path" to that same object.
|
171
|
+
#
|
172
|
+
# The following cases are supported:
|
173
|
+
#
|
174
|
+
# * Alias another configuration at this key by providing a `config`
|
175
|
+
# parameter but not a `key`. The given configuration is effectively
|
176
|
+
# "attached" as a subconfiguration; both the original configuration
|
177
|
+
# path, and this new key, point to the same configuration object and
|
178
|
+
# share configuration data.
|
179
|
+
# * Alias another field of this current configuration by providing a
|
180
|
+
# `key` parameter but not a `config`. The new key simply refers to the
|
181
|
+
# same object (which may be an option or a subconfig) as the original
|
182
|
+
# key, and shares the same data.
|
183
|
+
# * Alias another field or another configuration, by providing both a
|
184
|
+
# `config` parameter and a `key` parameter.
|
185
|
+
#
|
186
|
+
# @param [String, Symbol] new_key The key to alias.
|
187
|
+
# @param [Config, nil] config The original configuration.
|
188
|
+
# @param [String, Symbol, nil] key The original field name.
|
189
|
+
#
|
190
|
+
# @return [Config] self for chaining
|
191
|
+
#
|
192
|
+
def add_alias! new_key, config: nil, key: nil
|
193
|
+
new_key = validate_new_key! new_key
|
194
|
+
if config.nil? && key.nil?
|
195
|
+
raise ArgumentError, "You must provide a config and/or key."
|
196
|
+
end
|
197
|
+
field =
|
198
|
+
if key.nil?
|
199
|
+
config
|
200
|
+
else
|
201
|
+
(config || self).raw_field! key
|
202
|
+
end
|
203
|
+
@fields[new_key] = field
|
204
|
+
define_getter_method! new_key
|
205
|
+
define_setter_method! new_key if field.is_a? Option
|
206
|
+
self
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# Restore the original default value of the given key.
|
211
|
+
# If the key refers to a subconfiguration, restore its contents,
|
212
|
+
# recursively. If the key is omitted, restore the original defaults for
|
213
|
+
# all keys, including subconfigurations recursively.
|
214
|
+
#
|
215
|
+
# @param [Symbol, nil] key The key to reset. If omitted or `nil`,
|
216
|
+
# recursively reset all fields and subconfigs.
|
217
|
+
#
|
218
|
+
def reset! key = nil
|
219
|
+
if key.nil?
|
220
|
+
# rubocop:disable Performance/HashEachMethods
|
221
|
+
@fields.keys.each { |k| reset! k }
|
222
|
+
# rubocop:enable Performance/HashEachMethods
|
223
|
+
else
|
224
|
+
key = key.to_sym
|
225
|
+
unless @fields.key? key
|
226
|
+
raise ArgumentError, "Key #{key.inspect} does not exist"
|
227
|
+
end
|
228
|
+
field = @fields[key]
|
229
|
+
if field.is_a? Config
|
230
|
+
field.reset!
|
231
|
+
else
|
232
|
+
field.value = field.default
|
233
|
+
end
|
234
|
+
end
|
235
|
+
self
|
236
|
+
end
|
237
|
+
|
238
|
+
##
|
239
|
+
# Remove the given key from the configuration.
|
240
|
+
# If the key is omitted, deletes all keys.
|
241
|
+
#
|
242
|
+
# Note the actual object being referenced is not touched. So if a deleted
|
243
|
+
# option is an alias of some other option, the other option will remain
|
244
|
+
# and retain the setting. Similarly, if a subconfig is referenced
|
245
|
+
# elsewhere, it will remain accessible from that other location.
|
246
|
+
#
|
247
|
+
# @param [Symbol, nil] key The key to delete. If omitted or `nil`,
|
248
|
+
# delete all fields and subconfigs.
|
249
|
+
#
|
250
|
+
def delete! key = nil
|
251
|
+
if key.nil?
|
252
|
+
@fields.clear
|
253
|
+
else
|
254
|
+
key = key.to_sym
|
255
|
+
unless @fields.key? key
|
256
|
+
raise ArgumentError, "Key #{key.inspect} does not exist"
|
257
|
+
end
|
258
|
+
field = @fields.delete key
|
259
|
+
singleton_class.send :remove_method, :"#{key}"
|
260
|
+
singleton_class.send :remove_method, :"#{key}=" if field.is_a? Option
|
261
|
+
end
|
262
|
+
self
|
263
|
+
end
|
264
|
+
|
265
|
+
##
|
266
|
+
# Assign an option with the given name to the given value.
|
267
|
+
#
|
268
|
+
# @param [Symbol, String] key The option name
|
269
|
+
# @param [Object] value The new option value
|
270
|
+
#
|
271
|
+
def []= key, value
|
272
|
+
key = key.to_sym
|
273
|
+
unless @fields.key? key
|
274
|
+
raise ArgumentError, "Key #{key.inspect} does not exist"
|
275
|
+
end
|
276
|
+
field = @fields[key]
|
277
|
+
if field.is_a? Config
|
278
|
+
raise ArgumentError, "Key #{key.inspect} is a subconfig"
|
279
|
+
end
|
280
|
+
validate_value! key, field.validator, value
|
281
|
+
field.value = value
|
282
|
+
end
|
283
|
+
|
284
|
+
##
|
285
|
+
# Get the option or subconfig with the given name.
|
286
|
+
#
|
287
|
+
# @param [Symbol, String] key The option or subconfig name
|
288
|
+
# @return [Object] The option value or subconfig object
|
289
|
+
#
|
290
|
+
def [] key
|
291
|
+
key = key.to_sym
|
292
|
+
unless @fields.key? key
|
293
|
+
raise ArgumentError, "Key #{key.inspect} does not exist"
|
294
|
+
end
|
295
|
+
field = @fields[key]
|
296
|
+
if field.is_a? Config
|
297
|
+
field
|
298
|
+
else
|
299
|
+
field.value
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
##
|
304
|
+
# Check if this Config object has an option of the given name.
|
305
|
+
#
|
306
|
+
# @param [Symbol] key The key to check for.
|
307
|
+
# @return [boolean] true if the inquired key is a valid option for this
|
308
|
+
# Config object. False otherwise.
|
309
|
+
#
|
310
|
+
def option? key
|
311
|
+
@fields[key.to_sym].is_a? Option
|
312
|
+
end
|
313
|
+
|
314
|
+
##
|
315
|
+
# Check if this Config object has a subconfig of the given name.
|
316
|
+
#
|
317
|
+
# @param [Symbol] key The key to check for.
|
318
|
+
# @return [boolean] true if the inquired key is a valid subconfig of this
|
319
|
+
# Config object. False otherwise.
|
320
|
+
#
|
321
|
+
def subconfig? key
|
322
|
+
@fields[key.to_sym].is_a? Config
|
323
|
+
end
|
324
|
+
|
325
|
+
##
|
326
|
+
# Check if this Config object has a key of the given name, regardless of
|
327
|
+
# whether it is an option or a subconfig.
|
328
|
+
#
|
329
|
+
# @param [Symbol] key The key to check for.
|
330
|
+
# @return [boolean] true if the key exists.
|
331
|
+
#
|
332
|
+
def key? key
|
333
|
+
@fields.key? key.to_sym
|
334
|
+
end
|
335
|
+
|
336
|
+
##
|
337
|
+
# Return a list of valid option names.
|
338
|
+
#
|
339
|
+
# @return [Array<Symbol>] a list of option names as symbols.
|
340
|
+
#
|
341
|
+
def options!
|
342
|
+
@fields.keys.find_all { |key| @fields[key].is_a? Option }
|
343
|
+
end
|
344
|
+
|
345
|
+
##
|
346
|
+
# Return a list of valid subconfig names.
|
347
|
+
#
|
348
|
+
# @return [Array<Symbol>] a list of subconfig names as symbols.
|
349
|
+
#
|
350
|
+
def subconfigs!
|
351
|
+
@fields.keys.find_all { |key| @fields[key].is_a? Config }
|
352
|
+
end
|
353
|
+
|
354
|
+
##
|
355
|
+
# Return a list of valid keys, including both options and subconfigs.
|
356
|
+
#
|
357
|
+
# @return [Array<Symbol>] a list of keys as symbols.
|
358
|
+
#
|
359
|
+
def keys!
|
360
|
+
@fields.keys
|
361
|
+
end
|
362
|
+
|
363
|
+
##
|
364
|
+
# Returns a string representation of this configuration state.
|
365
|
+
#
|
366
|
+
# @return [String]
|
367
|
+
#
|
368
|
+
def to_s!
|
369
|
+
elems = @fields.map do |k, v|
|
370
|
+
vstr =
|
371
|
+
if v.is_a? Config
|
372
|
+
v.to_s!
|
373
|
+
else
|
374
|
+
v.value.inspect
|
375
|
+
end
|
376
|
+
" #{k}=#{vstr}"
|
377
|
+
end
|
378
|
+
"<Config#{elems.join}>"
|
379
|
+
end
|
380
|
+
|
381
|
+
##
|
382
|
+
# Returns a nested hash representation of this configuration state,
|
383
|
+
# including subconfigurations.
|
384
|
+
#
|
385
|
+
# @return [Hash]
|
386
|
+
#
|
387
|
+
def to_h!
|
388
|
+
@fields.transform_values { |v| v.is_a?(Config) ? v.to_h! : v.value }
|
389
|
+
end
|
390
|
+
|
391
|
+
##
|
392
|
+
# Override the default to_s implementation.
|
393
|
+
#
|
394
|
+
# @private
|
395
|
+
#
|
396
|
+
def to_s
|
397
|
+
to_s!
|
398
|
+
end
|
399
|
+
|
400
|
+
##
|
401
|
+
# Override the default inspect implementation.
|
402
|
+
#
|
403
|
+
# @private
|
404
|
+
#
|
405
|
+
def inspect
|
406
|
+
to_s!
|
407
|
+
end
|
408
|
+
|
409
|
+
##
|
410
|
+
# Override the default to_h implementation.
|
411
|
+
#
|
412
|
+
# @private
|
413
|
+
#
|
414
|
+
def to_h
|
415
|
+
to_h!
|
416
|
+
end
|
417
|
+
|
418
|
+
protected
|
419
|
+
|
420
|
+
##
|
421
|
+
# Get the raw value of the field hash for the given key.
|
422
|
+
#
|
423
|
+
# @private
|
424
|
+
#
|
425
|
+
def raw_field! key
|
426
|
+
key = key.to_sym
|
427
|
+
unless @fields.key? key
|
428
|
+
raise ArgumentError, "Key #{key.inspect} does not exist"
|
429
|
+
end
|
430
|
+
@fields[key]
|
431
|
+
end
|
432
|
+
|
433
|
+
private
|
434
|
+
|
435
|
+
##
|
436
|
+
# Internal data structure to hold configuration options
|
437
|
+
#
|
438
|
+
# @private
|
439
|
+
#
|
440
|
+
Option = Struct.new :value, :default, :validator
|
441
|
+
|
442
|
+
##
|
443
|
+
# A validator that allows all values
|
444
|
+
#
|
445
|
+
# @private
|
446
|
+
#
|
447
|
+
OPEN_VALIDATOR = ::Proc.new { true }
|
448
|
+
|
449
|
+
def validate_new_key! key
|
450
|
+
key_str = key.to_s
|
451
|
+
unless key_str =~ /^\w+$/
|
452
|
+
raise ArgumentError, "Illegal key: #{key_str.inspect}"
|
453
|
+
end
|
454
|
+
key = key.to_sym
|
455
|
+
if @fields.key? key
|
456
|
+
raise ArgumentError, "Key #{key.inspect} already exists"
|
457
|
+
end
|
458
|
+
key
|
459
|
+
end
|
460
|
+
|
461
|
+
def resolve_validator! initial, opts
|
462
|
+
allow_nil = initial.nil? || opts[:allow_nil]
|
463
|
+
if opts.key? :validator
|
464
|
+
build_proc_validator! opts[:validator], allow_nil
|
465
|
+
elsif opts.key? :match
|
466
|
+
build_match_validator! opts[:match], allow_nil
|
467
|
+
elsif opts.key? :enum
|
468
|
+
build_enum_validator! opts[:enum], allow_nil
|
469
|
+
elsif [true, false].include? initial
|
470
|
+
build_enum_validator! [true, false], allow_nil
|
471
|
+
elsif initial.nil?
|
472
|
+
OPEN_VALIDATOR
|
473
|
+
else
|
474
|
+
build_match_validator! initial.class, allow_nil
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def build_match_validator! matches, allow_nil
|
479
|
+
matches = Array(matches)
|
480
|
+
matches += [nil] if allow_nil && !matches.include?(nil)
|
481
|
+
->(val) { matches.any? { |m| m === val } }
|
482
|
+
end
|
483
|
+
|
484
|
+
def build_enum_validator! allowed, allow_nil
|
485
|
+
allowed = Array(allowed)
|
486
|
+
allowed += [nil] if allow_nil && !allowed.include?(nil)
|
487
|
+
->(val) { allowed.include? val }
|
488
|
+
end
|
489
|
+
|
490
|
+
def build_proc_validator! proc, allow_nil
|
491
|
+
->(val) { proc.call(val) || allow_nil && val.nil? }
|
492
|
+
end
|
493
|
+
|
494
|
+
def validate_value! key, validator, value
|
495
|
+
unless validator.call value
|
496
|
+
raise ArgumentError,
|
497
|
+
"Invalid value #{value.inspect} for key #{key.inspect}"
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
def define_getter_method! key
|
502
|
+
define_singleton_method key do
|
503
|
+
field = @fields[key]
|
504
|
+
if field.is_a? Config
|
505
|
+
field
|
506
|
+
else
|
507
|
+
field.value
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def define_setter_method! key
|
513
|
+
define_singleton_method :"#{key}=" do |value|
|
514
|
+
field = @fields[key]
|
515
|
+
validate_value! key, field.validator, value
|
516
|
+
field.value = value
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|