You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If many targets exist, each with multiple tags, it can be difficult
to know which tag needs to be used to control a given target.
Ease this by explicitly associating a name to each target and
automatically promoting it as a tag so that it needs not be
specified as a tag.
Also, keep state to know if a target is custom or not and use a
distinct macro (custom_target!) to easily spot in the code if
a target is custom or not.
Finally, calling the controller registry methods from the outside
of the crate is not needed since we can register third- party crate
targets with the custom_target! macro. So, update the README.md
explaining that option and make the register method private.
Signed-off-by: Fredi Raspall <fredi@githedgehog.com>
Copy file name to clipboardExpand all lines: tracectl/README.md
+34-25Lines changed: 34 additions & 25 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@
8
8
* However, we currently (mis)use a single hard-coded EnvFilter, without exploiting its flexibility: pretty much all logs are either disabled or governed by a single log-level set at compile time.
9
9
10
10
Ideally, we would be able to change traces' log levels (or even enable/disable them) selectively for the distinct subsystems, at runtime.
11
-
Run-time adjustment of the maximum log-level can be achieved by the EnvFilter object and its reloading capabilities. The EnvFilter allows adjusting log-levels for each *target*. Targets exist for *events* and *spans*. Think of targets as scopes where traces belong or are sent to: when some trace like `info!("Hey")` is added to the code, a target is implicitly created and automatically named, by default, based on the path of the module/submodule where the log resides in the source code (this is done using the `module_path!()` built-in). Knowing target identifiers is needed to adjust their log-level. This can be problematic in the dataplane implementation since the code is spread in distinct crates: we'd like to control the verbosity of targets defined across all crates in a **centralized** fashion; however, keeping some "target database" may not be easy because, if we use the implicit path-based identifiers, the targets in such a database may accidentally get out-of-sync if crates get internally re-organized. For instance, a trace emitted within some `sStateful` submodule within the `nat` crate may use an automatic target of `nat::stateful`. If the nat crate happened to be re-organized or some of the modules renamed, the target ids may become, say, `cgnat::modes::stateful`. This would require the database of targets to be updated and replace `nat::stateful` by `cgnat::modes::stateful`. Some way to get stable identifiers for automatic targets is needed.
11
+
Run-time adjustment of the log-level can be achieved by the EnvFilter object and its reloading capabilities. The EnvFilter allows adjusting log-levels for each *target*. Targets exist for *events* and *spans*. Think of targets as scopes where traces belong or are sent to: when some trace like `info!("Hey")` is added to the code, a target is implicitly created and automatically named, by default, based on the path of the module/submodule where the log resides in the source code (this is done using the `module_path!()` built-in). Knowing target identifiers is needed to adjust their log-level. This can be problematic in the dataplane implementation since the code is spread across distinct crates: we'd like to control the verbosity of targets across all crates in a **centralized** fashion; however, keeping some "target database" may not be easy because, if we use the implicit path-based identifiers, the targets in such a database may accidentally get out-of-sync if crates get internally re-organized. For instance, a trace emitted within some `sStateful` submodule within the `nat` crate may use an implicit target of `nat::stateful`. If the nat crate happened to be re-organized or some of the modules renamed, the target ids may become, say, `cgnat::modes::stateful`. This would require the database of targets to be updated and replace `nat::stateful` by `cgnat::modes::stateful`. Some way to get stable identifiers for implicitly-named targets is needed.
12
12
13
13
One option would be to explicitly set the target names in all the member crates. That would solve the issue of the database becoming out-of-sync on crate reorganization. However, that would not solve the problem of populating the target database in the first place: any time a crate defined a new target, we'd still need to update the database to add it. This may be solved by letting the participating crates register their targets in the database. However, such as solution is inconvenient if done at run-time: how would crates declare their targets? Would there need to be an initialization routine per crate? Who would call that?
14
14
@@ -21,11 +21,16 @@ This crate exists to solve the above issues allowing:
21
21
22
22
## Usage model and requirements
23
23
* This crate cannot (nor should) understand trace semantics. Trace/log semantics and their relations must be provided by the crates defining them.
24
-
* Crates *register* their targets of interest (the targets whose log-level is desired to be dynamically adjusted) by *declaring* them, along with their initial, default log level and the associated **tags**. Tags serve two purposes. First, they act as stable identifiers to refer to targets independently of their name; e.g. in an API. Second, a target may be associated with multiple tags. This allows controlling multiple targets simultaneously. For instance, in a packet pipeline, each network functions (NF) may emit logs to a distinct target; e.g. a NAT NF may have a target labeled as *nat*. This may allow enabling / disabling NAT-related debug logs at runtime, while only emitting warnings or errors in production. If, in addition, the NAT (and rest of NFs) are associated with some tag *pipeline*, one may be able to enable / disable the logs (or restrict them to, say, up to INFO) in all of the NFs composing the pipeline.
24
+
* Crates *register* their targets of interest (the targets whose log-level is to be dynamically adjusted) by *declaring* them, along with their initial, default log level, a stable *name* and optional **tags**. Tags serve two purposes. First, they act as stable identifiers to refer to targets independently of their path; e.g. in an API. Second, a target may be associated with multiple tags. This allows controlling multiple targets simultaneously.
25
+
26
+
For instance, in a packet pipeline, each network function (NF) may emit logs to a distinct target; e.g. a NAT NF may have a target labeled as *nat*. This may allow enabling / disabling NAT-related debug logs at runtime, while only emitting warnings or errors in production. If, in addition, the NAT (and rest of NFs) are associated with some tag *pipeline*, one may be able to enable / disable the logs (or restrict them to, say, up to INFO) in all of the NFs composing the pipeline.
25
27
So, the takeaway is that tags represent **sets of targets** and a target can be member of an arbitrary number of sets.
26
28
* Tags are implemented as strings since each crate should be able to define them and having a custom type (e.g. some *enum* in a centralized crate, like this one) would entail needing to update that crate every time some other crate required a new tag.
27
29
* However, if a tag is to be shared by distinct crates, the consistency of that needs to be enforced outside of this crate.
28
30
31
+
**Note:** The *name* of a target is its main identifier and is automatically treated as a *tag*. Therefore, registered targets always have at least one tag to control them.
32
+
33
+
29
34
## Implementation
30
35
This implementation has about 3 pieces:
31
36
* a *tracing controller*: a thread-safe database of targets, with some static initialization, that allows changing the log-level of targets and the default one and that allows reporting which targets are available, with which tags and log-level. This is important to be able to expose the targets in some form of API. We may expose the *tags* as stable identifiers.
@@ -34,47 +39,48 @@ This implementation has about 3 pieces:
34
39
35
40
# Usage
36
41
37
-
## Target registration (how to declare targets in crates)
42
+
## Target configuration and registration (how to declare targets in crates)
38
43
39
44
### Implicit targets
40
-
Registering implicit targets is straightforward. We just need to declare the target in the module with macro `trace_target!` to associate it with a tag and set the initial log-level.
41
-
45
+
Targets are implicitly created by macros like `info!()`, with a path that defaults to `module_path!()`. In order to be able to control the verbosity of those targets, these need to be *registered*. Registering such implicit targets is straightforward. We just need to declare the target in the module with macro `trace_target!` to associate it with a *name*, the initial log-level and additional, optional tags.
**Note**: the *name* of a target (`"mytarget1"` above) needs not be specified in the array of tags and will be automatically added.
56
+
51
57
### Custom targets
52
58
53
59
Placing a `trace_target!` stanza in each module/submodule should make all traces in a crate controllable via tag(s).
54
60
We may, however, need more control within a module/submodule and be able to govern log levels at a higher granularity.
55
-
This can be achieved by declaring a *custom*target. A custom target (a target with an explicitly-set identifier) can be declared by specifying its name in the first argument to `trace_target!` as
61
+
This can be achieved by declaring *custom*targets. Registering a custom target --a target with an explicitly-set identifier-- can be done similarly as
Emitting logs to target `"custom-target"`, can be done with the extant macros indicating the target as a key-value:
67
+
The above simply registers a configuration for a target called `"my-custom-target"`. However that will do nothing if the target does not exist. For such a target to exist, some logs (events/spans) should refer to it. In order to emit logs within some custom target, the existing macros can be used, specifying the target as a key-value:
62
68
```rust
63
-
info!(target:"custom-target", "This is a log");
69
+
info!(target:"my-custom-target", "This is a log");
64
70
```
65
71
66
72
In order to make the above less verbose, this crate defines new macros (`terror, twarn, tinfo, tdebug` and `ttrace`) which allow you to write, instead:
67
73
```rust
68
-
tinfo!("custom-target", "This is a log");
74
+
tinfo!("my-custom-target", "This is a log");
69
75
```
70
76
71
77
So, a consistent way of defining custom targets within a module may be:
... where `"tonic"` may be used to control all logs in the crate and `"third-party"` be a shared tag to control all third-party crates.
119
+
Additional custom targets could be created for the crate's modules, but that would require knowing the internal organization of the crate and such configs could get out of sync. Using a single target config with the name of the crate should guarantee that the configuration is in sync, which suffices in our case since we may use those target configs to mute all logs or limit them to just errors.
105
120
106
121
## Notes
107
122
* Using custom targets has implications on log formatting, depending on how the formatting layer is configured, since target names may be displayed.
108
-
* Targets may be declared *without* tags. That makes it not possible to change them dynamically, which is useful in some cases. E.g. we may never want the traces emitted by *this* crate to be adjustable.
123
+
* Targets may be declared *without* tags: they will always get one equal to their *name*.
109
124
* The way the target registration works, targets may be declared in any place in the code; even within functions. The recommendation is, however, to place them at the beginning of each source code file.
110
125
* The way the Envfilters are built by this crate, if an (implicit) target is not registered (i.e. no `trace_target!()` is explicitly set) in some module/submodule, its log-level will be that of the nearest ancestor in the hierarchy. If no ancestor target is explicitly registered, the log-level will be governed by the *default*.
111
126
This means that `trace_target!()`*needs not be added in every source code file*. The rule of thumb should be: if you believe that some set of debug logs are worth being governed separately (e.g. because they are generally verbose and usually not needed, but may be worth enabling at run-time), then declare their target. Else, don't.
@@ -140,13 +155,7 @@ The tracing controller also allows setting the desired default log-level as:
The dataplane uses third-party crates that emit logs themselves (e.g. *tonic* or *h2*). Since we can't use the 'automatic' target registry based on `trace_target!` in those crates, we may control those by directly invoking the controller methods as
... where tag `"tonic"` may be the tag to control the crate and `"third-party"` be a shared tag to control all of the targets in third-party crates. In most cases, this may be used to mute/disable logs or limit them to just errors.
150
159
151
160
### Checking targets, tags and levels
152
161
The tracing controller has several other methods to:
0 commit comments