Add Infra

This commit is contained in:
aditya.siregar
2025-03-03 18:59:25 +07:00
parent c02d8cda69
commit c9f1e33c24
1782 changed files with 692894 additions and 21 deletions
+18
View File
@@ -0,0 +1,18 @@
ignore:
- "docs/ex/**/*.go"
- "internal/e2e/**/*.go"
coverage:
range: 80..100
round: down
precision: 2
status:
project: # measuring the overall project coverage
default: # context, you can create multiple ones with custom titles
enabled: yes # must be yes|true to enable this status
target: 90% # specify the target coverage for each commit status
# option: "auto" (must increase from parent commit or pull request base)
# option: "X%" a static target percentage to hit
if_not_found: success # if parent is not found report status as success, error, or failure
if_ci_failed: error # if ci fails report status as success, error, or failure
+16
View File
@@ -0,0 +1,16 @@
/vendor
/.bench
*.mem
*.cpu
*.test
*.log
*.out
*.html
*.coverprofile
coverage.txt
*.pprof
/.bin
/.cache
/bin
.vscode
.mdoxcache
+95
View File
@@ -0,0 +1,95 @@
output:
# Make output more digestible with quickfix in vim/emacs/etc.
sort-results: true
print-issued-lines: false
linters:
# We'll track the golangci-lint default linters manually
# instead of letting them change without our control.
disable-all: true
enable:
# golangci-lint defaults:
- gosimple
- govet
- ineffassign
- staticcheck
- unused
# Our own extras:
- gofumpt
- nolintlint # lints nolint directives
- revive
- errorlint
# License header check:
- goheader
linters-settings:
govet:
# These govet checks are disabled by default, but they're useful.
enable:
- niliness
- reflectvaluecompare
- sortslice
- unusedwrite
goheader:
values:
const:
COMPANY: 'Uber Technologies, Inc.'
regexp:
YEAR_RANGE: '\d{4}(-\d{4})?'
template: |-
Copyright (c) {{ YEAR_RANGE }} {{ COMPANY }}
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
issues:
# Print all issues reported by all linters.
max-issues-per-linter: 0
max-same-issues: 0
# Don't ignore some of the issues that golangci-lint considers okay.
# This includes documenting all exported entities.
exclude-use-default: false
exclude-rules:
# Don't warn on unused parameters.
# Parameter names are useful; replacing them with '_' is undesirable.
- linters: [revive]
text: 'unused-parameter: parameter \S+ seems to be unused, consider removing or renaming it as _'
# staticcheck already has smarter checks for empty blocks.
# revive's empty-block linter has false positives.
# For example, as of writing this, the following is not allowed.
# for foo() { }
- linters: [revive]
text: 'empty-block: this block is empty, you can remove it'
# It's okay if internal packages and examples in docs/
# don't have package comments.
- linters: [revive]
path: '.+/internal/.+|^internal/.+|^docs/.+'
text: 'should have a package comment'
# It's okay for tests to use dot imports.
- linters: [revive]
path: '_test\.go$'
text: 'should not use dot imports'
+465
View File
@@ -0,0 +1,465 @@
---
search:
exclude: true
---
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.23.0](https://github.com/uber-go/fx/compare/v1.22.2...v1.22.3) - 2024-10-11
### Added
- Added `Runtime` to `fxevent.Run` event, which stores the runtime of
a constructor or a decorator that's run, including functions created
by `fx.Supply` and `fx.Replace`.
### Changed
- Overhauled the documentation website. (https://uber-go.github.io/fx/)
## [1.22.2](https://github.com/uber-go/fx/compare/v1.22.1...v1.22.2) - 2024-08-07
### Fixed
- A deadlock with the relayer in signal receivers.
### Changed
- Upgrade Dig dependency to v1.18.0
## [1.22.1](https://github.com/uber-go/fx/compare/v1.22.0...v1.22.1) - 2024-06-25
### Fixed
- Fx apps will only listen to signals when `.Run()`, `.Wait()`, or `.Done()`
are called, fixing a regression introduced in v1.19.0.
## [1.22.0](https://github.com/uber-go/fx/compare/v1.21.1...v1.22.0) - 2024-05-30
### Added
- Add `fx.Self` which can be passed to the `fx.As` annotation to signify
that a type should be provided as itself.
- Add `fxtest.EnforceTimeout` that can be passed to `fxtest.NewLifecycle`
to force `Start` and `Stop` to return context errors when hook context expires.
### Changed
- `fx.Private` can now be used with `fx.Supply`.
### Fixed
- Fx apps will no longer listen to OS signals when they are stopped,
solving blocking issues in programs that depended on OS signals
after an Fx app stops.
## [1.21.1](https://github.com/uber-go/fx/compare/v1.21.0...v1.21.1) - 2024-04-24
### Changed
- Register Fx provides (e.g. fx.Lifecycle, fx.Shutdowner, fx.DotGraph) before
user provides, to increase likelihood of successful custom logger creation.
## [1.21.0](https://github.com/uber-go/fx/compare/v1.20.1...v1.21.0) - 2024-03-13
### Added
- fxtest: Add WithTestLogger option that uses a `testing.TB` as the
Fx event logger.
- An fxevent logger that can log events using a slog logger has been added.
### Changed
- Upgrade Dig dependency to v1.17.1
## [1.20.1](https://github.com/uber-go/fx/compare/v1.20.0...v1.20.1) - 2023-10-17
### Added
- Provided, Decorated, Supplied, and Replaced events now include a trace
of module locations through which the option was given to the App.
- wasi support.
## [1.20.0](https://github.com/uber-go/fx/compare/v1.19.3...v1.20.0) - 2023-06-12
### Added
- A new event `fxevent.Run` is now emitted when Fx runs a constructor, decorator,
or supply/replace stub.
### Changed
- `fx.Populate` now works with `fx.Annotate`.
- Upgrade Dig dependency to v1.17.0.
## [1.19.3](https://github.com/uber-go/fx/compare/v1.19.2...v1.19.3) - 2023-04-17
### Changed
- Fixed several typos in docs.
- WASM build support.
- Annotating In and Out structs with From/As annotations generated invalid results.
The annotation check now blocks this.
- `Shutdown`: Support calling from `Invoke`.
### Deprecated
- Deprecate `ShutdownTimeout` option.
### Fixed
- Respect Shutdowner ExitCode from calling `Run`.
## [1.19.2](https://github.com/uber-go/fx/compare/v1.19.1...v1.19.2) - 2023-02-21
### Changed
- Update Dig dependency to v1.16.1.
## [1.19.1](https://github.com/uber-go/fx/compare/v1.19.0...v1.19.1) - 2023-01-10
### Changed
- Calling `fx.Stop()` after the `App` has already stopped no longer errors out.
### Fixed
- Addressed a regression in 1.19.0 release which caused apps to ignore OS signals
after running for startTimeout duration.
## [1.19.0](https://github.com/uber-go/fx/compare/v1.18.2...v1.19.0) - 2023-01-03
### Added
- `fx.RecoverFromPanics` Option which allows Fx to recover from user-provided constructors
and invoked functions.
- `fx.Private` that allows the constructor to limit the scope of its outputs to the wrapping
`fx.Module`.
- `ExitCode` ShutdownOption which allows setting custom exit code at the end of app
lifecycle.
- `Wait` which returns a channel that can be used for waiting on application shutdown.
- fxevent/ZapLogger now exposes `UseLogLevel` and `UseErrorLevel` methods to set
the level of the Zap logs produced by it.
- Add lifecycle hook-convertible methods: `StartHook`, `StopHook`, `StartStopHook`
that can be used with more function signatures.
### Changed
- `fx.WithLogger` can now be passed at `fx.Module` level, setting custom logger at
`Module` scope instead of the whole `App`.
### Fixed
- `fx.OnStart` and `fx.OnStop` Annotations now work with annotated types that was
provided by the annotated constructor.
- fxevent/ZapLogger: Errors from `fx.Supply` are now logged at `Error` level, not
`Info`.
- A race condition in lifecycle Start/Stop methods.
- Typos in docs.
## [1.18.2](https://github.com/uber-go/fx/compare/v1.18.1...v1.18.2) - 2022-09-28
### Added
- Clarify ordering of `Invoke`s in `Module`s.
### Fixed
- Fix `Decorate` not being applied to transitive dependencies at root `App` level.
## [1.18.1](https://github.com/uber-go/fx/compare/v1.18.0...v1.18.1) - 2022-08-08
### Fixed
- Fix a nil panic when `nil` is passed to `OnStart` and `OnStop` lifecycle methods.
## [1.18.0](https://github.com/uber-go/fx/compare/v1.17.1...v1.18.0) - 2022-08-05
### Added
- Soft value groups that lets you specify value groups as best-effort dependencies.
- `fx.OnStart` and `fx.OnStop` annotations which lets you annotate dependencies to provide
OnStart and OnStop lifecycle hooks.
- A new `fxevent.Replaced` event written to `fxevent.Logger` following an `fx.Replace`.
### Fixed
- Upgrade Dig dependency to v1.14.1 to address a couple of issues with decorations. Refer to
Dig v1.14.1 release notes for more details.
- `fx.WithLogger` no longer ignores decorations and replacements of types that
it depends on.
- Don't run lifecycle hooks if the context for them has already expired.
- `App.Start` and `App.Stop` no longer deadlock if the OnStart/OnStop hook
exits the current goroutine.
- `fxevent.ConsoleLogger` no longer emits an extraneous argument for the
Supplied event.
### Deprecated
- `fx.Extract` in favor of `fx.Populate`.
## [1.17.1](https://github.com/uber-go/fx/compare/v1.17.0...v1.17.1) - 2022-03-23
### Added
- Logging for provide/invoke/decorate now includes the associated `fx.Module` name.
## [1.17.0](https://github.com/uber-go/fx/compare/v1.16.0...v1.17.0) - 2022-02-28
### Added
- Add `fx.Module` which scopes any modifications made to the dependency graph.
- Add `fx.Decorate` and `fx.Replace` that lets you modify a dependency graph with decorators.
- Add `fxevent.Decorated` event which gets emitted upon a dependency getting decorated.
### Changed
- `fx.Annotate`: Validate that `fx.In` or `fx.Out` structs are not passed to it.
- `fx.Annotate`: Upon failure to Provide, the error contains the actual location
of the provided constructor.
## [1.16.0](https://github.com/uber-go/fx/compare/v1.15.0...v1.16.0) - 2021-12-02
### Added
- Add the ability to provide a function as multiple interfaces at once using `fx.As`.
### Changed
- `fx.Annotate`: support variadic functions, and feeding value groups into them.
### Fixed
- Fix an issue where OnStop hooks weren't getting called on SIGINT on Windows.
- Fix a data race between app.Done() and shutdown.
## [1.15.0](https://github.com/uber-go/fx/compare/v1.14.2...v1.15.0) - 2021-11-08
### Added
- Add `fx.Annotate` to allow users to provide parameter and result tags easily without
having to create `fx.In` or `fx.Out` structs.
- Add `fx.As` that allows users to annotate a constructor to provide its result type(s) as
interface(s) that they implement instead of the types themselves.
### Fixed
- Fix `fxevent.Stopped` not being logged when `App.Stop` is called.
- Fix `fxevent.Started` or `fxevent.Stopped` not being logged when start or
stop times out.
## [1.14.2](https://github.com/uber-go/fx/compare/v1.14.1...v1.14.2) - 2021-08-16
### Changed
- For `fxevent` console implementation: no longer log non-error case for `fxevent.Invoke`
event, while for zap implementation, start logging `fx.Invoking` case without stack.
## [1.14.1](https://github.com/uber-go/fx/compare/v1.14.0...v1.14.1) - 2021-08-16
### Changed
- `fxevent.Invoked` was being logged at `Error` level even upon successful `Invoke`.
This was changed to log at `Info` level when `Invoke` succeeded.
## [1.14.0](https://github.com/uber-go/fx/compare/v1.13.1...v1.14.0) - 2021-08-12
### Added
- Introduce the new `fx.WithLogger` option. Provide a constructor for
`fxevent.Logger` objects with it to customize how Fx logs events.
- Add new `fxevent` package that exposes events from Fx in a structured way.
Use this to write custom logger implementations for use with the
`fx.WithLogger` option.
- Expose and log additional information when lifecycle hooks time out.
### Changed
- Fx now emits structured JSON logs by default. These may be parsed and
processed by log ingestion systems.
- `fxtest.Lifecycle` now logs to the provided `testing.TB` instead of stderr.
- `fx.In` and `fx.Out` are now type aliases instead of structs.
## [1.13.1](https://github.com/uber-go/fx/compare/v1.13.0...v1.13.1) - 2020-08-19
### Fixed
- Fix minimum version constraint for dig. `fx.ValidateGraph` requires at least
dig 1.10.
## [1.13.0](https://github.com/uber-go/fx/compare/v1.12.0...v1.13.0) - 2020-06-16
### Added
- Added `fx.ValidateGraph` which allows graph cycle validation and dependency correctness
without running anything. This is useful if `fx.Invoke` has side effects, does I/O, etc.
## [1.12.0](https://github.com/uber-go/fx/compare/v1.11.0...v1.12.0) - 2020-04-09
### Added
- Added `fx.Supply` to provide externally created values to Fx containers
without building anonymous constructors.
### Changed
- Drop library dependency on development tools.
## [1.11.0](https://github.com/uber-go/fx/compare/v1.10.0...v1.11.0) - 2020-04-01
### Added
- Value groups can use the `flatten` option to indicate values in a slice should
be provided individually rather than providing the slice itself. See package
documentation for details.
## [1.10.0](https://github.com/uber-go/fx/compare/v1.9.0...v1.10.0) - 2019-11-20
### Added
- All `fx.Option`s now include readable string representations.
- Report stack traces when `fx.Provide` and `fx.Invoke` calls fail. This
should make these errors more debuggable.
### Changed
- Migrated to Go modules.
## [1.9.0](https://github.com/uber-go/fx/compare/v1.8.0...v1.9.0) - 2019-01-22
### Added
- Add the ability to shutdown Fx applications from inside the container. See
the Shutdowner documentation for details.
- Add `fx.Annotated` to allow users to provide named values without creating a
new constructor.
## [1.8.0](https://github.com/uber-go/fx/compare/v1.7.1...v1.8.0) - 2018-11-06
### Added
- Provide DOT graph of dependencies in the container.
## [1.7.1](https://github.com/uber-go/fx/compare/v1.7.0...v1.7.1) - 2018-09-26
### Fixed
- Make `fxtest.New` ensure that the app was created successfully. Previously,
it would return the app (similar to `fx.New`, which expects the user to verify
the error).
- Update dig container to defer acyclic validation until after Invoke. Application
startup time should improve proportional to the size of the dependency graph.
- Fix a goroutine leak in `fxtest.Lifecycle`.
## [1.7.0](https://github.com/uber-go/fx/compare/v1.6.0...v1.7.0) - 2018-08-16
### Added
- Add `fx.ErrorHook` option to allow users to provide `ErrorHandler`s on invoke
failures.
- `VisualizeError` returns the visualization wrapped in the error if available.
## [1.6.0](https://github.com/uber-go/fx/compare/v1.5.0...v1.6.0) - 2018-06-12
### Added
- Add `fx.Error` option to short-circuit application startup.
## [1.5.0](https://github.com/uber-go/fx/compare/v1.4.0...v1.5.0) - 2018-04-11
### Added
- Add `fx.StartTimeout` and `fx.StopTimeout` to make configuring application
start and stop timeouts easier.
- Export the default start and stop timeout as `fx.DefaultTimeout`.
### Fixed
- Make `fxtest` respect the application's start and stop timeouts.
## [1.4.0](https://github.com/uber-go/fx/compare/v1.3.0...v1.4.0) - 2017-12-07
### Added
- Add `fx.Populate` to populate variables with values from the dependency
injection container without requiring intermediate structs.
## [1.3.0](https://github.com/uber-go/fx/compare/v1.2.0...v1.3.0) - 2017-11-28
### Changed
- Improve readability of hook logging in addition to provide and invoke.
### Fixed
- Fix bug which caused the OnStop for a lifecycle hook to be called even if it
failed to start.
## [1.2.0](https://github.com/uber-go/fx/compare/v1.1.0...v1.2.0) - 2017-09-06
### Added
- Add `fx.NopLogger` which disables the Fx application's log output.
## [1.1.0](https://github.com/uber-go/fx/compare/v1.0.0...v1.1.0) - 2017-08-22
### Changed
- Improve readability of start up logging.
## [1.0.0](https://github.com/uber-go/fx/compare/v1.0.0-rc2...v1.0.0) - 2017-07-31
First stable release: no breaking changes will be made in the 1.x series.
### Added
- `fx.Extract` now supports `fx.In` tags on target structs.
### Changed
- **[Breaking]** Rename `fx.Inject` to `fx.Extract`.
- **[Breaking]** Rename `fxtest.Must*` to `fxtest.Require*`.
### Removed
- **[Breaking]** Remove `fx.Timeout` and `fx.DefaultTimeout`.
## [1.0.0-rc2](https://github.com/uber-go/fx/compare/v1.0.0-rc1...v1.0.0-rc2) - 2017-07-21
- **[Breaking]** Lifecycle hooks now take a context.
- Add `fx.In` and `fx.Out` which exposes optional and named types.
Modules should embed these types instead of relying on `dig.In` and `dig.Out`.
- Add an `Err` method to retrieve the underlying errors during the dependency
graph construction. The same error is also returned from `Start`.
- Graph resolution now happens as part of `fx.New`, rather than at the beginning
of `app.Start`. This allows inspection of the graph errors through `app.Err()`
before the decision to start the app.
- Add a `Logger` option, which allows users to send Fx's logs to different
sink.
- Add `fxtest.App`, which redirects log output to the user's `testing.TB` and
provides some lifecycle helpers.
## [1.0.0-rc1](https://github.com/uber-go/fx/compare/v1.0.0-beta4...v1.0.0-rc1) - 2017-06-20
- **[Breaking]** Providing types into `fx.App` and invoking functions are now
options passed during application construction. This makes users'
interactions with modules and collections of modules identical.
- **[Breaking]** `TestLifecycle` is now in a separate `fxtest` subpackage.
- Add `fx.Inject()` to pull values from the container into a struct.
## [1.0.0-beta4](https://github.com/uber-go/fx/compare/v1.0.0-beta3...v1.0.0-beta4) - 2017-06-12
- **[Breaking]** Monolithic framework, as released in initial betas, has been
broken into smaller pieces as a result of recent advances in `dig` library.
This is a radical departure from the previous direction, but it needed to
be done for the long-term good of the project.
- **[Breaking]** `Module interface` has been scoped all the way down to being
*a single dig constructor*. This allows for very sophisticated module
compositions. See `go.uber.org/dig` for more information on the constructors.
- **[Breaking]** `package config` has been moved to its own repository.
see `go.uber.org/config` for more information.
- `fx.Lifecycle` has been added for modules to hook into the framework
lifecycle events.
- `service.Host` interface which composed a number of primitives together
(configuration, metrics, tracing) has been deprecated in favor of
`fx.App`.
## [1.0.0-beta3](https://github.com/uber-go/fx/compare/v1.0.0-beta2...v1.0.0-beta3) - 2017-03-28
- **[Breaking]** Environment config provider was removed. If you were using
environment variables to override YAML values, see config documentation for
more information.
- **[Breaking]** Simplify Provider interface: remove `Scope` method from the
`config.Provider` interface, one can use either ScopedProvider and Value.Get()
to access sub fields.
- Add `task.MustRegister` convenience function which fails fast by panicking
Note that this should only be used during app initialization, and is provided
to avoid repetetive error checking for services which register many tasks.
- Expose options on task module to disable execution. This will allow users to
enqueue and consume tasks on different clusters.
- **[Breaking]** Rename Backend interface `Publish` to `Enqueue`. Created a new
`ExecuteAsync` method that will kick off workers to consume tasks and this is
subsumed by module Start.
- **[Breaking]** Rename package `uhttp/client` to `uhttp/uhttpclient` for clarity.
- **[Breaking]** Rename `PopulateStruct` method in value to `Populate`.
The method can now populate not only structs, but anything: slices,
maps, builtin types and maps.
- **[Breaking]** `package dig` has moved from `go.uber.org/fx/dig` to a new home
at `go.uber.org/dig`.
- **[Breaking]** Pass a tracer the `uhttp/uhttpclient` constructor explicitly, instead
of using a global tracer. This will allow to use http client in parallel tests.
## [1.0.0-beta2](https://github.com/uber-go/fx/compare/v1.0.0-beta1...v1.0.0-beta2) - 2017-03-09
- **[Breaking]** Remove `ulog.Logger` interface and expose `*zap.Logger` directly.
- **[Breaking]** Rename config and module from `modules.rpc` to `modules.yarpc`
- **[Breaking]** Rename config key from `modules.http` to `modules.uhttp` to match
the module name
- **[Breaking]** Upgrade `zap` to `v1.0.0-rc.3` (now go.uber.org/zap, was
github.com/uber-go/zap)
- Remove now-unused `config.IsDevelopmentEnv()` helper to encourage better
testing practices. Not a breaking change as nobody is using this func
themselves according to our code search tool.
- Log `traceID` and `spanID` in hex format to match Jaeger UI. Upgrade Jaeger to
min version 2.1.0
and use jaeger's adapters for jaeger and tally initialization.
- Tally now supports reporting histogram samples for a bucket. Upgrade Tally to 2.1.0
- **[Breaking]** Make new module naming consistent `yarpc.ThriftModule` to
`yarpc.New`, `task.NewModule`
to `task.New`
- **[Breaking]** Rename `yarpc.CreateThriftServiceFunc` to `yarpc.ServiceCreateFunc`
as it is not thrift-specific.
- Report version metrics for company-wide version usage information.
- Allow configurable service name and module name via service options.
- DIG constructors now support returning a tuple with the second argument being
an error.
## 1.0.0-beta1 - 2017-02-20
This is the first beta release of the framework, where we invite users to start
building services on it and provide us feedback. **Warning** we are not
promising API compatibility between beta releases and the final 1.0.0 release.
In fact, we expect our beta user feedback to require some changes to the way
things work. Once we reach 1.0, we will provider proper version compatibility.
+254
View File
@@ -0,0 +1,254 @@
---
search:
exclude: true
---
# Contributing
Thanks for helping to make Fx better for everyone!
If you'd like to add new exported APIs,
please [open an issue](https://github.com/uber-go/fx/issues/new)
describing your proposal.
Discussing API changes ahead of time makes pull request review much smoother.
!!! tip
You'll need to sign [Uber's CLA](https://cla-assistant.io/uber-go/fx)
before we can accept any of your contributions.
If necessary, a bot will remind
you to accept the CLA when you open your pull request.
## Contribute code
Set up your local development environment to contribute to Fx.
1. [Fork](https://github.com/uber-go/fx/fork), then clone the repository.
=== "Git"
```bash
git clone https://github.com/your_github_username/fx.git
cd fx
git remote add upstream https://github.com/uber-go/fx.git
git fetch upstream
```
=== "GitHub CLI"
```bash
gh repo fork --clone uber-go/fx
```
2. Install Fx's dependencies:
```bash
go mod download
```
3. Verify that tests and other checks pass locally.
```bash
make lint
make test
```
Note that for `make lint` to work,
you must be using the latest stable version of Go.
If you're on an older version, you can still contribute your change,
but we may discover style violations when you open the pull request.
Next, make your changes.
1. Create a new feature branch.
```bash
git checkout master
git pull
git checkout -b cool_new_feature
```
2. Make your changes, and verify that all tests and lints still pass.
```bash
$EDITOR app.go
make lint
make test
```
3. When you're satisfied with the change,
push it to your fork and make a pull request.
=== "Git"
```bash
git push origin cool_new_feature
# Open a PR at https://github.com/uber-go/fx/compare
```
=== "GitHub CLI"
```bash
gh pr create
```
At this point, you're waiting on us to review your changes.
We *try* to respond to issues and pull requests within a few business days,
and we may suggest some improvements or alternatives.
Once your changes are approved, one of the project maintainers will merge them.
The review process will go more smoothly if you:
- add tests for new functionality
- write a [good commit message](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
- maintain backward compatibility
- follow our [style guide](https://github.com/uber-go/guide/blob/master/style.md)
## Contribute documentation
To contribute documentation to Fx,
1. Set up your local development environment
as you would to [contribute code](#contribute-code).
2. [Install uv](https://docs.astral.sh/uv/getting-started/installation/).
We use this to manage our Python dependencies.
3. Run the development server.
```bash
make serve
```
4. Make your changes.
Documentation changes should adhere to the guidance laid out below.
### Document by purpose
Documentation is organized in one of the following categories.
- **Tutorials**: These hold step-by-step instructions for an end-to-end project
that a beginner could follow along to.
Don't spend time explaining things.
If explanations are available elsewhere, link to them.
These are entry points to answer the prompt,
"I don't know what Fx is, show me what it can do,"
so there won't be too many of these.
- **Explanations**: These hold long-form explanations of concepts and ideas.
These are intended to build an understanding of Fx.
Feel free to go wild here--use learning aids like diagrams, tables, etc.
- **How-tos**: These are step-by-step instructions for a *specific problem*.
Unlike tutorials, these are not meant to be end-to-end.
Feel free to leave things out, make assumptions,
or provide options ("if you're doing this, do this").
As with tutorials, don't spend time explaining;
link to explanations elsewhere.
As an example,
- A tutorial will use lifecycle hooks as part of
a larger set of instructions for a full end-to-end application.
- An explanation will explain what lifecycle hooks are, how they work,
when and how you should use them, and link to relevant APIs and guides.
- A how-to guide will demonstrate how to use lifecycle hooks
with an HTTP server, a gRPC server, etc.
Explanations and how-to guides are often on the same page,
but they should be in distinct sections.
This separation is inspired by the
[Divio documentation system](https://documentation.divio.com/),
### Formatting
#### ATX-style headers
Use ATX-style headers (`#`-prefixed),
not Setext-style (underlined with `===` or `---`).
```markdown
Bad header
==========
## Good header
```
#### Semantic Line Breaks
- **Do not** write overly long lines of text
- **Do not** "reflow" Markdown paragraphs
- **Do** use [Semantic Line Breaks](https://sembr.org/) to break these lines down
```markdown
This is a bad paragraph because it's really long, all on one line. When I open this in a text editor, I'll have to scroll right.
This is a bad paragraph because even though it's not all one one line, it adds
line breaks when it reaches the line length limit. This means that anytime I
change anything in this paragraph, I have to "reflow" it, which will change
other lines and make the change I'm making more difficult to review.
This is a good paragraph. It uses semantic line breaks.
I can add words or modify an existing sentence,
or even parts of a sentence,
easily and without affecting other lines.
When I change something, the actual change I made is easy to review.
Markdown will reflow this into a "normal" pargraph when rendering.
```
### Test everything
All code samples in documentation must be buildable and testable.
To make this possible, we put code samples in the "ex/" directory,
and use the [PyMdown Snippets extension](https://facelessuser.github.io/pymdown-extensions/extensions/snippets/)
to include them in the documentation.
To include code snippets in your documentation,
take the following steps:
1. Add source code under the `ex/` directory.
Usually, the file will be placed in a directory
with a name matching the documentation file
that will include the snippet.
For example,
for src/annotation.md, examples will reside in ex/annotation/.
2. Inside the source file, name regions of code with comments in the forms:
```
// --8<-- [start:name]
...
// --8<-- [end:name]
```
Where `name` is the name of the snippet.
For example:
```go
// --8<-- [start:New]
func New() *http.Server {
// ...
}
// --8<-- [end:New]
```
3. Include the snippet in a code block with the following syntax:
```markdown
```go
;--8<-- "path/to/file.go:name"
```
Where `path/to/file.go` is the path to the file containing the snippet
relative to the `ex/` directory,
and `name` is the name of the snippet.
You can include multiple snippets from the same file like so:
```
;--8<-- "path/to/file.go:snippet1"
;--8<-- "path/to/file.go:snippet2"
```
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2016-2018 Uber Technologies, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+65
View File
@@ -0,0 +1,65 @@
# Directory containing the Makefile.
PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
export GOBIN ?= $(PROJECT_ROOT)/bin
export PATH := $(GOBIN):$(PATH)
FXLINT = $(GOBIN)/fxlint
MODULES = . ./tools ./docs ./internal/e2e
# 'make cover' should not run on docs by default.
# We run that separately explicitly on a specific platform.
COVER_MODULES ?= $(filter-out ./docs,$(MODULES))
.PHONY: all
all: build lint test
.PHONY: build
build:
go build ./...
.PHONY: lint
lint: golangci-lint tidy-lint fx-lint
.PHONY: test
test:
@$(foreach dir,$(MODULES),(cd $(dir) && go test -race ./...) &&) true
.PHONY: cover
cover:
@$(foreach dir,$(COVER_MODULES), \
(cd $(dir) && \
echo "[cover] $(dir)" && \
go test -race -coverprofile=cover.out -coverpkg=./... ./... && \
go tool cover -html=cover.out -o cover.html) &&) true
.PHONY: tidy
tidy:
@$(foreach dir,$(MODULES),(cd $(dir) && go mod tidy) &&) true
.PHONY: docs
docs:
cd docs && make build
.PHONY: golangci-lint
golangci-lint:
@$(foreach mod,$(MODULES), \
(cd $(mod) && \
echo "[lint] golangci-lint: $(mod)" && \
golangci-lint run --path-prefix $(mod)) &&) true
.PHONY: tidy-lint
tidy-lint:
@$(foreach mod,$(MODULES), \
(cd $(mod) && \
echo "[lint] tidy: $(mod)" && \
go mod tidy && \
git diff --exit-code -- go.mod go.sum) &&) true
.PHONY: fx-lint
fx-lint: $(FXLINT)
@$(FXLINT) ./...
$(FXLINT): tools/cmd/fxlint/main.go
cd tools && go install go.uber.org/fx/tools/cmd/fxlint
+40
View File
@@ -0,0 +1,40 @@
# :unicorn: Fx [![GoDoc](https://pkg.go.dev/badge/go.uber.org/fx)](https://pkg.go.dev/go.uber.org/fx) [![Github release](https://img.shields.io/github/release/uber-go/fx.svg)](https://github.com/uber-go/fx/releases) [![Build Status](https://github.com/uber-go/fx/actions/workflows/go.yml/badge.svg)](https://github.com/uber-go/fx/actions/workflows/go.yml) [![Coverage Status](https://codecov.io/gh/uber-go/fx/branch/master/graph/badge.svg)](https://codecov.io/gh/uber-go/fx/branch/master) [![Go Report Card](https://goreportcard.com/badge/go.uber.org/fx)](https://goreportcard.com/report/go.uber.org/fx)
Fx is a dependency injection system for Go.
**Benefits**
- Eliminate globals: Fx helps you remove global state from your application.
No more `init()` or global variables. Use Fx-managed singletons.
- Code reuse: Fx lets teams within your organization build loosely-coupled
and well-integrated shareable components.
- Battle tested: Fx is the backbone of nearly all Go services at Uber.
See our [docs](https://uber-go.github.io/fx/) to get started and/or
learn more about Fx.
## Installation
Use Go modules to install Fx in your application.
```shell
go get go.uber.org/fx@v1
```
## Getting started
To get started with Fx, [start here](https://uber-go.github.io/fx/get-started/).
## Stability
This library is `v1` and follows [SemVer](https://semver.org/) strictly.
No breaking changes will be made to exported APIs before `v2.0.0`.
This project follows the [Go Release Policy](https://golang.org/doc/devel/release.html#policy). Each major
version of Go is supported until there are two newer major releases.
## Stargazers over time
[![Stargazers over time](https://starchart.cc/uber-go/fx.svg)](https://starchart.cc/uber-go/fx)
+1825
View File
File diff suppressed because it is too large Load Diff
+832
View File
@@ -0,0 +1,832 @@
// Copyright (c) 2020-2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"reflect"
"strings"
"time"
"go.uber.org/dig"
"go.uber.org/fx/fxevent"
"go.uber.org/fx/internal/fxclock"
"go.uber.org/fx/internal/fxlog"
"go.uber.org/fx/internal/fxreflect"
"go.uber.org/fx/internal/lifecycle"
"go.uber.org/multierr"
)
// DefaultTimeout is the default timeout for starting or stopping an
// application. It can be configured with the [StartTimeout] and [StopTimeout]
// options.
const DefaultTimeout = 15 * time.Second
// An Option specifies the behavior of the application.
// This is the primary means by which you interface with Fx.
//
// Zero or more options are specified at startup with [New].
// Options cannot be changed once an application has been initialized.
// Options may be grouped into a single option using the [Options] function.
// A group of options providing a logical unit of functionality
// may use [Module] to name that functionality
// and scope certain operations to within that module.
type Option interface {
fmt.Stringer
apply(*module)
}
// Error registers any number of errors with the application to short-circuit
// startup. If more than one error is given, the errors are combined into a
// single error.
//
// Similar to invocations, errors are applied in order. All Provide and Invoke
// options registered before or after an Error option will not be applied.
func Error(errs ...error) Option {
return errorOption(errs)
}
type errorOption []error
func (errs errorOption) apply(mod *module) {
mod.app.err = multierr.Append(mod.app.err, multierr.Combine(errs...))
}
func (errs errorOption) String() string {
return fmt.Sprintf("fx.Error(%v)", multierr.Combine(errs...))
}
// Options bundles a group of options together into a single option.
//
// Use Options to group together options that don't belong in a [Module].
//
// var loggingAndMetrics = fx.Options(
// logging.Module,
// metrics.Module,
// fx.Invoke(func(logger *log.Logger) {
// app.globalLogger = logger
// }),
// )
func Options(opts ...Option) Option {
return optionGroup(opts)
}
type optionGroup []Option
func (og optionGroup) apply(mod *module) {
for _, opt := range og {
opt.apply(mod)
}
}
func (og optionGroup) String() string {
items := make([]string, len(og))
for i, opt := range og {
items[i] = fmt.Sprint(opt)
}
return fmt.Sprintf("fx.Options(%s)", strings.Join(items, ", "))
}
// StartTimeout changes the application's start timeout.
// This controls the total time that all [OnStart] hooks have to complete.
// If the timeout is exceeded, the application will fail to start.
//
// Defaults to [DefaultTimeout].
func StartTimeout(v time.Duration) Option {
return startTimeoutOption(v)
}
type startTimeoutOption time.Duration
func (t startTimeoutOption) apply(m *module) {
if m.parent != nil {
m.app.err = fmt.Errorf("fx.StartTimeout Option should be passed to top-level App, " +
"not to fx.Module")
} else {
m.app.startTimeout = time.Duration(t)
}
}
func (t startTimeoutOption) String() string {
return fmt.Sprintf("fx.StartTimeout(%v)", time.Duration(t))
}
// StopTimeout changes the application's stop timeout.
// This controls the total time that all [OnStop] hooks have to complete.
// If the timeout is exceeded, the application will exit early.
//
// Defaults to [DefaultTimeout].
func StopTimeout(v time.Duration) Option {
return stopTimeoutOption(v)
}
type stopTimeoutOption time.Duration
func (t stopTimeoutOption) apply(m *module) {
if m.parent != nil {
m.app.err = fmt.Errorf("fx.StopTimeout Option should be passed to top-level App, " +
"not to fx.Module")
} else {
m.app.stopTimeout = time.Duration(t)
}
}
func (t stopTimeoutOption) String() string {
return fmt.Sprintf("fx.StopTimeout(%v)", time.Duration(t))
}
// RecoverFromPanics causes panics that occur in functions given to [Provide],
// [Decorate], and [Invoke] to be recovered from.
// This error can be retrieved as any other error, by using (*App).Err().
func RecoverFromPanics() Option {
return recoverFromPanicsOption{}
}
type recoverFromPanicsOption struct{}
func (o recoverFromPanicsOption) apply(m *module) {
if m.parent != nil {
m.app.err = fmt.Errorf("fx.RecoverFromPanics Option should be passed to top-level " +
"App, not to fx.Module")
} else {
m.app.recoverFromPanics = true
}
}
func (o recoverFromPanicsOption) String() string {
return "fx.RecoverFromPanics()"
}
// WithLogger specifies the [fxevent.Logger] used by Fx to log its own events
// (e.g. a constructor was provided, a function was invoked, etc.).
//
// The argument to this is a constructor with one of the following return
// types:
//
// fxevent.Logger
// (fxevent.Logger, error)
//
// The constructor may depend on any other types provided to the application.
// For example,
//
// WithLogger(func(logger *zap.Logger) fxevent.Logger {
// return &fxevent.ZapLogger{Logger: logger}
// })
//
// If specified, Fx will construct the logger and log all its events to the
// specified logger.
//
// If Fx fails to build the logger, or no logger is specified, it will fall back to
// [fxevent.ConsoleLogger] configured to write to stderr.
func WithLogger(constructor interface{}) Option {
return withLoggerOption{
constructor: constructor,
Stack: fxreflect.CallerStack(1, 0),
}
}
type withLoggerOption struct {
constructor interface{}
Stack fxreflect.Stack
}
func (l withLoggerOption) apply(m *module) {
m.logConstructor = &provide{
Target: l.constructor,
Stack: l.Stack,
}
}
func (l withLoggerOption) String() string {
return fmt.Sprintf("fx.WithLogger(%s)", fxreflect.FuncName(l.constructor))
}
// Printer is the interface required by Fx's logging backend. It's implemented
// by most loggers, including the one bundled with the standard library.
//
// Note, this will be deprecated in a future release.
// Prefer to use [fxevent.Logger] instead.
type Printer interface {
Printf(string, ...interface{})
}
// Logger redirects the application's log output to the provided printer.
//
// Prefer to use [WithLogger] instead.
func Logger(p Printer) Option {
return loggerOption{p}
}
type loggerOption struct{ p Printer }
func (l loggerOption) apply(m *module) {
if m.parent != nil {
m.app.err = fmt.Errorf("fx.Logger Option should be passed to top-level App, " +
"not to fx.Module")
} else {
np := writerFromPrinter(l.p)
m.log = fxlog.DefaultLogger(np) // assuming np is thread-safe.
}
}
func (l loggerOption) String() string {
return fmt.Sprintf("fx.Logger(%v)", l.p)
}
// NopLogger disables the application's log output.
//
// Note that this makes some failures difficult to debug,
// since no errors are printed to console.
// Prefer to log to an in-memory buffer instead.
var NopLogger = WithLogger(func() fxevent.Logger { return fxevent.NopLogger })
// An App is a modular application built around dependency injection. Most
// users will only need to use the New constructor and the all-in-one Run
// convenience method. In more unusual cases, users may need to use the Err,
// Start, Done, and Stop methods by hand instead of relying on Run.
//
// [New] creates and initializes an App. All applications begin with a
// constructor for the Lifecycle type already registered.
//
// In addition to that built-in functionality, users typically pass a handful
// of [Provide] options and one or more [Invoke] options. The Provide options
// teach the application how to instantiate a variety of types, and the Invoke
// options describe how to initialize the application.
//
// When created, the application immediately executes all the functions passed
// via Invoke options. To supply these functions with the parameters they
// need, the application looks for constructors that return the appropriate
// types; if constructors for any required types are missing or any
// invocations return an error, the application will fail to start (and Err
// will return a descriptive error message).
//
// Once all the invocations (and any required constructors) have been called,
// New returns and the application is ready to be started using Run or Start.
// On startup, it executes any OnStart hooks registered with its Lifecycle.
// OnStart hooks are executed one at a time, in order, and must all complete
// within a configurable deadline (by default, 15 seconds). For details on the
// order in which OnStart hooks are executed, see the documentation for the
// Start method.
//
// At this point, the application has successfully started up. If started via
// Run, it will continue operating until it receives a shutdown signal from
// Done (see the [App.Done] documentation for details); if started explicitly via
// Start, it will operate until the user calls Stop. On shutdown, OnStop hooks
// execute one at a time, in reverse order, and must all complete within a
// configurable deadline (again, 15 seconds by default).
type App struct {
err error
clock fxclock.Clock
lifecycle *lifecycleWrapper
container *dig.Container
root *module
modules []*module
// Timeouts used
startTimeout time.Duration
stopTimeout time.Duration
// Decides how we react to errors when building the graph.
errorHooks []ErrorHandler
validate bool
// Whether to recover from panics in Dig container
recoverFromPanics bool
// Used to signal shutdowns.
receivers signalReceivers
osExit func(code int) // os.Exit override; used for testing only
}
// provide is a single constructor provided to Fx.
type provide struct {
// Constructor provided to Fx. This may be an fx.Annotated.
Target interface{}
// Stack trace of where this provide was made.
Stack fxreflect.Stack
// IsSupply is true when the Target constructor was emitted by fx.Supply.
IsSupply bool
SupplyType reflect.Type // set only if IsSupply
// Set if the type should be provided at private scope.
Private bool
}
// invoke is a single invocation request to Fx.
type invoke struct {
// Function to invoke.
Target interface{}
// Stack trace of where this invoke was made.
Stack fxreflect.Stack
}
// ErrorHandler handles Fx application startup errors.
// Register these with [ErrorHook].
// If specified, and the application fails to start up,
// the failure will still cause a crash,
// but you'll have a chance to log the error or take some other action.
type ErrorHandler interface {
HandleError(error)
}
// ErrorHook registers error handlers that implement error handling functions.
// They are executed on invoke failures. Passing multiple ErrorHandlers appends
// the new handlers to the application's existing list.
func ErrorHook(funcs ...ErrorHandler) Option {
return errorHookOption(funcs)
}
type errorHookOption []ErrorHandler
func (eho errorHookOption) apply(m *module) {
m.app.errorHooks = append(m.app.errorHooks, eho...)
}
func (eho errorHookOption) String() string {
items := make([]string, len(eho))
for i, eh := range eho {
items[i] = fmt.Sprint(eh)
}
return fmt.Sprintf("fx.ErrorHook(%v)", strings.Join(items, ", "))
}
type errorHandlerList []ErrorHandler
func (ehl errorHandlerList) HandleError(err error) {
for _, eh := range ehl {
eh.HandleError(err)
}
}
// validate sets *App into validation mode without running invoked functions.
func validate(validate bool) Option {
return &validateOption{
validate: validate,
}
}
type validateOption struct {
validate bool
}
func (o validateOption) apply(m *module) {
if m.parent != nil {
m.app.err = fmt.Errorf("fx.validate Option should be passed to top-level App, " +
"not to fx.Module")
} else {
m.app.validate = o.validate
}
}
func (o validateOption) String() string {
return fmt.Sprintf("fx.validate(%v)", o.validate)
}
// ValidateApp validates that supplied graph would run and is not missing any dependencies. This
// method does not invoke actual input functions.
func ValidateApp(opts ...Option) error {
opts = append(opts, validate(true))
app := New(opts...)
return app.Err()
}
// New creates and initializes an App, immediately executing any functions
// registered via [Invoke] options. See the documentation of the App struct for
// details on the application's initialization, startup, and shutdown logic.
func New(opts ...Option) *App {
logger := fxlog.DefaultLogger(os.Stderr)
app := &App{
clock: fxclock.System,
startTimeout: DefaultTimeout,
stopTimeout: DefaultTimeout,
receivers: newSignalReceivers(),
}
app.root = &module{
app: app,
// We start with a logger that writes to stderr. One of the
// following three things can change this:
//
// - fx.Logger was provided to change the output stream
// - fx.WithLogger was provided to change the logger
// implementation
// - Both, fx.Logger and fx.WithLogger were provided
//
// The first two cases are straightforward: we use what the
// user gave us. For the last case, however, we need to fall
// back to what was provided to fx.Logger if fx.WithLogger
// fails.
log: logger,
trace: []string{fxreflect.CallerStack(1, 2)[0].String()},
}
app.modules = append(app.modules, app.root)
for _, opt := range opts {
opt.apply(app.root)
}
// There are a few levels of wrapping on the lifecycle here. To quickly
// cover them:
//
// - lifecycleWrapper ensures that we don't unintentionally expose the
// Start and Stop methods of the internal lifecycle.Lifecycle type
// - lifecycleWrapper also adapts the internal lifecycle.Hook type into
// the public fx.Hook type.
// - appLogger ensures that the lifecycle always logs events to the
// "current" logger associated with the fx.App.
app.lifecycle = &lifecycleWrapper{
lifecycle.New(appLogger{app}, app.clock),
}
containerOptions := []dig.Option{
dig.DeferAcyclicVerification(),
dig.DryRun(app.validate),
}
if app.recoverFromPanics {
containerOptions = append(containerOptions, dig.RecoverFromPanics())
}
app.container = dig.New(containerOptions...)
for _, m := range app.modules {
m.build(app, app.container)
}
// Provide Fx types first to increase the chance a custom logger
// can be successfully built in the face of unrelated DI failure.
// E.g., for a custom logger that relies on the Lifecycle type.
frames := fxreflect.CallerStack(0, 0) // include New in the stack for default Provides
app.root.provide(provide{
Target: func() Lifecycle { return app.lifecycle },
Stack: frames,
})
app.root.provide(provide{Target: app.shutdowner, Stack: frames})
app.root.provide(provide{Target: app.dotGraph, Stack: frames})
for _, m := range app.modules {
m.provideAll()
}
// Run decorators before executing any Invokes -- including the one
// inside constructCustomLogger.
app.err = multierr.Append(app.err, app.root.decorateAll())
// If you are thinking about returning here after provides: do not (just yet)!
// If a custom logger was being used, we're still buffering messages.
// We'll want to flush them to the logger.
// custom app logger will be initialized by the root module.
for _, m := range app.modules {
m.constructAllCustomLoggers()
}
// This error might have come from the provide loop above. We've
// already flushed to the custom logger, so we can return.
if app.err != nil {
return app
}
if err := app.root.executeInvokes(); err != nil {
app.err = err
if dig.CanVisualizeError(err) {
var b bytes.Buffer
dig.Visualize(app.container, &b, dig.VisualizeError(err))
err = errorWithGraph{
graph: b.String(),
err: err,
}
}
errorHandlerList(app.errorHooks).HandleError(err)
}
return app
}
func (app *App) log() fxevent.Logger {
return app.root.log
}
// DotGraph contains a DOT language visualization of the dependency graph in
// an Fx application. It is provided in the container by default at
// initialization. On failure to build the dependency graph, it is attached
// to the error and if possible, colorized to highlight the root cause of the
// failure.
//
// Note that DotGraph does not yet recognize [Decorate] and [Replace].
type DotGraph string
type errWithGraph interface {
Graph() DotGraph
}
type errorWithGraph struct {
graph string
err error
}
func (err errorWithGraph) Graph() DotGraph {
return DotGraph(err.graph)
}
func (err errorWithGraph) Error() string {
return err.err.Error()
}
// VisualizeError returns the visualization of the error if available.
//
// Note that VisualizeError does not yet recognize [Decorate] and [Replace].
func VisualizeError(err error) (string, error) {
var erg errWithGraph
if errors.As(err, &erg) {
if g := erg.Graph(); g != "" {
return string(g), nil
}
}
return "", errors.New("unable to visualize error")
}
// Exits the application with the given exit code.
func (app *App) exit(code int) {
osExit := os.Exit
if app.osExit != nil {
osExit = app.osExit
}
osExit(code)
}
// Run starts the application, blocks on the signals channel, and then
// gracefully shuts the application down. It uses [DefaultTimeout] to set a
// deadline for application startup and shutdown, unless the user has
// configured different timeouts with the [StartTimeout] or [StopTimeout] options.
// It's designed to make typical applications simple to run.
// The minimal Fx application looks like this:
//
// fx.New().Run()
//
// All of Run's functionality is implemented in terms of the exported
// Start, Done, and Stop methods. Applications with more specialized needs
// can use those methods directly instead of relying on Run.
func (app *App) Run() {
// Historically, we do not os.Exit(0) even though most applications
// cede control to Fx with they call app.Run. To avoid a breaking
// change, never os.Exit for success.
if code := app.run(app.Wait); code != 0 {
app.exit(code)
}
}
func (app *App) run(done func() <-chan ShutdownSignal) (exitCode int) {
startCtx, cancel := app.clock.WithTimeout(context.Background(), app.StartTimeout())
defer cancel()
if err := app.Start(startCtx); err != nil {
return 1
}
sig := <-done()
app.log().LogEvent(&fxevent.Stopping{Signal: sig.Signal})
exitCode = sig.ExitCode
stopCtx, cancel := app.clock.WithTimeout(context.Background(), app.StopTimeout())
defer cancel()
if err := app.Stop(stopCtx); err != nil {
return 1
}
return exitCode
}
// Err returns any error encountered during New's initialization. See the
// documentation of the New method for details, but typical errors include
// missing constructors, circular dependencies, constructor errors, and
// invocation errors.
//
// Most users won't need to use this method, since both Run and Start
// short-circuit if initialization failed.
func (app *App) Err() error {
return app.err
}
var (
_onStartHook = "OnStart"
_onStopHook = "OnStop"
)
// Start kicks off all long-running goroutines, like network servers or
// message queue consumers. It does this by interacting with the application's
// Lifecycle.
//
// By taking a dependency on the Lifecycle type, some of the user-supplied
// functions called during initialization may have registered start and stop
// hooks. Because initialization calls constructors serially and in dependency
// order, hooks are naturally registered in serial and dependency order too.
//
// Start executes all OnStart hooks registered with the application's
// Lifecycle, one at a time and in order. This ensures that each constructor's
// start hooks aren't executed until all its dependencies' start hooks
// complete. If any of the start hooks return an error, Start short-circuits,
// calls Stop, and returns the inciting error.
//
// Note that Start short-circuits immediately if the New constructor
// encountered any errors in application initialization.
func (app *App) Start(ctx context.Context) (err error) {
defer func() {
app.log().LogEvent(&fxevent.Started{Err: err})
}()
if app.err != nil {
// Some provides failed, short-circuit immediately.
return app.err
}
return withTimeout(ctx, &withTimeoutParams{
hook: _onStartHook,
callback: app.start,
lifecycle: app.lifecycle,
log: app.log(),
})
}
// withRollback will execute an anonymous function with a given context.
// if the anon func returns an error, rollback methods will be called and related events emitted
func (app *App) withRollback(
ctx context.Context,
f func(context.Context) error,
) error {
if err := f(ctx); err != nil {
app.log().LogEvent(&fxevent.RollingBack{StartErr: err})
stopErr := app.lifecycle.Stop(ctx)
app.log().LogEvent(&fxevent.RolledBack{Err: stopErr})
if stopErr != nil {
return multierr.Append(err, stopErr)
}
return err
}
return nil
}
func (app *App) start(ctx context.Context) error {
return app.withRollback(ctx, func(ctx context.Context) error {
if err := app.lifecycle.Start(ctx); err != nil {
return err
}
return nil
})
}
// Stop gracefully stops the application. It executes any registered OnStop
// hooks in reverse order, so that each constructor's stop hooks are called
// before its dependencies' stop hooks.
//
// If the application didn't start cleanly, only hooks whose OnStart phase was
// called are executed. However, all those hooks are executed, even if some
// fail.
func (app *App) Stop(ctx context.Context) (err error) {
defer func() {
app.log().LogEvent(&fxevent.Stopped{Err: err})
}()
cb := func(ctx context.Context) error {
defer app.receivers.Stop(ctx)
return app.lifecycle.Stop(ctx)
}
return withTimeout(ctx, &withTimeoutParams{
hook: _onStopHook,
callback: cb,
lifecycle: app.lifecycle,
log: app.log(),
})
}
// Done returns a channel of signals to block on after starting the
// application. Applications listen for the SIGINT and SIGTERM signals; during
// development, users can send the application SIGTERM by pressing Ctrl-C in
// the same terminal as the running process.
//
// Alternatively, a signal can be broadcast to all done channels manually by
// using the Shutdown functionality (see the [Shutdowner] documentation for details).
func (app *App) Done() <-chan os.Signal {
app.receivers.Start() // No-op if running
return app.receivers.Done()
}
// Wait returns a channel of [ShutdownSignal] to block on after starting the
// application and function, similar to [App.Done], but with a minor difference:
// if the app was shut down via [Shutdowner.Shutdown],
// the exit code (if provied via [ExitCode]) will be available
// in the [ShutdownSignal] struct.
// Otherwise, the signal that was received will be set.
func (app *App) Wait() <-chan ShutdownSignal {
app.receivers.Start() // No-op if running
return app.receivers.Wait()
}
// StartTimeout returns the configured startup timeout.
// This defaults to [DefaultTimeout], and can be changed with the
// [StartTimeout] option.
func (app *App) StartTimeout() time.Duration {
return app.startTimeout
}
// StopTimeout returns the configured shutdown timeout.
// This defaults to [DefaultTimeout], and can be changed with the
// [StopTimeout] option.
func (app *App) StopTimeout() time.Duration {
return app.stopTimeout
}
func (app *App) dotGraph() (DotGraph, error) {
var b bytes.Buffer
err := dig.Visualize(app.container, &b)
return DotGraph(b.String()), err
}
type withTimeoutParams struct {
log fxevent.Logger
hook string
callback func(context.Context) error
lifecycle *lifecycleWrapper
}
// errHookCallbackExited is returned when a hook callback does not finish executing
var errHookCallbackExited = errors.New("goroutine exited without returning")
func withTimeout(ctx context.Context, param *withTimeoutParams) error {
c := make(chan error, 1)
go func() {
// If runtime.Goexit() is called from within the callback
// then nothing is written to the chan.
// However the defer will still be called, so we can write to the chan,
// to avoid hanging until the timeout is reached.
callbackExited := false
defer func() {
if !callbackExited {
c <- errHookCallbackExited
}
}()
c <- param.callback(ctx)
callbackExited = true
}()
var err error
select {
case <-ctx.Done():
err = ctx.Err()
case err = <-c:
// If the context finished at the same time as the callback
// prefer the context error.
// This eliminates non-determinism in select-case selection.
if ctx.Err() != nil {
err = ctx.Err()
}
}
return err
}
// appLogger logs events to the given Fx app's "current" logger.
//
// Use this with lifecycle, for example, to ensure that events always go to the
// correct logger.
type appLogger struct{ app *App }
func (l appLogger) LogEvent(ev fxevent.Event) {
l.app.log().LogEvent(ev)
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) 2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package fx
import "golang.org/x/sys/unix"
const (
_sigINT = unix.SIGINT
_sigTERM = unix.SIGTERM
)
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) 2023 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//go:build (js && wasm) || (wasip1 && wasm)
// +build js,wasm wasip1,wasm
package fx
import "syscall"
const (
_sigINT = syscall.SIGINT
_sigTERM = syscall.SIGTERM
)
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) 2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//go:build windows
// +build windows
package fx
import "golang.org/x/sys/windows"
const (
_sigINT = windows.SIGINT
_sigTERM = windows.SIGTERM
)
+179
View File
@@ -0,0 +1,179 @@
// Copyright (c) 2024 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"fmt"
"os"
"sync"
)
// broadcaster broadcasts signals to registered signal listeners.
// All methods on the broadcaster are concurrency-safe.
type broadcaster struct {
// This lock is used to protect all fields of broadcaster.
// Methods on broadcaster should protect all concurrent access
// by taking this lock when accessing its fields.
// Conversely, this lock should NOT be taken outside of broadcaster.
m sync.Mutex
// last will contain a pointer to the last ShutdownSignal received, or
// nil if none, if a new channel is created by Wait or Done, this last
// signal will be immediately written to, this allows Wait or Done state
// to be read after application stop
last *ShutdownSignal
// contains channels created by Done
done []chan os.Signal
// contains channels created by Wait
wait []chan ShutdownSignal
}
func (b *broadcaster) reset() {
b.m.Lock()
defer b.m.Unlock()
b.last = nil
}
// Done creates a new channel that will receive signals being broadcast
// via the broadcaster.
//
// If a signal has been received prior to the call of Done,
// the signal will be sent to the new channel.
func (b *broadcaster) Done() <-chan os.Signal {
b.m.Lock()
defer b.m.Unlock()
ch := make(chan os.Signal, 1)
// If we had received a signal prior to the call of done, send it's
// os.Signal to the new channel.
// However we still want to have the operating system notify signals to this
// channel should the application receive another.
if b.last != nil {
ch <- b.last.Signal
}
b.done = append(b.done, ch)
return ch
}
// Wait creates a new channel that will receive signals being broadcast
// via the broadcaster.
//
// If a signal has been received prior to the call of Wait,
// the signal will be sent to the new channel.
func (b *broadcaster) Wait() <-chan ShutdownSignal {
b.m.Lock()
defer b.m.Unlock()
ch := make(chan ShutdownSignal, 1)
if b.last != nil {
ch <- *b.last
}
b.wait = append(b.wait, ch)
return ch
}
// Broadcast sends the given signal to all channels that have been created
// via Done or Wait. It does not block on sending, and returns an unsentSignalError
// if any send did not go through.
func (b *broadcaster) Broadcast(signal ShutdownSignal) error {
b.m.Lock()
defer b.m.Unlock()
b.last = &signal
channels, unsent := b.broadcast(
signal,
b.broadcastDone,
b.broadcastWait,
)
if unsent != 0 {
return &unsentSignalError{
Signal: signal,
Total: channels,
Unsent: unsent,
}
}
return nil
}
func (b *broadcaster) broadcast(
signal ShutdownSignal,
anchors ...func(ShutdownSignal) (int, int),
) (int, int) {
var channels, unsent int
for _, anchor := range anchors {
c, u := anchor(signal)
channels += c
unsent += u
}
return channels, unsent
}
func (b *broadcaster) broadcastDone(signal ShutdownSignal) (int, int) {
var unsent int
for _, reader := range b.done {
select {
case reader <- signal.Signal:
default:
unsent++
}
}
return len(b.done), unsent
}
func (b *broadcaster) broadcastWait(signal ShutdownSignal) (int, int) {
var unsent int
for _, reader := range b.wait {
select {
case reader <- signal:
default:
unsent++
}
}
return len(b.wait), unsent
}
type unsentSignalError struct {
Signal ShutdownSignal
Unsent int
Total int
}
func (err *unsentSignalError) Error() string {
return fmt.Sprintf(
"send %v signal: %v/%v channels are blocked",
err.Signal,
err.Unsent,
err.Total,
)
}
+237
View File
@@ -0,0 +1,237 @@
// Copyright (c) 2022 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"fmt"
"reflect"
"strings"
"go.uber.org/dig"
"go.uber.org/fx/internal/fxreflect"
)
// Decorate specifies one or more decorator functions to an Fx application.
//
// # Decorator functions
//
// Decorator functions let users augment objects in the graph.
// They can take in zero or more dependencies that must be provided to the
// application with fx.Provide, and produce one or more values that can be used
// by other fx.Provide and fx.Invoke calls.
//
// fx.Decorate(func(log *zap.Logger) *zap.Logger {
// return log.Named("myapp")
// })
// fx.Invoke(func(log *zap.Logger) {
// log.Info("hello")
// // Output:
// // {"level": "info","logger":"myapp","msg":"hello"}
// })
//
// The following decorator accepts multiple dependencies from the graph,
// augments and returns one of them.
//
// fx.Decorate(func(log *zap.Logger, cfg *Config) *zap.Logger {
// return log.Named(cfg.Name)
// })
//
// Similar to fx.Provide, functions passed to fx.Decorate may optionally return
// an error as their last result.
// If a decorator returns a non-nil error, it will halt application startup.
//
// fx.Decorate(func(conn *sql.DB, cfg *Config) (*sql.DB, error) {
// if err := conn.Ping(); err != nil {
// return sql.Open("driver-name", cfg.FallbackDB)
// }
// return conn, nil
// })
//
// Decorators support both, fx.In and fx.Out structs, similar to fx.Provide and
// fx.Invoke.
//
// type Params struct {
// fx.In
//
// Client usersvc.Client `name:"readOnly"`
// }
//
// type Result struct {
// fx.Out
//
// Client usersvc.Client `name:"readOnly"`
// }
//
// fx.Decorate(func(p Params) Result {
// ...
// })
//
// Decorators can be annotated with the fx.Annotate function, but not with the
// fx.Annotated type. Refer to documentation on fx.Annotate() to learn how to
// use it for annotating functions.
//
// fx.Decorate(
// fx.Annotate(
// func(client usersvc.Client) usersvc.Client {
// // ...
// },
// fx.ParamTags(`name:"readOnly"`),
// fx.ResultTags(`name:"readOnly"`),
// ),
// )
//
// Decorators support augmenting, filtering, or replacing value groups.
// To decorate a value group, expect the entire value group slice and produce
// the new slice.
//
// type HandlerParam struct {
// fx.In
//
// Log *zap.Logger
// Handlers []Handler `group:"server"
// }
//
// type HandlerResult struct {
// fx.Out
//
// Handlers []Handler `group:"server"
// }
//
// fx.Decorate(func(p HandlerParam) HandlerResult {
// var r HandlerResult
// for _, handler := range p.Handlers {
// r.Handlers = append(r.Handlers, wrapWithLogger(p.Log, handler))
// }
// return r
// }),
//
// Decorators can not add new values to the graph,
// only modify or replace existing ones.
// Types returned by a decorator that are not already in the graph
// will be ignored.
//
// # Decorator scope
//
// Modifications made to the Fx graph with fx.Decorate are scoped to the
// deepest fx.Module inside which the decorator was specified.
//
// fx.Module("mymodule",
// fx.Decorate(func(log *zap.Logger) *zap.Logger {
// return log.Named("myapp")
// }),
// fx.Invoke(func(log *zap.Logger) {
// log.Info("decorated logger")
// // Output:
// // {"level": "info","logger":"myapp","msg":"decorated logger"}
// }),
// ),
// fx.Invoke(func(log *zap.Logger) {
// log.Info("plain logger")
// // Output:
// // {"level": "info","msg":"plain logger"}
// }),
//
// Decorations specified in the top-level fx.New call apply across the
// application and chain with module-specific decorators.
//
// fx.New(
// // ...
// fx.Decorate(func(log *zap.Logger) *zap.Logger {
// return log.With(zap.Field("service", "myservice"))
// }),
// // ...
// fx.Invoke(func(log *zap.Logger) {
// log.Info("outer decorator")
// // Output:
// // {"level": "info","service":"myservice","msg":"outer decorator"}
// }),
// // ...
// fx.Module("mymodule",
// fx.Decorate(func(log *zap.Logger) *zap.Logger {
// return log.Named("myapp")
// }),
// fx.Invoke(func(log *zap.Logger) {
// log.Info("inner decorator")
// // Output:
// // {"level": "info","logger":"myapp","service":"myservice","msg":"inner decorator"}
// }),
// ),
// )
func Decorate(decorators ...interface{}) Option {
return decorateOption{
Targets: decorators,
Stack: fxreflect.CallerStack(1, 0),
}
}
type decorateOption struct {
Targets []interface{}
Stack fxreflect.Stack
}
func (o decorateOption) apply(mod *module) {
for _, target := range o.Targets {
mod.decorators = append(mod.decorators, decorator{
Target: target,
Stack: o.Stack,
})
}
}
func (o decorateOption) String() string {
items := make([]string, len(o.Targets))
for i, f := range o.Targets {
items[i] = fxreflect.FuncName(f)
}
return fmt.Sprintf("fx.Decorate(%s)", strings.Join(items, ", "))
}
// decorator is a single decorator used in Fx.
type decorator struct {
// Decorator provided to Fx.
Target interface{}
// Stack trace of where this provide was made.
Stack fxreflect.Stack
// Whether this decorator was specified via fx.Replace
IsReplace bool
ReplaceType reflect.Type // set only if IsReplace
}
func runDecorator(c container, d decorator, opts ...dig.DecorateOption) (err error) {
decorator := d.Target
defer func() {
if err != nil {
err = fmt.Errorf("fx.Decorate(%v) from:\n%+vFailed: %w", decorator, d.Stack, err)
}
}()
switch decorator := decorator.(type) {
case annotated:
if dcor, derr := decorator.Build(); derr == nil {
err = c.Decorate(dcor, opts...)
}
default:
err = c.Decorate(decorator, opts...)
}
return
}
+360
View File
@@ -0,0 +1,360 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Package fx is a framework that makes it easy to build applications out of
// reusable, composable modules.
//
// Fx applications use dependency injection to eliminate globals without the
// tedium of manually wiring together function calls. Unlike other approaches
// to dependency injection, Fx works with plain Go functions: you don't need
// to use struct tags or embed special types, so Fx automatically works well
// with most Go packages.
//
// # Basic usage
//
// Basic usage is explained in the package-level example.
// If you're new to Fx, start there!
//
// Advanced features, including named instances, optional parameters,
// and value groups, are explained in this section further down.
//
// # Testing Fx Applications
//
// To test functions that use the Lifecycle type or to write end-to-end tests
// of your Fx application, use the helper functions and types provided by the
// go.uber.org/fx/fxtest package.
//
// # Parameter Structs
//
// Fx constructors declare their dependencies as function parameters. This can
// quickly become unreadable if the constructor has a lot of dependencies.
//
// func NewHandler(users *UserGateway, comments *CommentGateway, posts *PostGateway, votes *VoteGateway, authz *AuthZGateway) *Handler {
// // ...
// }
//
// To improve the readability of constructors like this, create a struct that
// lists all the dependencies as fields and change the function to accept that
// struct instead. The new struct is called a parameter struct.
//
// Fx has first class support for parameter structs: any struct embedding
// fx.In gets treated as a parameter struct, so the individual fields in the
// struct are supplied via dependency injection. Using a parameter struct, we
// can make the constructor above much more readable:
//
// type HandlerParams struct {
// fx.In
//
// Users *UserGateway
// Comments *CommentGateway
// Posts *PostGateway
// Votes *VoteGateway
// AuthZ *AuthZGateway
// }
//
// func NewHandler(p HandlerParams) *Handler {
// // ...
// }
//
// Though it's rarelly necessary to mix the two, constructors can receive any
// combination of parameter structs and parameters.
//
// func NewHandler(p HandlerParams, l *log.Logger) *Handler {
// // ...
// }
//
// # Result Structs
//
// Result structs are the inverse of parameter structs.
// These structs represent multiple outputs from a
// single function as fields. Fx treats all structs embedding fx.Out as result
// structs, so other constructors can rely on the result struct's fields
// directly.
//
// Without result structs, we sometimes have function definitions like this:
//
// func SetupGateways(conn *sql.DB) (*UserGateway, *CommentGateway, *PostGateway, error) {
// // ...
// }
//
// With result structs, we can make this both more readable and easier to
// modify in the future:
//
// type Gateways struct {
// fx.Out
//
// Users *UserGateway
// Comments *CommentGateway
// Posts *PostGateway
// }
//
// func SetupGateways(conn *sql.DB) (Gateways, error) {
// // ...
// }
//
// # Named Values
//
// Some use cases require the application container to hold multiple values of
// the same type.
//
// A constructor that produces a result struct can tag any field with
// `name:".."` to have the corresponding value added to the graph under the
// specified name. An application may contain at most one unnamed value of a
// given type, but may contain any number of named values of the same type.
//
// type ConnectionResult struct {
// fx.Out
//
// ReadWrite *sql.DB `name:"rw"`
// ReadOnly *sql.DB `name:"ro"`
// }
//
// func ConnectToDatabase(...) (ConnectionResult, error) {
// // ...
// return ConnectionResult{ReadWrite: rw, ReadOnly: ro}, nil
// }
//
// Similarly, a constructor that accepts a parameter struct can tag any field
// with `name:".."` to have the corresponding value injected by name.
//
// type GatewayParams struct {
// fx.In
//
// WriteToConn *sql.DB `name:"rw"`
// ReadFromConn *sql.DB `name:"ro"`
// }
//
// func NewCommentGateway(p GatewayParams) (*CommentGateway, error) {
// // ...
// }
//
// Note that both the name AND type of the fields on the
// parameter struct must match the corresponding result struct.
//
// # Optional Dependencies
//
// Constructors often have optional dependencies on some types: if those types are
// missing, they can operate in a degraded state. Fx supports optional
// dependencies via the `optional:"true"` tag to fields on parameter structs.
//
// type UserGatewayParams struct {
// fx.In
//
// Conn *sql.DB
// Cache *redis.Client `optional:"true"`
// }
//
// If an optional field isn't available in the container, the constructor
// receives the field's zero value.
//
// func NewUserGateway(p UserGatewayParams, log *log.Logger) (*UserGateway, error) {
// if p.Cache == nil {
// log.Print("Caching disabled")
// }
// // ...
// }
//
// Constructors that declare optional dependencies MUST gracefully handle
// situations in which those dependencies are absent.
//
// The optional tag also allows adding new dependencies without breaking
// existing consumers of the constructor.
//
// The optional tag may be combined with the name tag to declare a named
// value dependency optional.
//
// type GatewayParams struct {
// fx.In
//
// WriteToConn *sql.DB `name:"rw"`
// ReadFromConn *sql.DB `name:"ro" optional:"true"`
// }
//
// func NewCommentGateway(p GatewayParams, log *log.Logger) (*CommentGateway, error) {
// if p.ReadFromConn == nil {
// log.Print("Warning: Using RW connection for reads")
// p.ReadFromConn = p.WriteToConn
// }
// // ...
// }
//
// # Value Groups
//
// To make it easier to produce and consume many values of the same type, Fx
// supports named, unordered collections called value groups.
//
// Constructors can send values into value groups by returning a result struct
// tagged with `group:".."`.
//
// type HandlerResult struct {
// fx.Out
//
// Handler Handler `group:"server"`
// }
//
// func NewHelloHandler() HandlerResult {
// // ...
// }
//
// func NewEchoHandler() HandlerResult {
// // ...
// }
//
// Any number of constructors may provide values to this named collection, but
// the ordering of the final collection is unspecified.
//
// Value groups require parameter and result structs to use fields with
// different types: if a group of constructors each returns type T, parameter
// structs consuming the group must use a field of type []T.
//
// Parameter structs can request a value group by using a field of type []T
// tagged with `group:".."`.
// This will execute all constructors that provide a value to
// that group in an unspecified order, then collect all the results into a
// single slice.
//
// type ServerParams struct {
// fx.In
//
// Handlers []Handler `group:"server"`
// }
//
// func NewServer(p ServerParams) *Server {
// server := newServer()
// for _, h := range p.Handlers {
// server.Register(h)
// }
// return server
// }
//
// Note that values in a value group are unordered. Fx makes no guarantees
// about the order in which these values will be produced.
//
// # Soft Value Groups
//
// By default, when a constructor declares a dependency on a value group,
// all values provided to that value group are eagerly instantiated.
// That is undesirable for cases where an optional component wants to
// constribute to a value group, but only if it was actually used
// by the rest of the application.
//
// A soft value group can be thought of as a best-attempt at populating the
// group with values from constructors that have already run. In other words,
// if a constructor's output type is only consumed by a soft value group,
// it will not be run.
//
// Note that Fx randomizes the order of values in the value group,
// so the slice of values may not match the order in which constructors
// were run.
//
// To declare a soft relationship between a group and its constructors, use
// the `soft` option on the input group tag (`group:"[groupname],soft"`).
// This option is only valid for input parameters.
//
// type Params struct {
// fx.In
//
// Handlers []Handler `group:"server,soft"`
// Logger *zap.Logger
// }
//
// func NewServer(p Params) *Server {
// // ...
// }
//
// With such a declaration, a constructor that provides a value to the 'server'
// value group will be called only if there's another instantiated component
// that consumes the results of that constructor.
//
// func NewHandlerAndLogger() (Handler, *zap.Logger) {
// // ...
// }
//
// func NewHandler() Handler {
// // ...
// }
//
// fx.Provide(
// fx.Annotate(NewHandlerAndLogger, fx.ResultTags(`group:"server"`)),
// fx.Annotate(NewHandler, fx.ResultTags(`group:"server"`)),
// )
//
// NewHandlerAndLogger will be called because the Logger is consumed by the
// application, but NewHandler will not be called because it's only consumed
// by the soft value group.
//
// # Value group flattening
//
// By default, values of type T produced to a value group are consumed as []T.
//
// type HandlerResult struct {
// fx.Out
//
// Handler Handler `group:"server"`
// }
//
// type ServerParams struct {
// fx.In
//
// Handlers []Handler `group:"server"`
// }
//
// This means that if the producer produces []T,
// the consumer must consume [][]T.
//
// There are cases where it's desirable
// for the producer (the fx.Out) to produce multiple values ([]T),
// and for the consumer (the fx.In) consume them as a single slice ([]T).
// Fx offers flattened value groups for this purpose.
//
// To provide multiple values for a group from a result struct, produce a
// slice and use the `,flatten` option on the group tag. This indicates that
// each element in the slice should be injected into the group individually.
//
// type HandlerResult struct {
// fx.Out
//
// Handler []Handler `group:"server,flatten"`
// // Consumed as []Handler in ServerParams.
// }
//
// # Unexported fields
//
// By default, a type that embeds fx.In may not have any unexported fields. The
// following will return an error if used with Fx.
//
// type Params struct {
// fx.In
//
// Logger *zap.Logger
// mu sync.Mutex
// }
//
// If you have need of unexported fields on such a type, you may opt-into
// ignoring unexported fields by adding the ignore-unexported struct tag to the
// fx.In. For example,
//
// type Params struct {
// fx.In `ignore-unexported:"true"`
//
// Logger *zap.Logger
// mu sync.Mutex
// }
package fx // import "go.uber.org/fx"
+156
View File
@@ -0,0 +1,156 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"fmt"
"reflect"
"unicode"
"unicode/utf8"
)
var _typeOfIn = reflect.TypeOf(In{})
// Extract fills the given struct with values from the dependency injection
// container on application initialization. The target MUST be a pointer to a
// struct. Only exported fields will be filled.
//
// Deprecated: Use Populate instead.
func Extract(target interface{}) Option {
v := reflect.ValueOf(target)
if t := v.Type(); t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
return Error(fmt.Errorf("Extract expected a pointer to a struct, got a %v", t))
}
v = v.Elem()
t := v.Type()
// We generate a function which accepts a single fx.In struct as an
// argument. This struct contains all exported fields of the target
// struct.
// Fields of the generated fx.In struct.
fields := make([]reflect.StructField, 0, t.NumField()+1)
// Anonymous dig.In field.
fields = append(fields, reflect.StructField{
Name: _typeOfIn.Name(),
Anonymous: true,
Type: _typeOfIn,
})
// List of values in the target struct aligned with the fields of the
// generated struct.
//
// So for example, if the target is,
//
// var target struct {
// Foo io.Reader
// bar []byte
// Baz io.Writer
// }
//
// The generated struct has the shape,
//
// struct {
// fx.In
//
// F0 io.Reader
// F2 io.Writer
// }
//
// And `targets` is,
//
// [
// target.Field(0), // Foo io.Reader
// target.Field(2), // Baz io.Writer
// ]
//
// As we iterate through the fields of the generated struct, we can copy
// the value into the corresponding value in the targets list.
targets := make([]reflect.Value, 0, t.NumField())
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
// Skip unexported fields.
if f.Anonymous {
// If embedded, StructField.PkgPath is not a reliable indicator of
// whether the field is exported. See
// https://github.com/golang/go/issues/21122
t := f.Type
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if !isExported(t.Name()) {
continue
}
} else if f.PkgPath != "" {
continue
}
// We don't copy over names or embedded semantics.
fields = append(fields, reflect.StructField{
Name: fmt.Sprintf("F%d", i),
Type: f.Type,
Tag: f.Tag,
})
targets = append(targets, v.Field(i))
}
// Equivalent to,
//
// func(r struct {
// fx.In
//
// F1 Foo
// F2 Bar
// }) {
// target.Foo = r.F1
// target.Bar = r.F2
// }
fn := reflect.MakeFunc(
reflect.FuncOf(
[]reflect.Type{reflect.StructOf(fields)},
nil, /* results */
false, /* variadic */
),
func(args []reflect.Value) []reflect.Value {
result := args[0]
for i := 1; i < result.NumField(); i++ {
targets[i-1].Set(result.Field(i))
}
return nil
},
)
return Invoke(fn.Interface())
}
// isExported reports whether the identifier is exported.
func isExported(id string) bool {
r, _ := utf8.DecodeRuneInString(id)
return unicode.IsUpper(r)
}
+152
View File
@@ -0,0 +1,152 @@
// Copyright (c) 2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fxevent
import (
"fmt"
"io"
"strings"
)
// ConsoleLogger is an Fx event logger that attempts to write human-readable
// messages to the console.
//
// Use this during development.
type ConsoleLogger struct {
W io.Writer
}
var _ Logger = (*ConsoleLogger)(nil)
func (l *ConsoleLogger) logf(msg string, args ...interface{}) {
fmt.Fprintf(l.W, "[Fx] "+msg+"\n", args...)
}
// LogEvent logs the given event to the provided Zap logger.
func (l *ConsoleLogger) LogEvent(event Event) {
switch e := event.(type) {
case *OnStartExecuting:
l.logf("HOOK OnStart\t\t%s executing (caller: %s)", e.FunctionName, e.CallerName)
case *OnStartExecuted:
if e.Err != nil {
l.logf("HOOK OnStart\t\t%s called by %s failed in %s: %+v", e.FunctionName, e.CallerName, e.Runtime, e.Err)
} else {
l.logf("HOOK OnStart\t\t%s called by %s ran successfully in %s", e.FunctionName, e.CallerName, e.Runtime)
}
case *OnStopExecuting:
l.logf("HOOK OnStop\t\t%s executing (caller: %s)", e.FunctionName, e.CallerName)
case *OnStopExecuted:
if e.Err != nil {
l.logf("HOOK OnStop\t\t%s called by %s failed in %s: %+v", e.FunctionName, e.CallerName, e.Runtime, e.Err)
} else {
l.logf("HOOK OnStop\t\t%s called by %s ran successfully in %s", e.FunctionName, e.CallerName, e.Runtime)
}
case *Supplied:
if e.Err != nil {
l.logf("ERROR\tFailed to supply %v: %+v", e.TypeName, e.Err)
} else if e.ModuleName != "" {
l.logf("SUPPLY\t%v from module %q", e.TypeName, e.ModuleName)
} else {
l.logf("SUPPLY\t%v", e.TypeName)
}
case *Provided:
var privateStr string
if e.Private {
privateStr = " (PRIVATE)"
}
for _, rtype := range e.OutputTypeNames {
if e.ModuleName != "" {
l.logf("PROVIDE%v\t%v <= %v from module %q", privateStr, rtype, e.ConstructorName, e.ModuleName)
} else {
l.logf("PROVIDE%v\t%v <= %v", privateStr, rtype, e.ConstructorName)
}
}
if e.Err != nil {
l.logf("Error after options were applied: %+v", e.Err)
}
case *Replaced:
for _, rtype := range e.OutputTypeNames {
if e.ModuleName != "" {
l.logf("REPLACE\t%v from module %q", rtype, e.ModuleName)
} else {
l.logf("REPLACE\t%v", rtype)
}
}
if e.Err != nil {
l.logf("ERROR\tFailed to replace: %+v", e.Err)
}
case *Decorated:
for _, rtype := range e.OutputTypeNames {
if e.ModuleName != "" {
l.logf("DECORATE\t%v <= %v from module %q", rtype, e.DecoratorName, e.ModuleName)
} else {
l.logf("DECORATE\t%v <= %v", rtype, e.DecoratorName)
}
}
if e.Err != nil {
l.logf("Error after options were applied: %+v", e.Err)
}
case *Run:
var moduleStr string
if e.ModuleName != "" {
moduleStr = fmt.Sprintf(" from module %q", e.ModuleName)
}
l.logf("RUN\t%v: %v in %s%v", e.Kind, e.Name, e.Runtime, moduleStr)
if e.Err != nil {
l.logf("Error returned: %+v", e.Err)
}
case *Invoking:
if e.ModuleName != "" {
l.logf("INVOKE\t\t%s from module %q", e.FunctionName, e.ModuleName)
} else {
l.logf("INVOKE\t\t%s", e.FunctionName)
}
case *Invoked:
if e.Err != nil {
l.logf("ERROR\t\tfx.Invoke(%v) called from:\n%+vFailed: %+v", e.FunctionName, e.Trace, e.Err)
}
case *Stopping:
l.logf("%v", strings.ToUpper(e.Signal.String()))
case *Stopped:
if e.Err != nil {
l.logf("ERROR\t\tFailed to stop cleanly: %+v", e.Err)
}
case *RollingBack:
l.logf("ERROR\t\tStart failed, rolling back: %+v", e.StartErr)
case *RolledBack:
if e.Err != nil {
l.logf("ERROR\t\tCouldn't roll back cleanly: %+v", e.Err)
}
case *Started:
if e.Err != nil {
l.logf("ERROR\t\tFailed to start: %+v", e.Err)
} else {
l.logf("RUNNING")
}
case *LoggerInitialized:
if e.Err != nil {
l.logf("ERROR\t\tFailed to initialize custom logger: %+v", e.Err)
} else {
l.logf("LOGGER\tInitialized custom logger from %v", e.ConstructorName)
}
}
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright (c) 2024 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Package fxevent defines a means of changing how Fx logs internal events.
//
// # Changing the Logger
//
// By default, the [ConsoleLogger] is used, writing readable logs to stderr.
//
// You can use the fx.WithLogger option to change this behavior
// by providing a constructor that returns an alternative implementation
// of the [Logger] interface.
//
// fx.WithLogger(func(cfg *Config) Logger {
// return &MyFxLogger{...}
// })
//
// Because WithLogger accepts a constructor,
// you can pull any other types and values from inside the container,
// allowing use of the same logger that your application uses.
//
// For example, if you're using Zap inside your application,
// you can use the [ZapLogger] implementation of the interface.
//
// fx.New(
// fx.Provide(
// zap.NewProduction, // provide a *zap.Logger
// ),
// fx.WithLogger(
// func(log *zap.Logger) fxevent.Logger {
// return &fxevent.ZapLogger{Logger: log}
// },
// ),
// )
//
// # Implementing a Custom Logger
//
// To implement a custom logger, you need to implement the [Logger] interface.
// The Logger.LogEvent method accepts an [Event] object.
//
// [Event] is a union type that represents all the different events that Fx can emit.
// You can use a type switch to handle each event type.
// See 'event.go' for a list of all the possible events.
//
// func (l *MyFxLogger) LogEvent(e fxevent.Event) {
// switch e := e.(type) {
// case *fxevent.OnStartExecuting:
// // ...
// // ...
// }
// }
//
// The events contain enough information for observability and debugging purposes.
// If you need more information in them,
// feel free to open an issue to discuss the addition.
package fxevent
+285
View File
@@ -0,0 +1,285 @@
// Copyright (c) 2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fxevent
import (
"os"
"time"
)
// Event defines an event emitted by fx.
type Event interface {
event() // Only fxlog can implement this interface.
}
// Passing events by type to make Event hashable in the future.
func (*OnStartExecuting) event() {}
func (*OnStartExecuted) event() {}
func (*OnStopExecuting) event() {}
func (*OnStopExecuted) event() {}
func (*Supplied) event() {}
func (*Provided) event() {}
func (*Replaced) event() {}
func (*Decorated) event() {}
func (*Run) event() {}
func (*Invoking) event() {}
func (*Invoked) event() {}
func (*Stopping) event() {}
func (*Stopped) event() {}
func (*RollingBack) event() {}
func (*RolledBack) event() {}
func (*Started) event() {}
func (*LoggerInitialized) event() {}
// OnStartExecuting is emitted before an OnStart hook is executed.
type OnStartExecuting struct {
// FunctionName is the name of the function that will be executed.
FunctionName string
// CallerName is the name of the function that scheduled the hook for
// execution.
CallerName string
}
// OnStartExecuted is emitted after an OnStart hook has been executed.
type OnStartExecuted struct {
// FunctionName is the name of the function that was executed.
FunctionName string
// CallerName is the name of the function that scheduled the hook for
// execution.
CallerName string
// Method specifies the kind of the hook. This is one of "OnStart" and
// "OnStop".
Method string
// Runtime specifies how long it took to run this hook.
Runtime time.Duration
// Err is non-nil if the hook failed to execute.
Err error
}
// OnStopExecuting is emitted before an OnStop hook is executed.
type OnStopExecuting struct {
// FunctionName is the name of the function that will be executed.
FunctionName string
// CallerName is the name of the function that scheduled the hook for
// execution.
CallerName string
}
// OnStopExecuted is emitted after an OnStop hook has been executed.
type OnStopExecuted struct {
// FunctionName is the name of the function that was executed.
FunctionName string
// CallerName is the name of the function that scheduled the hook for
// execution.
CallerName string
// Runtime specifies how long it took to run this hook.
Runtime time.Duration
// Err is non-nil if the hook failed to execute.
Err error
}
// Supplied is emitted after a value is added with fx.Supply.
type Supplied struct {
// TypeName is the name of the type of value that was added.
TypeName string
// StackTrace is the stack trace of the call to Supply.
StackTrace []string
// ModuleTrace contains the module locations through which this value was added.
ModuleTrace []string
// ModuleName is the name of the module in which the value was added to.
ModuleName string
// Err is non-nil if we failed to supply the value.
Err error
}
// Provided is emitted when a constructor is provided to Fx.
type Provided struct {
// ConstructorName is the name of the constructor that was provided to
// Fx.
ConstructorName string
// StackTrace is the stack trace of where the constructor was provided to Fx.
StackTrace []string
// ModuleTrace contains the module locations through which this was provided to Fx.
ModuleTrace []string
// OutputTypeNames is a list of names of types that are produced by
// this constructor.
OutputTypeNames []string
// ModuleName is the name of the module in which the constructor was
// provided to.
ModuleName string
// Err is non-nil if we failed to provide this constructor.
Err error
// Private denotes whether the provided constructor is a [Private] constructor.
Private bool
}
// Replaced is emitted when a value replaces a type in Fx.
type Replaced struct {
// OutputTypeNames is a list of names of types that were replaced.
OutputTypeNames []string
// StackTrace is the stack trace of the call to Replace.
StackTrace []string
// ModuleTrace contains the module locations through which this value was added.
ModuleTrace []string
// ModuleName is the name of the module in which the value was added to.
ModuleName string
// Err is non-nil if we failed to supply the value.
Err error
}
// Decorated is emitted when a decorator is executed in Fx.
type Decorated struct {
// DecoratorName is the name of the decorator function that was
// provided to Fx.
DecoratorName string
// StackTrace is the stack trace of where the decorator was given to Fx.
StackTrace []string
// ModuleTrace contains the module locations through which this value was added.
ModuleTrace []string
// ModuleName is the name of the module in which the value was added to.
ModuleName string
// OutputTypeNames is a list of names of types that are decorated by
// this decorator.
OutputTypeNames []string
// Err is non-nil if we failed to run this decorator.
Err error
}
// Run is emitted after a constructor, decorator, or supply/replace stub is run by Fx.
type Run struct {
// Name is the name of the function that was run.
Name string
// Kind indicates which Fx option was used to pass along the function.
// It is either "provide", "decorate", "supply", or "replace".
Kind string
// ModuleName is the name of the module in which the function belongs.
ModuleName string
// Runtime specifies how long it took to run this function.
Runtime time.Duration
// Err is non-nil if the function returned an error.
// If fx.RecoverFromPanics is used, this will include panics.
Err error
}
// Invoking is emitted before we invoke a function specified with fx.Invoke.
type Invoking struct {
// FunctionName is the name of the function that will be invoked.
FunctionName string
// ModuleName is the name of the module in which the value was added to.
ModuleName string
}
// Invoked is emitted after we invoke a function specified with fx.Invoke,
// whether it succeeded or failed.
type Invoked struct {
// Functionname is the name of the function that was invoked.
FunctionName string
// ModuleName is the name of the module in which the value was added to.
ModuleName string
// Err is non-nil if the function failed to execute.
Err error
// Trace records information about where the fx.Invoke call was made.
// Note that this is NOT a stack trace of the error itself.
Trace string
}
// Started is emitted when an application is started successfully and/or it
// errored.
type Started struct {
// Err is non-nil if the application failed to start successfully.
Err error
}
// Stopping is emitted when the application receives a signal to shut down
// after starting. This may happen with fx.Shutdowner or by sending a signal to
// the application on the command line.
type Stopping struct {
// Signal is the signal that caused this shutdown.
Signal os.Signal
}
// Stopped is emitted when the application has finished shutting down, whether
// successfully or not.
type Stopped struct {
// Err is non-nil if errors were encountered during shutdown.
Err error
}
// RollingBack is emitted when the application failed to start up due to an
// error, and is being rolled back.
type RollingBack struct {
// StartErr is the error that caused this rollback.
StartErr error
}
// RolledBack is emitted after a service has been rolled back, whether it
// succeeded or not.
type RolledBack struct {
// Err is non-nil if the rollback failed.
Err error
}
// LoggerInitialized is emitted when a logger supplied with fx.WithLogger is
// instantiated, or if it fails to instantiate.
type LoggerInitialized struct {
// ConstructorName is the name of the constructor that builds this
// logger.
ConstructorName string
// Err is non-nil if the logger failed to build.
Err error
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright (c) 2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fxevent
// Logger defines interface used for logging.
type Logger interface {
// LogEvent is called when a logging event is emitted.
LogEvent(Event)
}
// NopLogger is an Fx event logger that ignores all messages.
var NopLogger = nopLogger{}
type nopLogger struct{}
var _ Logger = nopLogger{}
func (nopLogger) LogEvent(Event) {}
func (nopLogger) String() string { return "NopLogger" }
+280
View File
@@ -0,0 +1,280 @@
// Copyright (c) 2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//go:build go1.21
package fxevent
import (
"context"
"log/slog"
"strconv"
"strings"
)
var _ Logger = (*SlogLogger)(nil)
// SlogLogger an Fx event logger that logs events using a slog logger.
type SlogLogger struct {
Logger *slog.Logger
ctx context.Context
logLevel slog.Level
errorLevel *slog.Level
}
// UseContext sets the context that will be used when logging to slog.
func (l *SlogLogger) UseContext(ctx context.Context) {
l.ctx = ctx
}
// UseLogLevel sets the level of non-error logs emitted by Fx to level.
func (l *SlogLogger) UseLogLevel(level slog.Level) {
l.logLevel = level
}
// UseErrorLevel sets the level of error logs emitted by Fx to level.
func (l *SlogLogger) UseErrorLevel(level slog.Level) {
l.errorLevel = &level
}
func (l *SlogLogger) filter(fields []any) []any {
filtered := []any{}
for _, field := range fields {
if field, ok := field.(slog.Attr); ok {
if _, ok := field.Value.Any().(slogFieldSkip); ok {
continue
}
}
filtered = append(filtered, field)
}
return filtered
}
func (l *SlogLogger) logEvent(msg string, fields ...any) {
l.Logger.Log(l.ctx, l.logLevel, msg, l.filter(fields)...)
}
func (l *SlogLogger) logError(msg string, fields ...any) {
lvl := slog.LevelError
if l.errorLevel != nil {
lvl = *l.errorLevel
}
l.Logger.Log(l.ctx, lvl, msg, l.filter(fields)...)
}
// LogEvent logs the given event to the provided Zap logger.
func (l *SlogLogger) LogEvent(event Event) {
switch e := event.(type) {
case *OnStartExecuting:
l.logEvent("OnStart hook executing",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
)
case *OnStartExecuted:
if e.Err != nil {
l.logError("OnStart hook failed",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
slogErr(e.Err),
)
} else {
l.logEvent("OnStart hook executed",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
slog.String("runtime", e.Runtime.String()),
)
}
case *OnStopExecuting:
l.logEvent("OnStop hook executing",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
)
case *OnStopExecuted:
if e.Err != nil {
l.logError("OnStop hook failed",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
slogErr(e.Err),
)
} else {
l.logEvent("OnStop hook executed",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
slog.String("runtime", e.Runtime.String()),
)
}
case *Supplied:
if e.Err != nil {
l.logError("error encountered while applying options",
slog.String("type", e.TypeName),
slogStrings("moduletrace", e.ModuleTrace),
slogStrings("stacktrace", e.StackTrace),
slogMaybeModuleField(e.ModuleName),
slogErr(e.Err))
} else {
l.logEvent("supplied",
slog.String("type", e.TypeName),
slogStrings("stacktrace", e.StackTrace),
slogStrings("moduletrace", e.ModuleTrace),
slogMaybeModuleField(e.ModuleName),
)
}
case *Provided:
for _, rtype := range e.OutputTypeNames {
l.logEvent("provided",
slog.String("constructor", e.ConstructorName),
slogStrings("stacktrace", e.StackTrace),
slogStrings("moduletrace", e.ModuleTrace),
slogMaybeModuleField(e.ModuleName),
slog.String("type", rtype),
slogMaybeBool("private", e.Private),
)
}
if e.Err != nil {
l.logError("error encountered while applying options",
slogMaybeModuleField(e.ModuleName),
slogStrings("stacktrace", e.StackTrace),
slogStrings("moduletrace", e.ModuleTrace),
slogErr(e.Err))
}
case *Replaced:
for _, rtype := range e.OutputTypeNames {
l.logEvent("replaced",
slogStrings("stacktrace", e.StackTrace),
slogStrings("moduletrace", e.ModuleTrace),
slogMaybeModuleField(e.ModuleName),
slog.String("type", rtype),
)
}
if e.Err != nil {
l.logError("error encountered while replacing",
slogStrings("stacktrace", e.StackTrace),
slogStrings("moduletrace", e.ModuleTrace),
slogMaybeModuleField(e.ModuleName),
slogErr(e.Err))
}
case *Decorated:
for _, rtype := range e.OutputTypeNames {
l.logEvent("decorated",
slog.String("decorator", e.DecoratorName),
slogStrings("stacktrace", e.StackTrace),
slogStrings("moduletrace", e.ModuleTrace),
slogMaybeModuleField(e.ModuleName),
slog.String("type", rtype),
)
}
if e.Err != nil {
l.logError("error encountered while applying options",
slogStrings("stacktrace", e.StackTrace),
slogStrings("moduletrace", e.ModuleTrace),
slogMaybeModuleField(e.ModuleName),
slogErr(e.Err))
}
case *Run:
if e.Err != nil {
l.logError("error returned",
slog.String("name", e.Name),
slog.String("kind", e.Kind),
slogMaybeModuleField(e.ModuleName),
slogErr(e.Err),
)
} else {
l.logEvent("run",
slog.String("name", e.Name),
slog.String("kind", e.Kind),
slog.String("runtime", e.Runtime.String()),
slogMaybeModuleField(e.ModuleName),
)
}
case *Invoking:
// Do not log stack as it will make logs hard to read.
l.logEvent("invoking",
slog.String("function", e.FunctionName),
slogMaybeModuleField(e.ModuleName),
)
case *Invoked:
if e.Err != nil {
l.logError("invoke failed",
slogErr(e.Err),
slog.String("stack", e.Trace),
slog.String("function", e.FunctionName),
slogMaybeModuleField(e.ModuleName),
)
}
case *Stopping:
l.logEvent("received signal",
slog.String("signal", strings.ToUpper(e.Signal.String())))
case *Stopped:
if e.Err != nil {
l.logError("stop failed", slogErr(e.Err))
}
case *RollingBack:
l.logError("start failed, rolling back", slogErr(e.StartErr))
case *RolledBack:
if e.Err != nil {
l.logError("rollback failed", slogErr(e.Err))
}
case *Started:
if e.Err != nil {
l.logError("start failed", slogErr(e.Err))
} else {
l.logEvent("started")
}
case *LoggerInitialized:
if e.Err != nil {
l.logError("custom logger initialization failed", slogErr(e.Err))
} else {
l.logEvent("initialized custom fxevent.Logger", slog.String("function", e.ConstructorName))
}
}
}
type slogFieldSkip struct{}
func slogMaybeModuleField(name string) slog.Attr {
if len(name) == 0 {
return slog.Any("module", slogFieldSkip{})
}
return slog.String("module", name)
}
func slogMaybeBool(name string, b bool) slog.Attr {
if !b {
return slog.Any(name, slogFieldSkip{})
}
return slog.Bool(name, true)
}
func slogErr(err error) slog.Attr {
return slog.String("error", err.Error())
}
func slogStrings(key string, str []string) slog.Attr {
attrs := make([]any, len(str))
for i, val := range str {
attrs[i] = slog.String(strconv.Itoa(i), val)
}
return slog.Group(key, attrs...)
}
+241
View File
@@ -0,0 +1,241 @@
// Copyright (c) 2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fxevent
import (
"strings"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// ZapLogger is an Fx event logger that logs events to Zap.
type ZapLogger struct {
Logger *zap.Logger
logLevel zapcore.Level // default: zapcore.InfoLevel
errorLevel *zapcore.Level
}
var _ Logger = (*ZapLogger)(nil)
// UseErrorLevel sets the level of error logs emitted by Fx to level.
func (l *ZapLogger) UseErrorLevel(level zapcore.Level) {
l.errorLevel = &level
}
// UseLogLevel sets the level of non-error logs emitted by Fx to level.
func (l *ZapLogger) UseLogLevel(level zapcore.Level) {
l.logLevel = level
}
func (l *ZapLogger) logEvent(msg string, fields ...zap.Field) {
l.Logger.Log(l.logLevel, msg, fields...)
}
func (l *ZapLogger) logError(msg string, fields ...zap.Field) {
lvl := zapcore.ErrorLevel
if l.errorLevel != nil {
lvl = *l.errorLevel
}
l.Logger.Log(lvl, msg, fields...)
}
// LogEvent logs the given event to the provided Zap logger.
func (l *ZapLogger) LogEvent(event Event) {
switch e := event.(type) {
case *OnStartExecuting:
l.logEvent("OnStart hook executing",
zap.String("callee", e.FunctionName),
zap.String("caller", e.CallerName),
)
case *OnStartExecuted:
if e.Err != nil {
l.logError("OnStart hook failed",
zap.String("callee", e.FunctionName),
zap.String("caller", e.CallerName),
zap.Error(e.Err),
)
} else {
l.logEvent("OnStart hook executed",
zap.String("callee", e.FunctionName),
zap.String("caller", e.CallerName),
zap.String("runtime", e.Runtime.String()),
)
}
case *OnStopExecuting:
l.logEvent("OnStop hook executing",
zap.String("callee", e.FunctionName),
zap.String("caller", e.CallerName),
)
case *OnStopExecuted:
if e.Err != nil {
l.logError("OnStop hook failed",
zap.String("callee", e.FunctionName),
zap.String("caller", e.CallerName),
zap.Error(e.Err),
)
} else {
l.logEvent("OnStop hook executed",
zap.String("callee", e.FunctionName),
zap.String("caller", e.CallerName),
zap.String("runtime", e.Runtime.String()),
)
}
case *Supplied:
if e.Err != nil {
l.logError("error encountered while applying options",
zap.String("type", e.TypeName),
zap.Strings("stacktrace", e.StackTrace),
zap.Strings("moduletrace", e.ModuleTrace),
moduleField(e.ModuleName),
zap.Error(e.Err))
} else {
l.logEvent("supplied",
zap.String("type", e.TypeName),
zap.Strings("stacktrace", e.StackTrace),
zap.Strings("moduletrace", e.ModuleTrace),
moduleField(e.ModuleName),
)
}
case *Provided:
for _, rtype := range e.OutputTypeNames {
l.logEvent("provided",
zap.String("constructor", e.ConstructorName),
zap.Strings("stacktrace", e.StackTrace),
zap.Strings("moduletrace", e.ModuleTrace),
moduleField(e.ModuleName),
zap.String("type", rtype),
maybeBool("private", e.Private),
)
}
if e.Err != nil {
l.logError("error encountered while applying options",
moduleField(e.ModuleName),
zap.Strings("stacktrace", e.StackTrace),
zap.Strings("moduletrace", e.ModuleTrace),
zap.Error(e.Err))
}
case *Replaced:
for _, rtype := range e.OutputTypeNames {
l.logEvent("replaced",
zap.Strings("stacktrace", e.StackTrace),
zap.Strings("moduletrace", e.ModuleTrace),
moduleField(e.ModuleName),
zap.String("type", rtype),
)
}
if e.Err != nil {
l.logError("error encountered while replacing",
zap.Strings("stacktrace", e.StackTrace),
zap.Strings("moduletrace", e.ModuleTrace),
moduleField(e.ModuleName),
zap.Error(e.Err))
}
case *Decorated:
for _, rtype := range e.OutputTypeNames {
l.logEvent("decorated",
zap.String("decorator", e.DecoratorName),
zap.Strings("stacktrace", e.StackTrace),
zap.Strings("moduletrace", e.ModuleTrace),
moduleField(e.ModuleName),
zap.String("type", rtype),
)
}
if e.Err != nil {
l.logError("error encountered while applying options",
zap.Strings("stacktrace", e.StackTrace),
zap.Strings("moduletrace", e.ModuleTrace),
moduleField(e.ModuleName),
zap.Error(e.Err))
}
case *Run:
if e.Err != nil {
l.logError("error returned",
zap.String("name", e.Name),
zap.String("kind", e.Kind),
moduleField(e.ModuleName),
zap.Error(e.Err),
)
} else {
l.logEvent("run",
zap.String("name", e.Name),
zap.String("kind", e.Kind),
zap.String("runtime", e.Runtime.String()),
moduleField(e.ModuleName),
)
}
case *Invoking:
// Do not log stack as it will make logs hard to read.
l.logEvent("invoking",
zap.String("function", e.FunctionName),
moduleField(e.ModuleName),
)
case *Invoked:
if e.Err != nil {
l.logError("invoke failed",
zap.Error(e.Err),
zap.String("stack", e.Trace),
zap.String("function", e.FunctionName),
moduleField(e.ModuleName),
)
}
case *Stopping:
l.logEvent("received signal",
zap.String("signal", strings.ToUpper(e.Signal.String())))
case *Stopped:
if e.Err != nil {
l.logError("stop failed", zap.Error(e.Err))
}
case *RollingBack:
l.logError("start failed, rolling back", zap.Error(e.StartErr))
case *RolledBack:
if e.Err != nil {
l.logError("rollback failed", zap.Error(e.Err))
}
case *Started:
if e.Err != nil {
l.logError("start failed", zap.Error(e.Err))
} else {
l.logEvent("started")
}
case *LoggerInitialized:
if e.Err != nil {
l.logError("custom logger initialization failed", zap.Error(e.Err))
} else {
l.logEvent("initialized custom fxevent.Logger", zap.String("function", e.ConstructorName))
}
}
}
func moduleField(name string) zap.Field {
if len(name) == 0 {
return zap.Skip()
}
return zap.String("module", name)
}
func maybeBool(name string, b bool) zap.Field {
if b {
return zap.Bool(name, true)
}
return zap.Skip()
}
+43
View File
@@ -0,0 +1,43 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import "go.uber.org/dig"
// In can be embedded into a struct to mark it as a parameter struct.
// This allows it to make use of advanced dependency injection features.
// See package documentation for more information.
//
// It's recommended that shared modules use a single parameter struct to
// provide a forward-compatible API:
// adding new optional fields to a struct is backward-compatible,
// so modules can evolve as needs change.
type In = dig.In
// Out is the inverse of In: it marks a struct as a result struct so that
// it can be used with advanced dependency injection features.
// See package documentation for more information.
//
// It's recommended that shared modules use a single result struct to
// provide a forward-compatible API:
// adding new fields to a struct is backward-compatible,
// so modules can produce more outputs as they grow.
type Out = dig.Out
+260
View File
@@ -0,0 +1,260 @@
// Copyright (c) 2024 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fxclock
import (
"context"
"sort"
"sync"
"time"
)
// Clock defines how Fx accesses time.
// We keep the interface pretty minimal.
type Clock interface {
Now() time.Time
Since(time.Time) time.Duration
Sleep(time.Duration)
WithTimeout(context.Context, time.Duration) (context.Context, context.CancelFunc)
}
// System is the default implementation of Clock based on real time.
var System Clock = systemClock{}
type systemClock struct{}
func (systemClock) Now() time.Time {
return time.Now()
}
func (systemClock) Since(t time.Time) time.Duration {
return time.Since(t)
}
func (systemClock) Sleep(d time.Duration) {
time.Sleep(d)
}
func (systemClock) WithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
return context.WithTimeout(ctx, d)
}
// Mock adapted from
// https://github.com/uber-go/zap/blob/7db06bc9b095571d3dc3d4eebdfbe4dd9bd20405/internal/ztest/clock.go.
// Mock is a fake source of time.
// It implements standard time operations,
// but allows the user to control the passage of time.
//
// Use the [Add] method to progress time.
type Mock struct {
mu sync.RWMutex
now time.Time
// The MockClock works by maintaining a list of waiters.
// Each waiter knows the time at which it should be resolved.
// When the clock advances, all waiters that are in range are resolved
// in chronological order.
waiters []waiter
waiterAdded *sync.Cond
}
var _ Clock = (*Mock)(nil)
// NewMock builds a new mock clock
// using the current actual time as the initial time.
func NewMock() *Mock {
m := &Mock{now: time.Now()}
m.waiterAdded = sync.NewCond(&m.mu)
return m
}
// Now reports the current time.
func (c *Mock) Now() time.Time {
c.mu.RLock()
defer c.mu.RUnlock()
return c.now
}
// Since reports the time elapsed since t.
// This is short for Now().Sub(t).
func (c *Mock) Since(t time.Time) time.Duration {
return c.Now().Sub(t)
}
// Sleep pauses the current goroutine for the given duration.
//
// With the mock clock, this will freeze
// until the clock is advanced with [Add] past the deadline.
func (c *Mock) Sleep(d time.Duration) {
ch := make(chan struct{})
c.runAt(c.Now().Add(d), func() { close(ch) })
<-ch
}
// WithTimeout returns a new context with a deadline of now + d.
//
// When the deadline is passed, the returned context's Done channel is closed
// and the context's Err method returns context.DeadlineExceeded.
// If the cancel function is called before the deadline is passed,
// the context's Err method returns context.Canceled.
func (c *Mock) WithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
// Unfortunately, we can't use context.WithCancelCause here.
// Per its documentation (and verified by trying it):
//
// ctx, cancel := context.WithCancelCause(parent)
// cancel(myError)
// ctx.Err() // returns context.Canceled
// context.Cause(ctx) // returns myError
//
// So it won't do for our purposes.
deadline := c.Now().Add(d)
inner, cancelInner := context.WithCancel(ctx)
dctx := &deadlineCtx{
inner: inner,
cancelInner: cancelInner,
done: make(chan struct{}),
deadline: deadline,
}
ctx = dctx
c.runAt(deadline, func() {
dctx.cancel(context.DeadlineExceeded)
})
return ctx, func() { dctx.cancel(context.Canceled) }
}
type deadlineCtx struct {
inner context.Context
cancelInner func()
done chan struct{}
deadline time.Time
mu sync.Mutex // guards err; the rest is immutable
err error
}
var _ context.Context = (*deadlineCtx)(nil)
func (c *deadlineCtx) Deadline() (deadline time.Time, ok bool) { return c.deadline, true }
func (c *deadlineCtx) Done() <-chan struct{} { return c.done }
func (c *deadlineCtx) Value(key any) any { return c.inner.Value(key) }
func (c *deadlineCtx) Err() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.err
}
func (c *deadlineCtx) cancel(err error) {
c.mu.Lock()
if c.err == nil {
c.err = err
close(c.done)
c.cancelInner()
}
c.mu.Unlock()
}
// runAt schedules the given function to be run at the given time.
// The function runs without a lock held, so it may schedule more work.
func (c *Mock) runAt(t time.Time, fn func()) {
c.mu.Lock()
defer c.mu.Unlock()
c.waiters = append(c.waiters, waiter{until: t, fn: fn})
c.waiterAdded.Broadcast()
}
// AwaitScheduled blocks until there are at least N
// operations scheduled for the future.
func (c *Mock) AwaitScheduled(n int) {
c.mu.Lock()
defer c.mu.Unlock()
// Note: waiterAdded is associated with c.mu,
// the same lock we're holding here.
//
// When we call Wait(), it'll release the lock
// and block until signaled by runAt,
// at which point it'll reacquire the lock
// (waiting until runAt has released it).
for len(c.waiters) < n {
c.waiterAdded.Wait()
}
}
type waiter struct {
until time.Time
fn func()
}
// Add progresses time by the given duration.
// Other operations waiting for the time to advance
// will be resolved if they are within range.
//
// Side effects of operations waiting for the time to advance
// will take effect on a best-effort basis.
// Avoid racing with operations that have side effects.
//
// Panics if the duration is negative.
func (c *Mock) Add(d time.Duration) {
if d < 0 {
panic("cannot add negative duration")
}
c.mu.Lock()
defer c.mu.Unlock()
sort.Slice(c.waiters, func(i, j int) bool {
return c.waiters[i].until.Before(c.waiters[j].until)
})
newTime := c.now.Add(d)
// newTime won't be recorded until the end of this method.
// This ensures that any waiters that are resolved
// are resolved at the time they were expecting.
for len(c.waiters) > 0 {
w := c.waiters[0]
if w.until.After(newTime) {
break
}
c.waiters[0] = waiter{} // avoid memory leak
c.waiters = c.waiters[1:]
// The waiter is within range.
// Travel to the time of the waiter and resolve it.
c.now = w.until
// The waiter may schedule more work
// so we must release the lock.
c.mu.Unlock()
w.fn()
// Sleeping here is necessary to let the side effects of waiters
// take effect before we continue.
time.Sleep(1 * time.Millisecond)
c.mu.Lock()
}
c.now = newTime
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright (c) 2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fxlog
import (
"io"
"go.uber.org/fx/fxevent"
)
// DefaultLogger constructs a Logger out of io.Writer.
func DefaultLogger(w io.Writer) fxevent.Logger {
return &fxevent.ConsoleLogger{W: w}
}
+91
View File
@@ -0,0 +1,91 @@
// Copyright (c) 2020-2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fxlog
import (
"reflect"
"sync"
"go.uber.org/fx/fxevent"
)
// Events is a list of events captured by fxlog.Spy.
type Events []fxevent.Event
// Len returns the number of events in this list.
func (es Events) Len() int { return len(es) }
// SelectByTypeName returns a new list with only events matching the specified
// type.
func (es Events) SelectByTypeName(name string) Events {
var out Events
for _, e := range es {
if reflect.TypeOf(e).Elem().Name() == name {
out = append(out, e)
}
}
return out
}
// Spy is an Fx event logger that captures emitted events and/or logged
// statements. It may be used in tests of Fx logs.
type Spy struct {
mu sync.RWMutex
events Events
}
var _ fxevent.Logger = &Spy{}
// LogEvent appends an Event.
func (s *Spy) LogEvent(event fxevent.Event) {
s.mu.Lock()
s.events = append(s.events, event)
s.mu.Unlock()
}
// Events returns all captured events.
func (s *Spy) Events() Events {
s.mu.RLock()
defer s.mu.RUnlock()
events := make(Events, len(s.events))
copy(events, s.events)
return events
}
// EventTypes returns all captured event types.
func (s *Spy) EventTypes() []string {
s.mu.RLock()
defer s.mu.RUnlock()
types := make([]string, len(s.events))
for i, e := range s.events {
types[i] = reflect.TypeOf(e).Elem().Name()
}
return types
}
// Reset clears all messages and events from the Spy.
func (s *Spy) Reset() {
s.mu.Lock()
s.events = s.events[:0]
s.mu.Unlock()
}
+86
View File
@@ -0,0 +1,86 @@
// Copyright (c) 2019-2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fxreflect
import (
"fmt"
"net/url"
"reflect"
"regexp"
"runtime"
"strings"
)
// Match from beginning of the line until the first `vendor/` (non-greedy)
var vendorRe = regexp.MustCompile("^.*?/vendor/")
// sanitize makes the function name suitable for logging display. It removes
// url-encoded elements from the `dot.git` package names and shortens the
// vendored paths.
func sanitize(function string) string {
// Use the stdlib to un-escape any package import paths which can happen
// in the case of the "dot-git" postfix. Seems like a bug in stdlib =/
if unescaped, err := url.QueryUnescape(function); err == nil {
function = unescaped
}
// strip everything prior to the vendor
return vendorRe.ReplaceAllString(function, "vendor/")
}
// Caller returns the formatted calling func name
func Caller() string {
return CallerStack(1, 0).CallerName()
}
// FuncName returns a funcs formatted name
func FuncName(fn interface{}) string {
fnV := reflect.ValueOf(fn)
if fnV.Kind() != reflect.Func {
return fmt.Sprint(fn)
}
function := runtime.FuncForPC(fnV.Pointer()).Name()
return fmt.Sprintf("%s()", sanitize(function))
}
// Ascend the call stack until we leave the Fx production code. This allows us
// to avoid hard-coding a frame skip, which makes this code work well even
// when it's wrapped.
func shouldIgnoreFrame(f Frame) bool {
// Treat test files as leafs.
if strings.Contains(f.File, "_test.go") {
return false
}
// The unique, fully-qualified name for all functions begins with
// "{{importPath}}.". We'll ignore Fx and its subpackages.
s := strings.TrimPrefix(f.Function, "go.uber.org/fx")
if len(s) > 0 && s[0] == '.' || s[0] == '/' {
// We want to match,
// go.uber.org/fx.Foo
// go.uber.org/fx/something.Foo
// But not, go.uber.org/fxfoo
return true
}
return false
}
+158
View File
@@ -0,0 +1,158 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fxreflect
import (
"fmt"
"io"
"runtime"
"strings"
)
// Frame holds information about a single frame in the call stack.
type Frame struct {
// Unique, package path-qualified name for the function of this call
// frame.
Function string
// File and line number of our location in the frame.
//
// Note that the line number does not refer to where the function was
// defined but where in the function the next call was made.
File string
Line int
}
func (f Frame) String() string {
// This takes the following forms.
// (path/to/file.go)
// (path/to/file.go:42)
// path/to/package.MyFunction
// path/to/package.MyFunction (path/to/file.go)
// path/to/package.MyFunction (path/to/file.go:42)
var sb strings.Builder
sb.WriteString(f.Function)
if len(f.File) > 0 {
if sb.Len() > 0 {
sb.WriteRune(' ')
}
fmt.Fprintf(&sb, "(%v", f.File)
if f.Line > 0 {
fmt.Fprintf(&sb, ":%d", f.Line)
}
sb.WriteRune(')')
}
if sb.Len() == 0 {
return "unknown"
}
return sb.String()
}
const _defaultCallersDepth = 8
// Stack is a stack of call frames.
//
// Formatted with %v, the output is in a single-line, in the form,
//
// foo/bar.Baz() (path/to/foo.go:42); bar/baz.Qux() (bar/baz/qux.go:12); ...
//
// Formatted with %+v, the output is in the form,
//
// foo/bar.Baz()
// path/to/foo.go:42
// bar/baz.Qux()
// bar/baz/qux.go:12
type Stack []Frame
// String returns a single-line, semi-colon representation of a Stack.
// For a list of strings where each represents one frame, use Strings.
// For a cleaner multi-line representation, use %+v.
func (fs Stack) String() string {
return strings.Join(fs.Strings(), "; ")
}
// Strings returns a list of strings, each representing a frame in the stack.
// Each line will be in the form,
//
// foo/bar.Baz() (path/to/foo.go:42)
func (fs Stack) Strings() []string {
items := make([]string, len(fs))
for i, f := range fs {
items[i] = f.String()
}
return items
}
// Format implements fmt.Formatter to handle "%+v".
func (fs Stack) Format(w fmt.State, c rune) {
if !w.Flag('+') {
// Without %+v, fall back to String().
io.WriteString(w, fs.String())
return
}
for _, f := range fs {
fmt.Fprintln(w, f.Function)
fmt.Fprintf(w, "\t%v:%v\n", f.File, f.Line)
}
}
// CallerName returns the name of the first caller in this stack that isn't
// owned by the Fx library.
func (fs Stack) CallerName() string {
for _, f := range fs {
if shouldIgnoreFrame(f) {
continue
}
return f.Function
}
return "n/a"
}
// CallerStack returns the call stack for the calling function, up to depth frames
// deep, skipping the provided number of frames, not including Callers itself.
//
// If zero, depth defaults to 8.
func CallerStack(skip, depth int) Stack {
if depth <= 0 {
depth = _defaultCallersDepth
}
pcs := make([]uintptr, depth)
// +2 to skip this frame and runtime.Callers.
n := runtime.Callers(skip+2, pcs)
pcs = pcs[:n] // truncate to number of frames actually read
result := make([]Frame, 0, n)
frames := runtime.CallersFrames(pcs)
for f, more := frames.Next(); more; f, more = frames.Next() {
result = append(result, Frame{
Function: sanitize(f.Function),
File: f.File,
Line: f.Line,
})
}
return result
}
+401
View File
@@ -0,0 +1,401 @@
// Copyright (c) 2022 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package lifecycle
import (
"context"
"errors"
"fmt"
"io"
"reflect"
"strings"
"sync"
"time"
"go.uber.org/fx/fxevent"
"go.uber.org/fx/internal/fxclock"
"go.uber.org/fx/internal/fxreflect"
"go.uber.org/multierr"
)
// Reflection types for each of the supported hook function signatures. These
// are used in cases in which the Callable constraint matches a user-defined
// function type that cannot be converted to an underlying function type with
// a conventional conversion or type switch.
var (
_reflFunc = reflect.TypeOf(Func(nil))
_reflErrorFunc = reflect.TypeOf(ErrorFunc(nil))
_reflContextFunc = reflect.TypeOf(ContextFunc(nil))
_reflContextErrorFunc = reflect.TypeOf(ContextErrorFunc(nil))
)
// Discrete function signatures that are allowed as part of a [Callable].
type (
// A Func can be converted to a ContextErrorFunc.
Func = func()
// An ErrorFunc can be converted to a ContextErrorFunc.
ErrorFunc = func() error
// A ContextFunc can be converted to a ContextErrorFunc.
ContextFunc = func(context.Context)
// A ContextErrorFunc is used as a [Hook.OnStart] or [Hook.OnStop]
// function.
ContextErrorFunc = func(context.Context) error
)
// A Callable is a constraint that matches functions that are, or can be
// converted to, functions suitable for a Hook.
//
// Callable must be identical to [fx.HookFunc].
type Callable interface {
~Func | ~ErrorFunc | ~ContextFunc | ~ContextErrorFunc
}
// Wrap wraps x into a ContextErrorFunc suitable for a Hook.
func Wrap[T Callable](x T) (ContextErrorFunc, string) {
if x == nil {
return nil, ""
}
switch fn := any(x).(type) {
case Func:
return func(context.Context) error {
fn()
return nil
}, fxreflect.FuncName(x)
case ErrorFunc:
return func(context.Context) error {
return fn()
}, fxreflect.FuncName(x)
case ContextFunc:
return func(ctx context.Context) error {
fn(ctx)
return nil
}, fxreflect.FuncName(x)
case ContextErrorFunc:
return fn, fxreflect.FuncName(x)
}
// Since (1) we're already using reflect in Fx, (2) we're not particularly
// concerned with performance, and (3) unsafe would require discrete build
// targets for appengine (etc), just use reflect to convert user-defined
// function types to their underlying function types and then call Wrap
// again with the converted value.
reflVal := reflect.ValueOf(x)
switch {
case reflVal.CanConvert(_reflFunc):
return Wrap(reflVal.Convert(_reflFunc).Interface().(Func))
case reflVal.CanConvert(_reflErrorFunc):
return Wrap(reflVal.Convert(_reflErrorFunc).Interface().(ErrorFunc))
case reflVal.CanConvert(_reflContextFunc):
return Wrap(reflVal.Convert(_reflContextFunc).Interface().(ContextFunc))
default:
// Is already convertible to ContextErrorFunc.
return Wrap(reflVal.Convert(_reflContextErrorFunc).Interface().(ContextErrorFunc))
}
}
// A Hook is a pair of start and stop callbacks, either of which can be nil,
// plus a string identifying the supplier of the hook.
type Hook struct {
OnStart func(context.Context) error
OnStop func(context.Context) error
OnStartName string
OnStopName string
callerFrame fxreflect.Frame
}
type appState int
const (
stopped appState = iota
starting
incompleteStart
started
stopping
)
func (as appState) String() string {
switch as {
case stopped:
return "stopped"
case starting:
return "starting"
case incompleteStart:
return "incompleteStart"
case started:
return "started"
case stopping:
return "stopping"
default:
return "invalidState"
}
}
// Lifecycle coordinates application lifecycle hooks.
type Lifecycle struct {
clock fxclock.Clock
logger fxevent.Logger
state appState
hooks []Hook
numStarted int
startRecords HookRecords
stopRecords HookRecords
runningHook Hook
mu sync.Mutex
}
// New constructs a new Lifecycle.
func New(logger fxevent.Logger, clock fxclock.Clock) *Lifecycle {
return &Lifecycle{logger: logger, clock: clock}
}
// Append adds a Hook to the lifecycle.
func (l *Lifecycle) Append(hook Hook) {
// Save the caller's stack frame to report file/line number.
if f := fxreflect.CallerStack(2, 0); len(f) > 0 {
hook.callerFrame = f[0]
}
l.hooks = append(l.hooks, hook)
}
// Start runs all OnStart hooks, returning immediately if it encounters an
// error.
func (l *Lifecycle) Start(ctx context.Context) error {
if ctx == nil {
return errors.New("called OnStart with nil context")
}
l.mu.Lock()
if l.state != stopped {
defer l.mu.Unlock()
return fmt.Errorf("attempted to start lifecycle when in state: %v", l.state)
}
l.numStarted = 0
l.state = starting
l.startRecords = make(HookRecords, 0, len(l.hooks))
l.mu.Unlock()
returnState := incompleteStart
defer func() {
l.mu.Lock()
l.state = returnState
l.mu.Unlock()
}()
for _, hook := range l.hooks {
// if ctx has cancelled, bail out of the loop.
if err := ctx.Err(); err != nil {
return err
}
if hook.OnStart != nil {
l.mu.Lock()
l.runningHook = hook
l.mu.Unlock()
runtime, err := l.runStartHook(ctx, hook)
if err != nil {
return err
}
l.mu.Lock()
l.startRecords = append(l.startRecords, HookRecord{
CallerFrame: hook.callerFrame,
Func: hook.OnStart,
Runtime: runtime,
})
l.mu.Unlock()
}
l.numStarted++
}
returnState = started
return nil
}
func (l *Lifecycle) runStartHook(ctx context.Context, hook Hook) (runtime time.Duration, err error) {
funcName := hook.OnStartName
if len(funcName) == 0 {
funcName = fxreflect.FuncName(hook.OnStart)
}
l.logger.LogEvent(&fxevent.OnStartExecuting{
CallerName: hook.callerFrame.Function,
FunctionName: funcName,
})
defer func() {
l.logger.LogEvent(&fxevent.OnStartExecuted{
CallerName: hook.callerFrame.Function,
FunctionName: funcName,
Runtime: runtime,
Err: err,
})
}()
begin := l.clock.Now()
err = hook.OnStart(ctx)
return l.clock.Since(begin), err
}
// Stop runs any OnStop hooks whose OnStart counterpart succeeded. OnStop
// hooks run in reverse order.
func (l *Lifecycle) Stop(ctx context.Context) error {
if ctx == nil {
return errors.New("called OnStop with nil context")
}
l.mu.Lock()
if l.state != started && l.state != incompleteStart && l.state != starting {
defer l.mu.Unlock()
return nil
}
l.state = stopping
l.mu.Unlock()
defer func() {
l.mu.Lock()
l.state = stopped
l.mu.Unlock()
}()
l.mu.Lock()
l.stopRecords = make(HookRecords, 0, l.numStarted)
// Take a snapshot of hook state to avoid races.
allHooks := l.hooks[:]
numStarted := l.numStarted
l.mu.Unlock()
// Run backward from last successful OnStart.
var errs []error
for ; numStarted > 0; numStarted-- {
if err := ctx.Err(); err != nil {
return err
}
hook := allHooks[numStarted-1]
if hook.OnStop == nil {
continue
}
l.mu.Lock()
l.runningHook = hook
l.mu.Unlock()
runtime, err := l.runStopHook(ctx, hook)
if err != nil {
// For best-effort cleanup, keep going after errors.
errs = append(errs, err)
}
l.mu.Lock()
l.stopRecords = append(l.stopRecords, HookRecord{
CallerFrame: hook.callerFrame,
Func: hook.OnStop,
Runtime: runtime,
})
l.mu.Unlock()
}
return multierr.Combine(errs...)
}
func (l *Lifecycle) runStopHook(ctx context.Context, hook Hook) (runtime time.Duration, err error) {
funcName := hook.OnStopName
if len(funcName) == 0 {
funcName = fxreflect.FuncName(hook.OnStop)
}
l.logger.LogEvent(&fxevent.OnStopExecuting{
CallerName: hook.callerFrame.Function,
FunctionName: funcName,
})
defer func() {
l.logger.LogEvent(&fxevent.OnStopExecuted{
CallerName: hook.callerFrame.Function,
FunctionName: funcName,
Runtime: runtime,
Err: err,
})
}()
begin := l.clock.Now()
err = hook.OnStop(ctx)
return l.clock.Since(begin), err
}
// RunningHookCaller returns the name of the hook that was running when a Start/Stop
// hook timed out.
func (l *Lifecycle) RunningHookCaller() string {
l.mu.Lock()
defer l.mu.Unlock()
return l.runningHook.callerFrame.Function
}
// HookRecord keeps track of each Hook's execution time, the caller that appended the Hook, and function that ran as the Hook.
type HookRecord struct {
CallerFrame fxreflect.Frame // stack frame of the caller
Func func(context.Context) error // function that ran as sanitized name
Runtime time.Duration // how long the hook ran
}
// HookRecords is a Stringer wrapper of HookRecord slice.
type HookRecords []HookRecord
func (rs HookRecords) Len() int {
return len(rs)
}
func (rs HookRecords) Less(i, j int) bool {
// Sort by runtime, greater ones at top.
return rs[i].Runtime > rs[j].Runtime
}
func (rs HookRecords) Swap(i, j int) {
rs[i], rs[j] = rs[j], rs[i]
}
// Used for logging startup errors.
func (rs HookRecords) String() string {
var b strings.Builder
for _, r := range rs {
fmt.Fprintf(&b, "%s took %v from %s",
fxreflect.FuncName(r.Func), r.Runtime, r.CallerFrame)
}
return b.String()
}
// Format implements fmt.Formatter to handle "%+v".
func (rs HookRecords) Format(w fmt.State, c rune) {
if !w.Flag('+') {
// Without %+v, fall back to String().
io.WriteString(w, rs.String())
return
}
for _, r := range rs {
fmt.Fprintf(w, "\n%s took %v from:\n\t%+v",
fxreflect.FuncName(r.Func),
r.Runtime,
r.CallerFrame)
}
fmt.Fprintf(w, "\n")
}
+111
View File
@@ -0,0 +1,111 @@
// Copyright (c) 2019-2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"fmt"
"strings"
"go.uber.org/fx/internal/fxreflect"
)
// Invoke registers functions that are executed eagerly on application start.
// Arguments for these invocations are built using the constructors registered
// by Provide. Passing multiple Invoke options appends the new invocations to
// the application's existing list.
//
// Unlike constructors, invocations are always executed, and they're always
// run in order. Invocations may have any number of returned values.
// If the final returned object is an error, it indicates whether the operation
// was successful.
// All other returned values are discarded.
//
// Invokes registered in [Module]s are run before the ones registered at the
// scope of the parent. Invokes within the same Module is run in the order
// they were provided. For example,
//
// fx.New(
// fx.Invoke(func3),
// fx.Module("someModule",
// fx.Invoke(func1),
// fx.Invoke(func2),
// ),
// fx.Invoke(func4),
// )
//
// invokes func1, func2, func3, func4 in that order.
//
// Typically, invoked functions take a handful of high-level objects (whose
// constructors depend on lower-level objects) and introduce them to each
// other. This kick-starts the application by forcing it to instantiate a
// variety of types.
//
// To see an invocation in use, read through the package-level example. For
// advanced features, including optional parameters and named instances, see
// the documentation of the In and Out types.
func Invoke(funcs ...interface{}) Option {
return invokeOption{
Targets: funcs,
Stack: fxreflect.CallerStack(1, 0),
}
}
type invokeOption struct {
Targets []interface{}
Stack fxreflect.Stack
}
func (o invokeOption) apply(mod *module) {
for _, target := range o.Targets {
mod.invokes = append(mod.invokes, invoke{
Target: target,
Stack: o.Stack,
})
}
}
func (o invokeOption) String() string {
items := make([]string, len(o.Targets))
for i, f := range o.Targets {
items[i] = fxreflect.FuncName(f)
}
return fmt.Sprintf("fx.Invoke(%s)", strings.Join(items, ", "))
}
func runInvoke(c container, i invoke) error {
fn := i.Target
switch fn := fn.(type) {
case Option:
return fmt.Errorf("fx.Option should be passed to fx.New directly, "+
"not to fx.Invoke: fx.Invoke received %v from:\n%+v",
fn, i.Stack)
case annotated:
af, err := fn.Build()
if err != nil {
return err
}
return c.Invoke(af)
default:
return c.Invoke(fn)
}
}
+147
View File
@@ -0,0 +1,147 @@
// Copyright (c) 2022 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"context"
"go.uber.org/fx/internal/lifecycle"
)
// A HookFunc is a function that can be used as a [Hook].
type HookFunc interface {
~func() | ~func() error | ~func(context.Context) | ~func(context.Context) error
}
// Lifecycle allows constructors to register callbacks that are executed on
// application start and stop. See the documentation for App for details on Fx
// applications' initialization, startup, and shutdown logic.
type Lifecycle interface {
Append(Hook)
}
// A Hook is a pair of start and stop callbacks, either of which can be nil.
// If a Hook's OnStart callback isn't executed (because a previous OnStart
// failure short-circuited application startup), its OnStop callback won't be
// executed.
type Hook struct {
OnStart func(context.Context) error
OnStop func(context.Context) error
onStartName string
onStopName string
}
// StartHook returns a new Hook with start as its [Hook.OnStart] function,
// wrapping its signature as needed. For example, given the following function:
//
// func myhook() {
// fmt.Println("hook called")
// }
//
// then calling:
//
// lifecycle.Append(StartHook(myfunc))
//
// is functionally equivalent to calling:
//
// lifecycle.Append(fx.Hook{
// OnStart: func(context.Context) error {
// myfunc()
// return nil
// },
// })
//
// The same is true for all functions that satisfy the HookFunc constraint.
// Note that any context.Context parameter or error return will be propagated
// as expected. If propagation is not intended, users should instead provide a
// closure that discards the undesired value(s), or construct a Hook directly.
func StartHook[T HookFunc](start T) Hook {
onstart, startname := lifecycle.Wrap(start)
return Hook{
OnStart: onstart,
onStartName: startname,
}
}
// StopHook returns a new Hook with stop as its [Hook.OnStop] function,
// wrapping its signature as needed. For example, given the following function:
//
// func myhook() {
// fmt.Println("hook called")
// }
//
// then calling:
//
// lifecycle.Append(StopHook(myfunc))
//
// is functionally equivalent to calling:
//
// lifecycle.Append(fx.Hook{
// OnStop: func(context.Context) error {
// myfunc()
// return nil
// },
// })
//
// The same is true for all functions that satisfy the HookFunc constraint.
// Note that any context.Context parameter or error return will be propagated
// as expected. If propagation is not intended, users should instead provide a
// closure that discards the undesired value(s), or construct a Hook directly.
func StopHook[T HookFunc](stop T) Hook {
onstop, stopname := lifecycle.Wrap(stop)
return Hook{
OnStop: onstop,
onStopName: stopname,
}
}
// StartStopHook returns a new Hook with start as its [Hook.OnStart] function
// and stop as its [Hook.OnStop] function, independently wrapping the signature
// of each as needed.
func StartStopHook[T1 HookFunc, T2 HookFunc](start T1, stop T2) Hook {
var (
onstart, startname = lifecycle.Wrap(start)
onstop, stopname = lifecycle.Wrap(stop)
)
return Hook{
OnStart: onstart,
OnStop: onstop,
onStartName: startname,
onStopName: stopname,
}
}
type lifecycleWrapper struct {
*lifecycle.Lifecycle
}
func (l *lifecycleWrapper) Append(h Hook) {
l.Lifecycle.Append(lifecycle.Hook{
OnStart: h.OnStart,
OnStop: h.OnStop,
OnStartName: h.onStartName,
OnStopName: h.onStopName,
})
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright (c) 2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"go.uber.org/fx/fxevent"
)
// logBuffer will buffer all messages until a logger has been
// initialized.
type logBuffer struct {
events []fxevent.Event
logger fxevent.Logger
}
// LogEvent buffers or logs an event.
func (l *logBuffer) LogEvent(event fxevent.Event) {
if l.logger == nil {
l.events = append(l.events, event)
} else {
l.logger.LogEvent(event)
}
}
// Connect flushes out all buffered events to a logger and resets them.
func (l *logBuffer) Connect(logger fxevent.Logger) {
l.logger = logger
for _, e := range l.events {
logger.LogEvent(e)
}
l.events = nil
}
+408
View File
@@ -0,0 +1,408 @@
// Copyright (c) 2022 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"fmt"
"go.uber.org/dig"
"go.uber.org/fx/fxevent"
"go.uber.org/fx/internal/fxreflect"
"go.uber.org/multierr"
)
// A container represents a set of constructors to provide
// dependencies, and a set of functions to invoke once all the
// dependencies have been initialized.
//
// This definition corresponds to the dig.Container and dig.Scope.
type container interface {
Invoke(interface{}, ...dig.InvokeOption) error
Provide(interface{}, ...dig.ProvideOption) error
Decorate(interface{}, ...dig.DecorateOption) error
}
// Module is a named group of zero or more fx.Options.
//
// A Module scopes the effect of certain operations to within the module.
// For more information, see [Decorate], [Replace], or [Invoke].
//
// Module allows packages to bundle sophisticated functionality into easy-to-use
// logical units.
// For example, a logging package might export a simple option like this:
//
// package logging
//
// var Module = fx.Module("logging",
// fx.Provide(func() *log.Logger {
// return log.New(os.Stdout, "", 0)
// }),
// // ...
// )
//
// A shared all-in-one microservice package could use Module to bundle
// all required components of a microservice:
//
// package server
//
// var Module = fx.Module("server",
// logging.Module,
// metrics.Module,
// tracing.Module,
// rpc.Module,
// )
//
// When new global functionality is added to the service ecosystem,
// it can be added to the shared module with minimal churn for users.
//
// Use the all-in-one pattern sparingly.
// It limits the flexibility available to the application.
func Module(name string, opts ...Option) Option {
mo := moduleOption{
name: name,
location: fxreflect.CallerStack(1, 2)[0],
options: opts,
}
return mo
}
type moduleOption struct {
name string
location fxreflect.Frame
options []Option
}
func (o moduleOption) String() string {
return fmt.Sprintf("fx.Module(%q, %v)", o.name, o.options)
}
func (o moduleOption) apply(mod *module) {
// This get called on any submodules' that are declared
// as part of another module.
// 1. Create a new module with the parent being the specified
// module.
// 2. Apply child Options on the new module.
// 3. Append it to the parent module.
// Create trace as parent's trace with this module's location pre-pended.
trace := append([]string{fmt.Sprintf("%v (%v)", o.location, o.name)}, mod.trace...)
newModule := &module{
name: o.name,
parent: mod,
trace: trace,
app: mod.app,
}
for _, opt := range o.options {
opt.apply(newModule)
}
mod.modules = append(mod.modules, newModule)
}
type module struct {
parent *module
name string
trace []string
scope scope
provides []provide
invokes []invoke
decorators []decorator
modules []*module
app *App
log fxevent.Logger
fallbackLogger fxevent.Logger
logConstructor *provide
}
// scope is a private wrapper interface for dig.Container and dig.Scope.
// We can consider moving this into Fx using type constraints after Go 1.20
// is released and 1.17 is deprecated.
type scope interface {
Decorate(f interface{}, opts ...dig.DecorateOption) error
Invoke(f interface{}, opts ...dig.InvokeOption) error
Provide(f interface{}, opts ...dig.ProvideOption) error
Scope(name string, opts ...dig.ScopeOption) *dig.Scope
String() string
}
// builds the Scopes using the App's Container. Note that this happens
// after applyModules' are called because the App's Container needs to
// be built for any Scopes to be initialized, and applys' should be called
// before the Container can get initialized.
func (m *module) build(app *App, root *dig.Container) {
if m.parent == nil {
m.scope = root
} else {
parentScope := m.parent.scope
m.scope = parentScope.Scope(m.name)
// use parent module's logger by default
m.log = m.parent.log
}
if m.logConstructor != nil {
// Since user supplied a custom logger, use a buffered logger
// to hold all messages until user supplied logger is
// instantiated. Then we flush those messages after fully
// constructing the custom logger.
m.fallbackLogger, m.log = m.log, new(logBuffer)
}
for _, mod := range m.modules {
mod.build(app, root)
}
}
func (m *module) provideAll() {
for _, p := range m.provides {
m.provide(p)
}
for _, m := range m.modules {
m.provideAll()
}
}
func (m *module) provide(p provide) {
if m.app.err != nil {
return
}
if p.IsSupply {
m.supply(p)
return
}
funcName := fxreflect.FuncName(p.Target)
var info dig.ProvideInfo
opts := []dig.ProvideOption{
dig.FillProvideInfo(&info),
dig.Export(!p.Private),
dig.WithProviderCallback(func(ci dig.CallbackInfo) {
m.log.LogEvent(&fxevent.Run{
Name: funcName,
Kind: "provide",
ModuleName: m.name,
Runtime: ci.Runtime,
Err: ci.Error,
})
}),
}
if err := runProvide(m.scope, p, opts...); err != nil {
m.app.err = err
}
outputNames := make([]string, len(info.Outputs))
for i, o := range info.Outputs {
outputNames[i] = o.String()
}
m.log.LogEvent(&fxevent.Provided{
ConstructorName: funcName,
StackTrace: p.Stack.Strings(),
ModuleTrace: append([]string{p.Stack[0].String()}, m.trace...),
ModuleName: m.name,
OutputTypeNames: outputNames,
Err: m.app.err,
Private: p.Private,
})
}
func (m *module) supply(p provide) {
typeName := p.SupplyType.String()
opts := []dig.ProvideOption{
dig.Export(!p.Private),
dig.WithProviderCallback(func(ci dig.CallbackInfo) {
m.log.LogEvent(&fxevent.Run{
Name: fmt.Sprintf("stub(%v)", typeName),
Kind: "supply",
Runtime: ci.Runtime,
ModuleName: m.name,
})
}),
}
if err := runProvide(m.scope, p, opts...); err != nil {
m.app.err = err
}
m.log.LogEvent(&fxevent.Supplied{
TypeName: typeName,
StackTrace: p.Stack.Strings(),
ModuleTrace: append([]string{p.Stack[0].String()}, m.trace...),
ModuleName: m.name,
Err: m.app.err,
})
}
// Constructs custom loggers for all modules in the tree
func (m *module) constructAllCustomLoggers() {
if m.logConstructor != nil {
if buffer, ok := m.log.(*logBuffer); ok {
// default to parent's logger if custom logger constructor fails
if err := m.constructCustomLogger(buffer); err != nil {
m.app.err = multierr.Append(m.app.err, err)
m.log = m.fallbackLogger
buffer.Connect(m.log)
}
}
m.fallbackLogger = nil
} else if m.parent != nil {
m.log = m.parent.log
}
for _, mod := range m.modules {
mod.constructAllCustomLoggers()
}
}
// Mirroring the behavior of app.constructCustomLogger
func (m *module) constructCustomLogger(buffer *logBuffer) (err error) {
p := m.logConstructor
fname := fxreflect.FuncName(p.Target)
defer func() {
m.log.LogEvent(&fxevent.LoggerInitialized{
Err: err,
ConstructorName: fname,
})
}()
// TODO: Use dig.FillProvideInfo to inspect the provided constructor
// and fail the application if its signature didn't match.
if err := m.scope.Provide(p.Target); err != nil {
return fmt.Errorf("fx.WithLogger(%v) from:\n%+v\nin Module: %q\nFailed: %w",
fname, p.Stack, m.name, err)
}
return m.scope.Invoke(func(log fxevent.Logger) {
m.log = log
buffer.Connect(log)
})
}
func (m *module) executeInvokes() error {
for _, m := range m.modules {
if err := m.executeInvokes(); err != nil {
return err
}
}
for _, invoke := range m.invokes {
if err := m.executeInvoke(invoke); err != nil {
return err
}
}
return nil
}
func (m *module) executeInvoke(i invoke) (err error) {
fnName := fxreflect.FuncName(i.Target)
m.log.LogEvent(&fxevent.Invoking{
FunctionName: fnName,
ModuleName: m.name,
})
err = runInvoke(m.scope, i)
m.log.LogEvent(&fxevent.Invoked{
FunctionName: fnName,
ModuleName: m.name,
Err: err,
Trace: fmt.Sprintf("%+v", i.Stack), // format stack trace as multi-line
})
return err
}
func (m *module) decorateAll() error {
for _, d := range m.decorators {
if err := m.decorate(d); err != nil {
return err
}
}
for _, m := range m.modules {
if err := m.decorateAll(); err != nil {
return err
}
}
return nil
}
func (m *module) decorate(d decorator) (err error) {
if d.IsReplace {
return m.replace(d)
}
funcName := fxreflect.FuncName(d.Target)
var info dig.DecorateInfo
opts := []dig.DecorateOption{
dig.FillDecorateInfo(&info),
dig.WithDecoratorCallback(func(ci dig.CallbackInfo) {
m.log.LogEvent(&fxevent.Run{
Name: funcName,
Kind: "decorate",
ModuleName: m.name,
Runtime: ci.Runtime,
Err: ci.Error,
})
}),
}
err = runDecorator(m.scope, d, opts...)
outputNames := make([]string, len(info.Outputs))
for i, o := range info.Outputs {
outputNames[i] = o.String()
}
m.log.LogEvent(&fxevent.Decorated{
DecoratorName: funcName,
StackTrace: d.Stack.Strings(),
ModuleTrace: append([]string{d.Stack[0].String()}, m.trace...),
ModuleName: m.name,
OutputTypeNames: outputNames,
Err: err,
})
return err
}
func (m *module) replace(d decorator) error {
typeName := d.ReplaceType.String()
opts := []dig.DecorateOption{
dig.WithDecoratorCallback(func(ci dig.CallbackInfo) {
m.log.LogEvent(&fxevent.Run{
Name: fmt.Sprintf("stub(%v)", typeName),
Kind: "replace",
ModuleName: m.name,
Runtime: ci.Runtime,
Err: ci.Error,
})
}),
}
err := runDecorator(m.scope, d, opts...)
m.log.LogEvent(&fxevent.Replaced{
ModuleName: m.name,
StackTrace: d.Stack.Strings(),
ModuleTrace: append([]string{d.Stack[0].String()}, m.trace...),
OutputTypeNames: []string{typeName},
Err: err,
})
return err
}
+115
View File
@@ -0,0 +1,115 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"fmt"
"reflect"
)
// Populate sets targets with values from the dependency injection container
// during application initialization. All targets must be pointers to the
// values that must be populated. Pointers to structs that embed In are
// supported, which can be used to populate multiple values in a struct.
//
// Annotating each pointer with ParamTags is also supported as a shorthand
// to passing a pointer to a struct that embeds In with field tags. For example:
//
// var a A
// var b B
// fx.Populate(
// fx.Annotate(
// &a,
// fx.ParamTags(`name:"A"`)
// ),
// fx.Annotate(
// &b,
// fx.ParamTags(`name:"B"`)
// )
// )
//
// Code above is equivalent to the following:
//
// type Target struct {
// fx.In
//
// a A `name:"A"`
// b B `name:"B"`
// }
// var target Target
// ...
// fx.Populate(&target)
//
// This is most helpful in unit tests: it lets tests leverage Fx's automatic
// constructor wiring to build a few structs, but then extract those structs
// for further testing.
func Populate(targets ...interface{}) Option {
// Validate all targets are non-nil pointers.
fields := make([]reflect.StructField, len(targets)+1)
fields[0] = reflect.StructField{
Name: "In",
Type: reflect.TypeOf(In{}),
Anonymous: true,
}
for i, t := range targets {
if t == nil {
return Error(fmt.Errorf("failed to Populate: target %v is nil", i+1))
}
var (
rt reflect.Type
tag reflect.StructTag
)
switch t := t.(type) {
case annotated:
rt = reflect.TypeOf(t.Target)
tag = reflect.StructTag(t.ParamTags[0])
targets[i] = t.Target
default:
rt = reflect.TypeOf(t)
}
if rt.Kind() != reflect.Ptr {
return Error(fmt.Errorf("failed to Populate: target %v is not a pointer type, got %T", i+1, t))
}
fields[i+1] = reflect.StructField{
Name: fmt.Sprintf("Field%d", i),
Type: rt.Elem(),
Tag: tag,
}
}
// Build a function that looks like:
//
// func(t1 T1, t2 T2, ...) {
// *targets[0] = t1
// *targets[1] = t2
// [...]
// }
//
fnType := reflect.FuncOf([]reflect.Type{reflect.StructOf(fields)}, nil, false /* variadic */)
fn := reflect.MakeFunc(fnType, func(args []reflect.Value) []reflect.Value {
arg := args[0]
for i, target := range targets {
reflect.ValueOf(target).Elem().Set(arg.Field(i + 1))
}
return nil
})
return Invoke(fn.Interface())
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright (c) 2020-2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import "io"
type printerWriter struct{ p Printer }
// writerFromPrinter returns an implementation of io.Writer used to support
// Logger option which implements Printer interface.
func writerFromPrinter(p Printer) io.Writer {
return &printerWriter{p: p}
}
func (w *printerWriter) Write(b []byte) (n int, err error) {
w.p.Printf(string(b))
return len(b), nil
}
+187
View File
@@ -0,0 +1,187 @@
// Copyright (c) 2022 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"fmt"
"reflect"
"strings"
"go.uber.org/dig"
"go.uber.org/fx/internal/fxreflect"
)
// Provide registers any number of constructor functions, teaching the
// application how to instantiate various types. The supplied constructor
// function(s) may depend on other types available in the application, must
// return one or more objects, and may return an error. For example:
//
// // Constructs type *C, depends on *A and *B.
// func(*A, *B) *C
//
// // Constructs type *C, depends on *A and *B, and indicates failure by
// // returning an error.
// func(*A, *B) (*C, error)
//
// // Constructs types *B and *C, depends on *A, and can fail.
// func(*A) (*B, *C, error)
//
// The order in which constructors are provided doesn't matter, and passing
// multiple Provide options appends to the application's collection of
// constructors. Constructors are called only if one or more of their returned
// types are needed, and their results are cached for reuse (so instances of a
// type are effectively singletons within an application). Taken together,
// these properties make it perfectly reasonable to Provide a large number of
// constructors even if only a fraction of them are used.
//
// See the documentation of the In and Out types for advanced features,
// including optional parameters and named instances.
//
// See the documentation for [Private] for restricting access to constructors.
//
// Constructor functions should perform as little external interaction as
// possible, and should avoid spawning goroutines. Things like server listen
// loops, background timer loops, and background processing goroutines should
// instead be managed using Lifecycle callbacks.
func Provide(constructors ...interface{}) Option {
return provideOption{
Targets: constructors,
Stack: fxreflect.CallerStack(1, 0),
}
}
type provideOption struct {
Targets []interface{}
Stack fxreflect.Stack
}
func (o provideOption) apply(mod *module) {
var private bool
targets := make([]interface{}, 0, len(o.Targets))
for _, target := range o.Targets {
if _, ok := target.(privateOption); ok {
private = true
continue
}
targets = append(targets, target)
}
for _, target := range targets {
mod.provides = append(mod.provides, provide{
Target: target,
Stack: o.Stack,
Private: private,
})
}
}
type privateOption struct{}
// Private is an option that can be passed as an argument to [Provide] or [Supply] to
// restrict access to the constructors being provided. Specifically,
// corresponding constructors can only be used within the current module
// or modules the current module contains. Other modules that contain this
// module won't be able to use the constructor.
//
// For example, the following would fail because the app doesn't have access
// to the inner module's constructor.
//
// fx.New(
// fx.Module("SubModule", fx.Provide(func() int { return 0 }, fx.Private)),
// fx.Invoke(func(a int) {}),
// )
var Private = privateOption{}
func (o provideOption) String() string {
items := make([]string, len(o.Targets))
for i, c := range o.Targets {
items[i] = fxreflect.FuncName(c)
}
return fmt.Sprintf("fx.Provide(%s)", strings.Join(items, ", "))
}
func runProvide(c container, p provide, opts ...dig.ProvideOption) error {
constructor := p.Target
if _, ok := constructor.(Option); ok {
return fmt.Errorf("fx.Option should be passed to fx.New directly, "+
"not to fx.Provide: fx.Provide received %v from:\n%+v",
constructor, p.Stack)
}
switch constructor := constructor.(type) {
case annotationError:
// fx.Annotate failed. Turn it into an Fx error.
return fmt.Errorf(
"encountered error while applying annotation using fx.Annotate to %s: %w",
fxreflect.FuncName(constructor.target), constructor.err)
case annotated:
ctor, err := constructor.Build()
if err != nil {
return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %w", constructor, p.Stack, err)
}
opts = append(opts, dig.LocationForPC(constructor.FuncPtr))
if err := c.Provide(ctor, opts...); err != nil {
return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %w", constructor, p.Stack, err)
}
case Annotated:
ann := constructor
switch {
case len(ann.Group) > 0 && len(ann.Name) > 0:
return fmt.Errorf(
"fx.Annotated may specify only one of Name or Group: received %v from:\n%+v",
ann, p.Stack)
case len(ann.Name) > 0:
opts = append(opts, dig.Name(ann.Name))
case len(ann.Group) > 0:
opts = append(opts, dig.Group(ann.Group))
}
if err := c.Provide(ann.Target, opts...); err != nil {
return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %w", ann, p.Stack, err)
}
default:
if reflect.TypeOf(constructor).Kind() == reflect.Func {
ft := reflect.ValueOf(constructor).Type()
for i := 0; i < ft.NumOut(); i++ {
t := ft.Out(i)
if t == reflect.TypeOf(Annotated{}) {
return fmt.Errorf(
"fx.Annotated should be passed to fx.Provide directly, "+
"it should not be returned by the constructor: "+
"fx.Provide received %v from:\n%+v",
fxreflect.FuncName(constructor), p.Stack)
}
}
}
if err := c.Provide(constructor, opts...); err != nil {
return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %w", fxreflect.FuncName(constructor), p.Stack, err)
}
}
return nil
}
+143
View File
@@ -0,0 +1,143 @@
// Copyright (c) 2022 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"fmt"
"reflect"
"strings"
"go.uber.org/fx/internal/fxreflect"
)
// Replace provides instantiated values for graph modification as if
// they had been provided using a decorator with fx.Decorate.
// The most specific type of each value (as determined by reflection) is used.
//
// Refer to the documentation on fx.Decorate to see how graph modifications
// work with fx.Module.
//
// This serves a purpose similar to what fx.Supply does for fx.Provide.
//
// For example, given,
//
// var log *zap.Logger = ...
//
// The following two forms are equivalent.
//
// fx.Replace(log)
//
// fx.Decorate(
// func() *zap.Logger {
// return log
// },
// )
//
// Replace panics if a value (or annotation target) is an untyped nil or an error.
//
// # Replace Caveats
//
// As mentioned above, Replace uses the most specific type of the provided
// value. For interface values, this refers to the type of the implementation,
// not the interface. So if you try to replace an io.Writer, fx.Replace will
// use the type of the implementation.
//
// var stderr io.Writer = os.Stderr
// fx.Replace(stderr)
//
// Is equivalent to,
//
// fx.Decorate(func() *os.File { return os.Stderr })
//
// This is typically NOT what you intended. To replace the io.Writer in the
// container with the value above, we need to use the fx.Annotate function with
// the fx.As annotation.
//
// fx.Replace(
// fx.Annotate(os.Stderr, fx.As(new(io.Writer)))
// )
func Replace(values ...interface{}) Option {
decorators := make([]interface{}, len(values)) // one function per value
types := make([]reflect.Type, len(values))
for i, value := range values {
switch value := value.(type) {
case annotated:
var typ reflect.Type
value.Target, typ = newReplaceDecorator(value.Target)
decorators[i] = value
types[i] = typ
default:
decorators[i], types[i] = newReplaceDecorator(value)
}
}
return replaceOption{
Targets: decorators,
Types: types,
Stack: fxreflect.CallerStack(1, 0),
}
}
type replaceOption struct {
Targets []interface{}
Types []reflect.Type // type of value produced by constructor[i]
Stack fxreflect.Stack
}
func (o replaceOption) apply(m *module) {
for i, target := range o.Targets {
m.decorators = append(m.decorators, decorator{
Target: target,
Stack: o.Stack,
IsReplace: true,
ReplaceType: o.Types[i],
})
}
}
func (o replaceOption) String() string {
items := make([]string, 0, len(o.Targets))
for _, typ := range o.Types {
items = append(items, typ.String())
}
return fmt.Sprintf("fx.Replace(%s)", strings.Join(items, ", "))
}
// Returns a function that takes no parameters, and returns the given value.
func newReplaceDecorator(value interface{}) (interface{}, reflect.Type) {
switch value.(type) {
case nil:
panic("untyped nil passed to fx.Replace")
case error:
panic("error value passed to fx.Replace")
}
typ := reflect.TypeOf(value)
returnTypes := []reflect.Type{typ}
returnValues := []reflect.Value{reflect.ValueOf(value)}
ft := reflect.FuncOf([]reflect.Type{}, returnTypes, false)
fv := reflect.MakeFunc(ft, func([]reflect.Value) []reflect.Value {
return returnValues
})
return fv.Interface(), typ
}
+94
View File
@@ -0,0 +1,94 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"time"
)
// Shutdowner provides a method that can manually trigger the shutdown of the
// application by sending a signal to all open Done channels. Shutdowner works
// on applications using Run as well as Start, Done, and Stop. The Shutdowner is
// provided to all Fx applications.
type Shutdowner interface {
Shutdown(...ShutdownOption) error
}
// ShutdownOption provides a way to configure properties of the shutdown
// process. Currently, no options have been implemented.
type ShutdownOption interface {
apply(*shutdowner)
}
type exitCodeOption int
func (code exitCodeOption) apply(s *shutdowner) {
s.exitCode = int(code)
}
var _ ShutdownOption = exitCodeOption(0)
// ExitCode is a [ShutdownOption] that may be passed to the Shutdown method of the
// [Shutdowner] interface.
// The given integer exit code will be broadcasted to any receiver waiting
// on a [ShutdownSignal] from the [Wait] method.
func ExitCode(code int) ShutdownOption {
return exitCodeOption(code)
}
type shutdownTimeoutOption time.Duration
func (shutdownTimeoutOption) apply(*shutdowner) {}
var _ ShutdownOption = shutdownTimeoutOption(0)
// ShutdownTimeout is a [ShutdownOption] that allows users to specify a timeout
// for a given call to Shutdown method of the [Shutdowner] interface. As the
// Shutdown method will block while waiting for a signal receiver relay
// goroutine to stop.
//
// Deprecated: This option has no effect. Shutdown is not a blocking operation.
func ShutdownTimeout(timeout time.Duration) ShutdownOption {
return shutdownTimeoutOption(timeout)
}
type shutdowner struct {
app *App
exitCode int
}
// Shutdown broadcasts a signal to all of the application's Done channels
// and begins the Stop process. Applications can be shut down only after they
// have finished starting up.
func (s *shutdowner) Shutdown(opts ...ShutdownOption) error {
for _, opt := range opts {
opt.apply(s)
}
return s.app.receivers.b.Broadcast(ShutdownSignal{
Signal: _sigTERM,
ExitCode: s.exitCode,
})
}
func (app *App) shutdowner() Shutdowner {
return &shutdowner{app: app}
}
+146
View File
@@ -0,0 +1,146 @@
// Copyright (c) 2022 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"context"
"fmt"
"os"
"os/signal"
"sync"
)
// ShutdownSignal represents a signal to be written to Wait or Done.
// Should a user call the Shutdown method via the Shutdowner interface with
// a provided ExitCode, that exit code will be populated in the ExitCode field.
//
// Should the application receive an operating system signal,
// the Signal field will be populated with the received os.Signal.
type ShutdownSignal struct {
Signal os.Signal
ExitCode int
}
// String will render a ShutdownSignal type as a string suitable for printing.
func (sig ShutdownSignal) String() string {
return fmt.Sprintf("%v", sig.Signal)
}
func newSignalReceivers() signalReceivers {
return signalReceivers{
notify: signal.Notify,
stopNotify: signal.Stop,
signals: make(chan os.Signal, 1),
b: &broadcaster{},
}
}
// signalReceivers listens to OS signals and shutdown signals,
// and relays them to registered listeners when started.
type signalReceivers struct {
// this mutex protects writes and reads of this struct to prevent
// race conditions in a parallel execution pattern
m sync.Mutex
// our os.Signal channel we relay from
signals chan os.Signal
// when written to, will instruct the signal relayer to shutdown
shutdown chan struct{}
// is written to when signal relay has finished shutting down
finished chan struct{}
// this stub allows us to unit test signal relay functionality
notify func(c chan<- os.Signal, sig ...os.Signal)
stopNotify func(c chan<- os.Signal)
// used to register and broadcast to signal listeners
// created via Done and Wait
b *broadcaster
}
func (recv *signalReceivers) relayer() {
defer func() {
recv.finished <- struct{}{}
}()
select {
case <-recv.shutdown:
return
case signal := <-recv.signals:
recv.b.Broadcast(ShutdownSignal{
Signal: signal,
})
}
}
// running returns true if the the signal relay go-routine is running.
// this method must be invoked under locked mutex to avoid race condition.
func (recv *signalReceivers) running() bool {
return recv.shutdown != nil && recv.finished != nil
}
func (recv *signalReceivers) Start() {
recv.m.Lock()
defer recv.m.Unlock()
// if the receiver has already been started; don't start it again
if recv.running() {
return
}
recv.finished = make(chan struct{}, 1)
recv.shutdown = make(chan struct{}, 1)
recv.notify(recv.signals, os.Interrupt, _sigINT, _sigTERM)
go recv.relayer()
}
func (recv *signalReceivers) Stop(ctx context.Context) error {
recv.m.Lock()
defer recv.m.Unlock()
recv.stopNotify(recv.signals)
// if the relayer is not running; return nil error
if !recv.running() {
return nil
}
recv.shutdown <- struct{}{}
select {
case <-ctx.Done():
return ctx.Err()
case <-recv.finished:
close(recv.shutdown)
close(recv.finished)
recv.shutdown = nil
recv.finished = nil
recv.b.reset()
return nil
}
}
func (recv *signalReceivers) Done() <-chan os.Signal {
return recv.b.Done()
}
func (recv *signalReceivers) Wait() <-chan ShutdownSignal {
return recv.b.Wait()
}
+162
View File
@@ -0,0 +1,162 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"fmt"
"reflect"
"strings"
"go.uber.org/fx/internal/fxreflect"
)
// Supply provides instantiated values for dependency injection as if
// they had been provided using a constructor that simply returns them.
// The most specific type of each value (as determined by reflection) is used.
//
// This serves a purpose similar to what fx.Replace does for fx.Decorate.
//
// For example, given:
//
// type (
// TypeA struct{}
// TypeB struct{}
// TypeC struct{}
// )
//
// var a, b, c = &TypeA{}, TypeB{}, &TypeC{}
//
// The following two forms are equivalent:
//
// fx.Supply(a, b, fx.Annotated{Target: c})
//
// fx.Provide(
// func() *TypeA { return a },
// func() TypeB { return b },
// fx.Annotated{Target: func() *TypeC { return c }},
// )
//
// Supply panics if a value (or annotation target) is an untyped nil or an error.
//
// [Private] can be used to restrict access to supplied values.
//
// # Supply Caveats
//
// As mentioned above, Supply uses the most specific type of the provided
// value. For interface values, this refers to the type of the implementation,
// not the interface. So if you supply an http.Handler, fx.Supply will use the
// type of the implementation.
//
// var handler http.Handler = http.HandlerFunc(f)
// fx.Supply(handler)
//
// Is equivalent to,
//
// fx.Provide(func() http.HandlerFunc { return f })
//
// This is typically NOT what you intended. To supply the handler above as an
// http.Handler, we need to use the fx.Annotate function with the fx.As
// annotation.
//
// fx.Supply(
// fx.Annotate(handler, fx.As(new(http.Handler))),
// )
func Supply(values ...interface{}) Option {
constructors := make([]interface{}, 0, len(values))
types := make([]reflect.Type, 0, len(values))
var private bool
for _, value := range values {
var (
typ reflect.Type
ctor any
)
switch value := value.(type) {
case privateOption:
private = true
continue
case annotated:
value.Target, typ = newSupplyConstructor(value.Target)
ctor = value
case Annotated:
value.Target, typ = newSupplyConstructor(value.Target)
ctor = value
default:
ctor, typ = newSupplyConstructor(value)
}
constructors = append(constructors, ctor)
types = append(types, typ)
}
return supplyOption{
Targets: constructors,
Types: types,
Stack: fxreflect.CallerStack(1, 0),
Private: private,
}
}
type supplyOption struct {
Targets []interface{}
Types []reflect.Type // type of value produced by constructor[i]
Stack fxreflect.Stack
Private bool
}
func (o supplyOption) apply(m *module) {
for i, target := range o.Targets {
m.provides = append(m.provides, provide{
Target: target,
Stack: o.Stack,
IsSupply: true,
SupplyType: o.Types[i],
Private: o.Private,
})
}
}
func (o supplyOption) String() string {
items := make([]string, 0, len(o.Targets))
for _, typ := range o.Types {
items = append(items, typ.String())
}
return fmt.Sprintf("fx.Supply(%s)", strings.Join(items, ", "))
}
// Returns a function that takes no parameters, and returns the given value.
func newSupplyConstructor(value interface{}) (interface{}, reflect.Type) {
switch value.(type) {
case nil:
panic("untyped nil passed to fx.Supply")
case error:
panic("error value passed to fx.Supply")
}
typ := reflect.TypeOf(value)
returnTypes := []reflect.Type{typ}
returnValues := []reflect.Value{reflect.ValueOf(value)}
ft := reflect.FuncOf([]reflect.Type{}, returnTypes, false)
fv := reflect.MakeFunc(ft, func([]reflect.Value) []reflect.Value {
return returnValues
})
return fv.Interface(), typ
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
// Version is exported for runtime compatibility checks.
const Version = "1.23.0"