CONTRIBUTING.md (17832B)
1 # Contributing to opentelemetry-go 2 3 The Go special interest group (SIG) meets regularly. See the 4 OpenTelemetry 5 [community](https://github.com/open-telemetry/community#golang-sdk) 6 repo for information on this and other language SIGs. 7 8 See the [public meeting 9 notes](https://docs.google.com/document/d/1E5e7Ld0NuU1iVvf-42tOBpu2VBBLYnh73GJuITGJTTU/edit) 10 for a summary description of past meetings. To request edit access, 11 join the meeting or get in touch on 12 [Slack](https://cloud-native.slack.com/archives/C01NPAXACKT). 13 14 ## Development 15 16 You can view and edit the source code by cloning this repository: 17 18 ```sh 19 git clone https://github.com/open-telemetry/opentelemetry-go.git 20 ``` 21 22 Run `make test` to run the tests instead of `go test`. 23 24 There are some generated files checked into the repo. To make sure 25 that the generated files are up-to-date, run `make` (or `make 26 precommit` - the `precommit` target is the default). 27 28 The `precommit` target also fixes the formatting of the code and 29 checks the status of the go module files. 30 31 Additionally, there is a `codespell` target that checks for common 32 typos in the code. It is not run by default, but you can run it 33 manually with `make codespell`. It will set up a virtual environment 34 in `venv` and install `codespell` there. 35 36 If after running `make precommit` the output of `git status` contains 37 `nothing to commit, working tree clean` then it means that everything 38 is up-to-date and properly formatted. 39 40 ## Pull Requests 41 42 ### How to Send Pull Requests 43 44 Everyone is welcome to contribute code to `opentelemetry-go` via 45 GitHub pull requests (PRs). 46 47 To create a new PR, fork the project in GitHub and clone the upstream 48 repo: 49 50 ```sh 51 go get -d go.opentelemetry.io/otel 52 ``` 53 54 (This may print some warning about "build constraints exclude all Go 55 files", just ignore it.) 56 57 This will put the project in `${GOPATH}/src/go.opentelemetry.io/otel`. You 58 can alternatively use `git` directly with: 59 60 ```sh 61 git clone https://github.com/open-telemetry/opentelemetry-go 62 ``` 63 64 (Note that `git clone` is *not* using the `go.opentelemetry.io/otel` name - 65 that name is a kind of a redirector to GitHub that `go get` can 66 understand, but `git` does not.) 67 68 This would put the project in the `opentelemetry-go` directory in 69 current working directory. 70 71 Enter the newly created directory and add your fork as a new remote: 72 73 ```sh 74 git remote add <YOUR_FORK> git@github.com:<YOUR_GITHUB_USERNAME>/opentelemetry-go 75 ``` 76 77 Check out a new branch, make modifications, run linters and tests, update 78 `CHANGELOG.md`, and push the branch to your fork: 79 80 ```sh 81 git checkout -b <YOUR_BRANCH_NAME> 82 # edit files 83 # update changelog 84 make precommit 85 git add -p 86 git commit 87 git push <YOUR_FORK> <YOUR_BRANCH_NAME> 88 ``` 89 90 Open a pull request against the main `opentelemetry-go` repo. Be sure to add the pull 91 request ID to the entry you added to `CHANGELOG.md`. 92 93 ### How to Receive Comments 94 95 * If the PR is not ready for review, please put `[WIP]` in the title, 96 tag it as `work-in-progress`, or mark it as 97 [`draft`](https://github.blog/2019-02-14-introducing-draft-pull-requests/). 98 * Make sure CLA is signed and CI is clear. 99 100 ### How to Get PRs Merged 101 102 A PR is considered **ready to merge** when: 103 104 * It has received two qualified approvals[^1]. 105 106 This is not enforced through automation, but needs to be validated by the 107 maintainer merging. 108 * The qualified approvals need to be from [Approver]s/[Maintainer]s 109 affiliated with different companies. Two qualified approvals from 110 [Approver]s or [Maintainer]s affiliated with the same company counts as a 111 single qualified approval. 112 * PRs introducing changes that have already been discussed and consensus 113 reached only need one qualified approval. The discussion and resolution 114 needs to be linked to the PR. 115 * Trivial changes[^2] only need one qualified approval. 116 117 * All feedback has been addressed. 118 * All PR comments and suggestions are resolved. 119 * All GitHub Pull Request reviews with a status of "Request changes" have 120 been addressed. Another review by the objecting reviewer with a different 121 status can be submitted to clear the original review, or the review can be 122 dismissed by a [Maintainer] when the issues from the original review have 123 been addressed. 124 * Any comments or reviews that cannot be resolved between the PR author and 125 reviewers can be submitted to the community [Approver]s and [Maintainer]s 126 during the weekly SIG meeting. If consensus is reached among the 127 [Approver]s and [Maintainer]s during the SIG meeting the objections to the 128 PR may be dismissed or resolved or the PR closed by a [Maintainer]. 129 * Any substantive changes to the PR require existing Approval reviews be 130 cleared unless the approver explicitly states that their approval persists 131 across changes. This includes changes resulting from other feedback. 132 [Approver]s and [Maintainer]s can help in clearing reviews and they should 133 be consulted if there are any questions. 134 135 * The PR branch is up to date with the base branch it is merging into. 136 * To ensure this does not block the PR, it should be configured to allow 137 maintainers to update it. 138 139 * It has been open for review for at least one working day. This gives people 140 reasonable time to review. 141 * Trivial changes[^2] do not have to wait for one day and may be merged with 142 a single [Maintainer]'s approval. 143 144 * All required GitHub workflows have succeeded. 145 * Urgent fix can take exception as long as it has been actively communicated 146 among [Maintainer]s. 147 148 Any [Maintainer] can merge the PR once the above criteria have been met. 149 150 [^1]: A qualified approval is a GitHub Pull Request review with "Approve" 151 status from an OpenTelemetry Go [Approver] or [Maintainer]. 152 [^2]: Trivial changes include: typo corrections, cosmetic non-substantive 153 changes, documentation corrections or updates, dependency updates, etc. 154 155 ## Design Choices 156 157 As with other OpenTelemetry clients, opentelemetry-go follows the 158 [OpenTelemetry Specification](https://opentelemetry.io/docs/specs/otel). 159 160 It's especially valuable to read through the [library 161 guidelines](https://opentelemetry.io/docs/specs/otel/library-guidelines). 162 163 ### Focus on Capabilities, Not Structure Compliance 164 165 OpenTelemetry is an evolving specification, one where the desires and 166 use cases are clear, but the method to satisfy those uses cases are 167 not. 168 169 As such, Contributions should provide functionality and behavior that 170 conforms to the specification, but the interface and structure is 171 flexible. 172 173 It is preferable to have contributions follow the idioms of the 174 language rather than conform to specific API names or argument 175 patterns in the spec. 176 177 For a deeper discussion, see 178 [this](https://github.com/open-telemetry/opentelemetry-specification/issues/165). 179 180 ## Documentation 181 182 Each non-example Go Module should have its own `README.md` containing: 183 184 - A pkg.go.dev badge which can be generated [here](https://pkg.go.dev/badge/). 185 - Brief description. 186 - Installation instructions (and requirements if applicable). 187 - Hyperlink to an example. Depending on the component the example can be: 188 - An `example_test.go` like [here](exporters/stdout/stdouttrace/example_test.go). 189 - A sample Go application with its own `README.md`, like [here](example/zipkin). 190 - Additional documentation sections such us: 191 - Configuration, 192 - Contributing, 193 - References. 194 195 [Here](exporters/jaeger/README.md) is an example of a concise `README.md`. 196 197 Moreover, it should be possible to navigate to any `README.md` from the 198 root `README.md`. 199 200 ## Style Guide 201 202 One of the primary goals of this project is that it is actually used by 203 developers. With this goal in mind the project strives to build 204 user-friendly and idiomatic Go code adhering to the Go community's best 205 practices. 206 207 For a non-comprehensive but foundational overview of these best practices 208 the [Effective Go](https://golang.org/doc/effective_go.html) documentation 209 is an excellent starting place. 210 211 As a convenience for developers building this project the `make precommit` 212 will format, lint, validate, and in some cases fix the changes you plan to 213 submit. This check will need to pass for your changes to be able to be 214 merged. 215 216 In addition to idiomatic Go, the project has adopted certain standards for 217 implementations of common patterns. These standards should be followed as a 218 default, and if they are not followed documentation needs to be included as 219 to the reasons why. 220 221 ### Configuration 222 223 When creating an instantiation function for a complex `type T struct`, it is 224 useful to allow variable number of options to be applied. However, the strong 225 type system of Go restricts the function design options. There are a few ways 226 to solve this problem, but we have landed on the following design. 227 228 #### `config` 229 230 Configuration should be held in a `struct` named `config`, or prefixed with 231 specific type name this Configuration applies to if there are multiple 232 `config` in the package. This type must contain configuration options. 233 234 ```go 235 // config contains configuration options for a thing. 236 type config struct { 237 // options ... 238 } 239 ``` 240 241 In general the `config` type will not need to be used externally to the 242 package and should be unexported. If, however, it is expected that the user 243 will likely want to build custom options for the configuration, the `config` 244 should be exported. Please, include in the documentation for the `config` 245 how the user can extend the configuration. 246 247 It is important that internal `config` are not shared across package boundaries. 248 Meaning a `config` from one package should not be directly used by another. The 249 one exception is the API packages. The configs from the base API, eg. 250 `go.opentelemetry.io/otel/trace.TracerConfig` and 251 `go.opentelemetry.io/otel/metric.InstrumentConfig`, are intended to be consumed 252 by the SDK therefore it is expected that these are exported. 253 254 When a config is exported we want to maintain forward and backward 255 compatibility, to achieve this no fields should be exported but should 256 instead be accessed by methods. 257 258 Optionally, it is common to include a `newConfig` function (with the same 259 naming scheme). This function wraps any defaults setting and looping over 260 all options to create a configured `config`. 261 262 ```go 263 // newConfig returns an appropriately configured config. 264 func newConfig(options ...Option) config { 265 // Set default values for config. 266 config := config{/* […] */} 267 for _, option := range options { 268 config = option.apply(config) 269 } 270 // Perform any validation here. 271 return config 272 } 273 ``` 274 275 If validation of the `config` options is also performed this can return an 276 error as well that is expected to be handled by the instantiation function 277 or propagated to the user. 278 279 Given the design goal of not having the user need to work with the `config`, 280 the `newConfig` function should also be unexported. 281 282 #### `Option` 283 284 To set the value of the options a `config` contains, a corresponding 285 `Option` interface type should be used. 286 287 ```go 288 type Option interface { 289 apply(config) config 290 } 291 ``` 292 293 Having `apply` unexported makes sure that it will not be used externally. 294 Moreover, the interface becomes sealed so the user cannot easily implement 295 the interface on its own. 296 297 The `apply` method should return a modified version of the passed config. 298 This approach, instead of passing a pointer, is used to prevent the config from being allocated to the heap. 299 300 The name of the interface should be prefixed in the same way the 301 corresponding `config` is (if at all). 302 303 #### Options 304 305 All user configurable options for a `config` must have a related unexported 306 implementation of the `Option` interface and an exported configuration 307 function that wraps this implementation. 308 309 The wrapping function name should be prefixed with `With*` (or in the 310 special case of a boolean options `Without*`) and should have the following 311 function signature. 312 313 ```go 314 func With*(…) Option { … } 315 ``` 316 317 ##### `bool` Options 318 319 ```go 320 type defaultFalseOption bool 321 322 func (o defaultFalseOption) apply(c config) config { 323 c.Bool = bool(o) 324 return c 325 } 326 327 // WithOption sets a T to have an option included. 328 func WithOption() Option { 329 return defaultFalseOption(true) 330 } 331 ``` 332 333 ```go 334 type defaultTrueOption bool 335 336 func (o defaultTrueOption) apply(c config) config { 337 c.Bool = bool(o) 338 return c 339 } 340 341 // WithoutOption sets a T to have Bool option excluded. 342 func WithoutOption() Option { 343 return defaultTrueOption(false) 344 } 345 ``` 346 347 ##### Declared Type Options 348 349 ```go 350 type myTypeOption struct { 351 MyType MyType 352 } 353 354 func (o myTypeOption) apply(c config) config { 355 c.MyType = o.MyType 356 return c 357 } 358 359 // WithMyType sets T to have include MyType. 360 func WithMyType(t MyType) Option { 361 return myTypeOption{t} 362 } 363 ``` 364 365 ##### Functional Options 366 367 ```go 368 type optionFunc func(config) config 369 370 func (fn optionFunc) apply(c config) config { 371 return fn(c) 372 } 373 374 // WithMyType sets t as MyType. 375 func WithMyType(t MyType) Option { 376 return optionFunc(func(c config) config { 377 c.MyType = t 378 return c 379 }) 380 } 381 ``` 382 383 #### Instantiation 384 385 Using this configuration pattern to configure instantiation with a `NewT` 386 function. 387 388 ```go 389 func NewT(options ...Option) T {…} 390 ``` 391 392 Any required parameters can be declared before the variadic `options`. 393 394 #### Dealing with Overlap 395 396 Sometimes there are multiple complex `struct` that share common 397 configuration and also have distinct configuration. To avoid repeated 398 portions of `config`s, a common `config` can be used with the union of 399 options being handled with the `Option` interface. 400 401 For example. 402 403 ```go 404 // config holds options for all animals. 405 type config struct { 406 Weight float64 407 Color string 408 MaxAltitude float64 409 } 410 411 // DogOption apply Dog specific options. 412 type DogOption interface { 413 applyDog(config) config 414 } 415 416 // BirdOption apply Bird specific options. 417 type BirdOption interface { 418 applyBird(config) config 419 } 420 421 // Option apply options for all animals. 422 type Option interface { 423 BirdOption 424 DogOption 425 } 426 427 type weightOption float64 428 429 func (o weightOption) applyDog(c config) config { 430 c.Weight = float64(o) 431 return c 432 } 433 434 func (o weightOption) applyBird(c config) config { 435 c.Weight = float64(o) 436 return c 437 } 438 439 func WithWeight(w float64) Option { return weightOption(w) } 440 441 type furColorOption string 442 443 func (o furColorOption) applyDog(c config) config { 444 c.Color = string(o) 445 return c 446 } 447 448 func WithFurColor(c string) DogOption { return furColorOption(c) } 449 450 type maxAltitudeOption float64 451 452 func (o maxAltitudeOption) applyBird(c config) config { 453 c.MaxAltitude = float64(o) 454 return c 455 } 456 457 func WithMaxAltitude(a float64) BirdOption { return maxAltitudeOption(a) } 458 459 func NewDog(name string, o ...DogOption) Dog {…} 460 func NewBird(name string, o ...BirdOption) Bird {…} 461 ``` 462 463 ### Interfaces 464 465 To allow other developers to better comprehend the code, it is important 466 to ensure it is sufficiently documented. One simple measure that contributes 467 to this aim is self-documenting by naming method parameters. Therefore, 468 where appropriate, methods of every exported interface type should have 469 their parameters appropriately named. 470 471 #### Interface Stability 472 473 All exported stable interfaces that include the following warning in their 474 documentation are allowed to be extended with additional methods. 475 476 > Warning: methods may be added to this interface in minor releases. 477 478 Otherwise, stable interfaces MUST NOT be modified. 479 480 If new functionality is needed for an interface that cannot be changed it MUST 481 be added by including an additional interface. That added interface can be a 482 simple interface for the specific functionality that you want to add or it can 483 be a super-set of the original interface. For example, if you wanted to a 484 `Close` method to the `Exporter` interface: 485 486 ```go 487 type Exporter interface { 488 Export() 489 } 490 ``` 491 492 A new interface, `Closer`, can be added: 493 494 ```go 495 type Closer interface { 496 Close() 497 } 498 ``` 499 500 Code that is passed the `Exporter` interface can now check to see if the passed 501 value also satisfies the new interface. E.g. 502 503 ```go 504 func caller(e Exporter) { 505 /* ... */ 506 if c, ok := e.(Closer); ok { 507 c.Close() 508 } 509 /* ... */ 510 } 511 ``` 512 513 Alternatively, a new type that is the super-set of an `Exporter` can be created. 514 515 ```go 516 type ClosingExporter struct { 517 Exporter 518 Close() 519 } 520 ``` 521 522 This new type can be used similar to the simple interface above in that a 523 passed `Exporter` type can be asserted to satisfy the `ClosingExporter` type 524 and the `Close` method called. 525 526 This super-set approach can be useful if there is explicit behavior that needs 527 to be coupled with the original type and passed as a unified type to a new 528 function, but, because of this coupling, it also limits the applicability of 529 the added functionality. If there exist other interfaces where this 530 functionality should be added, each one will need their own super-set 531 interfaces and will duplicate the pattern. For this reason, the simple targeted 532 interface that defines the specific functionality should be preferred. 533 534 ## Approvers and Maintainers 535 536 ### Approvers 537 538 - [Evan Torrie](https://github.com/evantorrie), Verizon Media 539 - [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics 540 - [David Ashpole](https://github.com/dashpole), Google 541 - [Robert Pająk](https://github.com/pellared), Splunk 542 - [Chester Cheung](https://github.com/hanyuancheung), Tencent 543 - [Damien Mathieu](https://github.com/dmathieu), Elastic 544 545 ### Maintainers 546 547 - [Aaron Clawson](https://github.com/MadVikingGod), LightStep 548 - [Anthony Mirabella](https://github.com/Aneurysm9), AWS 549 - [Tyler Yahn](https://github.com/MrAlias), Splunk 550 551 ### Emeritus 552 553 - [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep 554 - [Josh MacDonald](https://github.com/jmacd), LightStep 555 556 ### Become an Approver or a Maintainer 557 558 See the [community membership document in OpenTelemetry community 559 repo](https://github.com/open-telemetry/community/blob/main/community-membership.md). 560 561 [Approver]: #approvers 562 [Maintainer]: #maintainers